Notifications
Article
Unity ECS(二)HelloWorld!ECS!(1)
Updated 10 months ago
3.0 K
36
E
从方块开始构建第一个ECS程序
为了方便利用传统的MonoBehaviour编程方式的人员更容易理解上手,本文将不会从纯ECS开始,部分源仍使用MonoBehaviour的组件来作为数据输入源。
在这篇文章中,我将会编写这样一个由海量方块组成的噪波运动图形来引导你制作第一个HelloWorld ECS程序。并告诉你ECS是如何体现它的优势。

如果是初次使用ECS的人,可以先不用去细想每一个API的含义或者作用是什么,只关注过程则非常简单,但尽量要充分理解ECS与DOTS是如何进行工作的,在代码层面让你对整个编码流程有一个预了解,我会在下一篇文章中对其中的内容进行讲解。
(使用Unity版本为Unity2019.2.6)

(一)一些起始准备

前往Window->Package Manager,要使用ECS需要安装以下的组件:
安装Entities、Jobs、Mathematics、Hybrid Render(有些组件会被自动安装,确保完成后工程内有以下包的存在)

(二)创建一个方块实体

创建一个MonoBehaviour脚本,起名为CreateCubeEntity。引入命名空间:
using Unity.Entities; using Unity.Mathematics;
在脚本内撰写方法:
void CreateCube() { var manager = World.Active.EntityManager; var archeType = manager.CreateArchetype ( ComponentType.ReadWrite<LocalToWorld>(), ComponentType.ReadWrite<Translation>(), ComponentType.ReadOnly<RenderMesh>() ); var entity = manager.CreateEntity(archeType); manager.SetComponentData(entity,new Translation() { Value = new float3(0,0,0) }); var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); cube.GetComponent<MeshRenderer>().material.color=Color.black; manager.SetSharedComponentData(entity,new RenderMesh() { mesh=cube.GetComponent<MeshFilter>().sharedMesh, material = cube.GetComponent<MeshRenderer>().material, subMesh = 0, castShadows = UnityEngine.Rendering.ShadowCastingMode.Off, receiveShadows = false }); Destroy(cube); }
并在Start()函数内调用此方法:
void Start() { CreateCube(); }
将脚本挂在你喜欢的地方,运行,那么你会得到这样的结果:

(重要)在Entity Debugger中查看实体:

请注意,实体不是对象,你无法像对常规GameObject那样对方块实体进行操作(你甚至无法选中它!)
请在window->Analysis->Entity Debugger开启实体调试窗口,Entity Debugger窗口十分重要,你可以在此了解关于程序进程中的ECS信息,请将它设置为界面中的常驻停靠窗口。
和Hierarchy窗口类似,Entity Debugger列出了场景内所有实体。除此之外还有很多别的信息,但现在只是告诉你在哪里查看方块实体,让我们先把注意力放在流程上。

(三)创建方块阵列

