從方塊開始建構第一個ECS程式
為了友善利用傳統的MonoBehaviour程式設計方式的人員更容易了解上手,本文将不會從純ECS開始,部分源仍使用MonoBehaviour的元件來作為資料輸入源。
在這篇文章中,我将會編寫這樣一個由海量方塊組成的噪波運動圖形來引導你制作第一個HelloWorld ECS程式。并告訴你ECS是如何展現它的優勢。
一萬個Cube進行噪波運動
如果是初次使用ECS的人,可以先不用去細想每一個API的含義或者作用是什麼,隻關注過程則非常簡單,但盡量要充分了解ECS與DOTS是如何進行工作的,在代碼層面讓你對整個編碼流程有一個預了解,我會在下一篇文章中對其中的内容進行講解。
(使用Unity版本為Unity2019.2.6)
(一)一些起始準備
前往Window->Package Manager,要使用ECS需要安裝以下的元件:
請在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