Unity 2018 的新功能 ECS 现在已经放出了预览版本,我们现在就来尝试一下
—
Hybrid模式
hybrid 就是混合模式,因为 Unity ECS 现在还只有预览版本
对 GameObject 和各种 Render 支持很差
所以混合模式可以看成现在的 GameObject 模式到纯 ECS 之间的过渡模式
Hybrid 模式下,并没有在效率上有非常大的提升
比起纯 ECS 模式
- 初始化时间(遍历寻找Entity的过程)无法优化
- 载入时间无法优化
- 数据在内存中是随机获取的,非线性,执行效率下降
- 无法利用多核处理器
- 没有SIMD
但是我们任然可以先尝试通过这种方式,来提高编程效率
并提前熟悉 Unity ECS 的思维模式
资源准备
首先 导入了一组 星际争霸中刺蛇的 Sprite
先在 Sprite Editor 中切好了刺蛇的 走路 Sprite
做好了刺蛇16个方向走路的动作 Animation 和 Animator
Animator 增加参数 Direction Float 控制刺蛇的移动方向
总共 16 方向 做好从 AnyState 到每个方向的 Direction 条件
我们不需要转换动画,所以 每个 translation 的 setting 里面的 duration 都改成 0
并且要把 Can translate to self 取消勾选,不然 永远会卡第一帧
我们先通过简单的键盘操作来控制刺蛇移动
设计我们的系统和实体
实体
单位实体 Unit 包含的组件
- 位置 Position
- 速度 Velocity
- 单位 Unit
- 输入 PlayerInput
- 可转向 Directable
系统
逻辑系统
运动系统-需要
输入系统-需要 的组件
- 输入 PlayerInput
- 速度 Velocity
同步 GameObject 状态的系统
同步 transform 系统
同步刺蛇 Animator 方向状态的系统
- 速度 Velocity (速度控制朝向)
- Animator
编写上述组件和系统的代码
组件
位置
1 2 3 4 5 6
| using Unity.Mathematics; using UnityEngine; public class Position2D : MonoBehaviour { // 位置 x y public float2 Value; }
|
输入
1 2 3 4 5 6 7
| using Unity.Mathematics; using UnityEngine;
public class PlayerInput : MonoBehaviour { // 输入 x y public float2 Move; }
|
可转向
1 2 3 4 5 6
| using Unity.Mathematics; using UnityEngine;
public class Directable : MonoBehaviour {
}
|
单位
1 2 3 4 5 6
| using Unity.Mathematics; using UnityEngine;
public class Unit : MonoBehaviour { }
|
速度
1 2 3 4 5 6 7
| using Unity.Mathematics; using UnityEngine; public class Velocity : MonoBehaviour { // 速度 x y public float2 Value;
}
|
大家可以看到我们这边都是继承的 MonoBehavior ,有的还是一些空的类
继承 MonoBehavior 才能被添加到 GameObject 中,至于空类的作用我们下面在讲系统的时候来讲
系统
ECS 中的 S 意为 System 就是系统了,系统的做法,下面已经比较清楚了
在系统中 可以通过 GetEntities 来自动获得声明好的 结构体数据
结构体数据是从哪里获得的呢?
答案是从游戏中所有的 Entity 来自动获取,由于我们使用的是一个 GameObject ,要获得这个 GameObject 的数据,需要在这个 GameObject 中添加一个内置脚本 GameObjectEntity
表示我们这个 GameObject 是一个 Entity 了
那么大家可能也就知道上面那些空类的作用了
假如我们不对 GameObject 添加 Directable 脚本的话
就无法被 SyncAnimatorDirectionSystem(见下方代码) 中的 GetEntities 来获得
的作用就是获取含有 T 中所有字段类型的 所有 Entity
System 可以使用 UpdateAfter 或者 UpdateBefore 来控制其执行顺序
输入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| using Unity.Entities; using Unity.Mathematics; using UnityEngine; public class PlayerInputSystem : ComponentSystem { struct PlayerData {
public PlayerInput Input; public Velocity Velocity;
}
protected override void OnUpdate () { float dt = Time.deltaTime;
foreach (var entity in GetEntities<PlayerData> ()) { var pi = entity.Input;
pi.Move.x = Input.GetAxis ("Horizontal"); pi.Move.y = Input.GetAxis ("Vertical");
entity.Velocity.Value = new float2 (pi.Move.x, pi.Move.y);
} } }
|
移动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| using Unity.Entities; using Unity.Mathematics; using UnityEngine; public class UnitMoveSystem : ComponentSystem { struct MoveUnit {
public Position2D Position;
public Velocity MoveSpeed;
public Unit unit; }
protected override void OnUpdate () { var dt = Time.deltaTime; foreach (var entity in GetEntities<MoveUnit> ()) { var pos = entity.Position; pos.Value += entity.MoveSpeed.Value * dt; } } }
|
同步位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| using Unity.Collections; using Unity.Entities; using Unity.Mathematics; using UnityEngine;
[UpdateAfter (typeof (UnitMoveSystem))] public class SyncTransformSystem : ComponentSystem { public struct Data {
[ReadOnly] public Position2D Position;
public Transform Output; }
protected override void OnUpdate () { foreach (var entity in GetEntities<Data> ()) { float2 p = entity.Position.Value; entity.Output.position = new float3 (p.x, p.y, 0); } } } ````
#### 同步动画朝向状态 ```` C# using Unity.Collections; using Unity.Entities; using Unity.Mathematics; using UnityEngine;
[UpdateAfter (typeof (PlayerInputSystem))] public class SyncAnimatorDirectionSystem : ComponentSystem {
public struct Data {
[ReadOnly] public Velocity moveSpeed;
public Animator Output;
public Directable directable; }
protected override void OnUpdate () { foreach (var entity in GetEntities<Data> ()) {
float2 p = entity.moveSpeed.Value;
var angle = Mathf.Atan2 (p.x, p.y) * Mathf.Rad2Deg; if (angle < 0) { angle = 360 + angle; } entity.Output.SetFloat ("Direction", angle);
} } } ````
然后在 刺蛇的 Prefab 下添加需要的组件脚本
如下图
{% asset_img unity_02.png 刺蛇预制体的脚本添加 %}
加载刺蛇预制体,这里使用 Lua 加载的
``` Lua local go = UnityEngine.GameObject.Instantiate(prefab)
|
效果
按 wasd 来操作刺蛇移动