Mapz's Blog

可以递归的函数指针

Unity学习:ECS学习(一)Hybrid模式ECS控制刺蛇移动

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

系统

逻辑系统

运动系统-需要

  • 位置 Position
  • 速度 Velocity

输入系统-需要 的组件

  • 输入 PlayerInput
  • 速度 Velocity

同步 GameObject 状态的系统

同步 transform 系统

  • 位置 Position
  • 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 来获得

1
GetEntities <T>

的作用就是获取含有 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 来操作刺蛇移动