为CreateCubeEntity脚本增加字段:
public int row; public int colum;
在原方法CreateCube()内编写(紧接着Destory(Cube))
void CreateCube() { ... Destroy(cube); //new code using (NativeArray<Entity> entities = new NativeArray<Entity>(row * colum, Allocator.Temp, NativeArrayOptions.UninitializedMemory)) { manager.Instantiate(entity, entities); for (int i = 0; i < row; i++) { for (int j = 0; j < colum; j++) { int index = i + j * colum; manager.SetComponentData(entities[index],new Translation() { Value = new float3(i,0,j) }); } } } }
到场景内挂载脚本的物件下,设置你喜欢的尽量大的数值,运行,那么你应该会得到一个方块实体组成的平面,它的长宽是你设置的Row Colum值。
此时你可以检查Entity Debugger窗口 看看发生了哪些变化。

(四)创建NoiseHeightSystem

为了让方块实体阵列运动,现在要来编写ECS中的S(System)。新建Monobehaviour脚本,命名为NoiseHeightSystem,同样引入和上文提到的一样的命名空间并将类继承ComponentSystem(强制要求实现OnUpdate()方法)。
完整的NoiseHeightSystem代码如下:
using UnityEngine; using Unity.Entities; using Unity.Mathematics; using Unity.Transforms; public class NoiseHeightSystem : ComponentSystem { protected override void OnUpdate() { var time = Time.realtimeSinceStartup; Entities.ForEach((ref Translation translation) => { translation.Value.y = 3 * noise.snoise(new float2(time + 0.02f * translation.Value.x, time + 0.02f * translation.Value.z)); }); } }
完整的CreateCubeEntity脚本如下
using Unity.Collections; using UnityEngine; using Unity.Entities; using Unity.Mathematics; using Unity.Rendering; using Unity.Transforms; public class CreateCubeEntity : MonoBehaviour { public int row; public int colum; // Start is called before the first frame update void Start() { CreateCube(); } void CreateCube() { var manager = World.Active.EntityManager; var archeType = manager.CreateArchetype ( ComponentType.ReadWrite<LocalToWorld>(), ComponentType.ReadWrite<Translation>(), ComponentType.ReadOnly<RenderMesh>() ); var entity = manager.CreateEntity(archeType); manager.SetComponentData(entity,new Translation() { Value = new float3(0,0,0) }); var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); cube.GetComponent<MeshRenderer>().material.color=Color.black; manager.SetSharedComponentData(entity,new RenderMesh() { mesh=cube.GetComponent<MeshFilter>().sharedMesh, material = cube.GetComponent<MeshRenderer>().material, subMesh = 0, castShadows = UnityEngine.Rendering.ShadowCastingMode.Off, receiveShadows = false }); Destroy(cube); using (NativeArray<Entity> entities = new NativeArray<Entity>(row * colum, Allocator.Temp, NativeArrayOptions.UninitializedMemory)) { manager.Instantiate(entity, entities); for (int i = 0; i < row; i++) { for (int j = 0; j < colum; j++) { int index = i + j * colum; manager.SetComponentData(entities[index],new Translation() { Value = new float3(i,0,j) }); } } } }
编写完成后运行,你应该会看到方块实体阵列开始呈噪波图样运动。
同样记得观察Entity Debugger中发生了什么变化。
如果你到最后一步都完美无缺并实现了效果,那么恭喜你,但是别高兴太早。这个程序仍然有问题:
  1. 利用Monobehaviour做数据输入源(CreateCubeEntity脚本中的字段 int row 与 int colum),并不符合ECS的理念,这是基于快速实现效果的妥协。
  2. System中存在固有的数据,请牢记System只负责进行运算,应当将数据与System剥离开来。
  3. 它仍然是单线程运作的,并未利用到DOTS。
现在你可以休息一下,我将在后边的文章中告诉你如何完善这个HelloWorld程序,并利用DOTS提高它的性能。
E
EntherVarope
Electronic Arts-Leader Programmer - Programmer
6
Comments
YyzGame
16 days ago
可以参考一下这篇英文教程,写得相当不错 :) https://www.raywenderlich.com/7630142-entity-component-system-for-unity-getting-started
0
YyzGame
16 days ago
YyzGamevoid CreateCube() { var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); cube.GetComponent<MeshRenderer>().material.color=Color.black;
var manager = World.DefaultGameObjectInjectionWorld.EntityManager; var archeType = manager.CreateArchetype ( typeof(Translation), typeof(Rotation), typeof(RenderMesh), typeof(RenderBounds), typeof(LocalToWorld));
var entity = manager.CreateEntity(archeType);
manager.AddComponentData(entity,new Translation() { Value = new float3(0,0,0) });

manager.SetSharedComponentData(entity,new RenderMesh() { mesh=cube.GetComponent<MeshFilter>().sharedMesh, material = cube.GetComponent<MeshRenderer>().material, subMesh = 0, castShadows = UnityEngine.Rendering.ShadowCastingMode.Off, receiveShadows = false });
Destroy(cube); }
0
YyzGame
16 days ago
void CreateCube() { var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); cube.GetComponent<MeshRenderer>().material.color=Color.black;
var manager = World.DefaultGameObjectInjectionWorld.EntityManager; var archeType = manager.CreateArchetype ( typeof(Translation), typeof(Rotation), typeof(RenderMesh), typeof(RenderBounds), typeof(LocalToWorld));
var entity = manager.CreateEntity(archeType);
manager.AddComponentData(entity,new Translation() { Value = new float3(0,0,0) });

manager.SetSharedComponentData(entity,new RenderMesh() { mesh=cube.GetComponent<MeshFilter>().sharedMesh, material = cube.GetComponent<MeshRenderer>().material, subMesh = 0, castShadows = UnityEngine.Rendering.ShadowCastingMode.Off, receiveShadows = false });
Destroy(cube); }
0
YyzGame
16 days ago
新版本代码:
0
K
Kyou
a month ago
没有“RenderMesh”这个类型啊?
0