从方块开始构建第一个ECS程序
为了方便利用传统的MonoBehaviour编程方式的人员更容易理解上手,本文将不会从纯ECS开始,部分源仍使用MonoBehaviour的组件来作为数据输入源。
在这篇文章中,我将会编写这样一个由海量方块组成的噪波运动图形来引导你制作第一个HelloWorld ECS程序。并告诉你ECS是如何体现它的优势。
一万个Cube进行噪波运动
如果是初次使用ECS的人,可以先不用去细想每一个API的含义或者作用是什么,只关注过程则非常简单,但尽量要充分理解ECS与DOTS是如何进行工作的,在代码层面让你对整个编码流程有一个预了解,我会在下一篇文章中对其中的内容进行讲解。
(使用Unity版本为Unity2019.2.6)
(一)一些起始准备
前往Window->Package Manager,要使用ECS需要安装以下的组件:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5CN1YGZ2MzMyMmMzAzNhdTN2EjN2EDM4UGZ0IGNyUTMz8CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
请在Package Manager中安装Entities、Jobs、Mathematics、Hybrid Render
安装Entities、Jobs、Mathematics、Hybrid Render(有些组件会被自动安装,确保完成后工程内有以下包的存在)
(二)创建一个方块实体
创建一个MonoBehaviour脚本,起名为CreateCubeEntity。引入命名空间:
using Unity.Entities; using Unity.Mathematics;
在脚本内撰写方法:
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();
}
将脚本挂在你喜欢的地方,运行,那么你会得到这样的结果:
在坐标0,0,0的地方出现了一个黑色的方块实体
(重要)在Entity Debugger中查看实体:
请注意,实体不是对象,你无法像对常规GameObject那样对方块实体进行操作(你甚至无法选中它!)
请在window->Analysis->Entity Debugger开启实体调试窗口,Entity Debugger窗口十分重要,你可以在此了解关于程序进程中的ECS信息,请将它设置为界面中的常驻停靠窗口。
Entity 0即是刚生成的黑色方块实体
和Hierarchy窗口类似,Entity Debugger列出了场景内所有实体。除此之外还有很多别的信息,但现在只是告诉你在哪里查看方块实体,让我们先把注意力放在流程上。
(三)创建方块阵列
为CreateCubeEntity脚本增加字段:
在原方法CreateCube()内编写(紧接着Destory(Cube))
public int row;
public int colum;
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中发生了什么变化。
如果你到最后一步都完美无缺并实现了效果,那么恭喜你,但是别高兴太早。这个程序仍然有问题:
- 利用Monobehaviour做数据输入源(CreateCubeEntity脚本中的字段 int row 与 int colum),并不符合ECS的理念,这是基于快速实现效果的妥协。
- System中存在固有的数据,请牢记System只负责进行运算,应当将数据与System剥离开来。
- 它仍然是单线程运作的,并未利用到DOTS。
现在你可以休息一下,我将在后边的文章中告诉你如何完善这个HelloWorld程序,并利用DOTS提高它的性能。
转自 https://connect.unity.com/p/unityecs-er-helloworld-ecs-1