本章節為大家解析ECS案例,資源來自,大家自行擷取:
https://github.com/Unity-Technologies/EntityComponentSystemSamples
ECS案例解析
1、ForEach——ECS入門案例

在該案例中有兩個元件,一個是RotationSpeedSystem_ForEach,還有一個是ConvertToEntity
1.1、ConvertToEntity——Entity
這個元件的作用就是:将Inspector面闆的GameObject從根節點開始,将整個GameObject(包括子物體)全部轉成Entity,并且将所有現有的、能轉化的Component轉換成ECS的Component
在ConvertToEntity代碼中,執行了GameObjectConversionUtility.Convert()函數,而"能轉化"的四億是隻有當該元件實作了接口IConvertGameObjectToEntity的Convert()函數,才會根據Convert()函數産生作用
最後把原本的GameObject銷毀掉
1.2、RotationSpeedAuthoring_ForEach——Convert
using System;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
[RequiresEntityConversion]
public class RotationSpeedAuthoring_ForEach : MonoBehaviour, IConvertGameObjectToEntity
{
public float DegreesPerSecond;
//那麼這裡就是實作了1.1中的Convert函數,在這個Convert函數中
//将MonoBehaviour的參數作為RotationSpeed_ForEach元件的參數傳入
//并且從角度制變為弧度制
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
var data = new RotationSpeed_ForEach { RadiansPerSecond = math.radians(DegreesPerSecond) };
dstManager.AddComponentData(entity, data);
}
}
1.3、RotationSpeed_ForEach——Component
ECS中的元件繼承IComponentData,是struct類型,而在這個結構體中隻儲存了資料
using System;
using Unity.Entities;
// Serializable屬性是為了支援編輯器
[Serializable]
//記住IComponentData是struct類型
public struct RotationSpeed_ForEach : IComponentData//首先要繼承IComponentData
{
public float RadiansPerSecond;//元件中的資料
}
1.4、RotationSpeedSystem_ForEach——System
這部分内容,我已經在我的ECS(七)中介紹過了,這裡使用ComponentSystem處理資料,ComponentSystem在主線程上運作,是以不利用多個CPU核心,一般而言不推薦使用
ComponentSystem提供了Entities.ForEach函數,在系統的OnUpdate()函數中調用ForEach,傳入一個lambda函數,該函數會将相關元件作為參數并執行必要的工作
再次就是為具有RotationQuaternion和RotationSpeed元件的實體設定旋轉動畫
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
//這個系統會更新場景中所有RotationSpeed_ForEach和Rotation元件的實體
public class RotationSpeedSystem_ForEach : ComponentSystem
{
protected override void OnUpdate()
{
// Entities.ForEach會在主線程中運作(沒有用到Job),但是為了更好的性能,不推薦這麼使用
// 這邊隻是作為一個案例為大家介紹ECS,并且能夠快速入門
Entities.ForEach((ref RotationSpeed_ForEach rotationSpeed, ref Rotation rotation) =>
{
var deltaTime = Time.deltaTime;
rotation.Value = math.mul(math.normalize(rotation.Value),
quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * deltaTime));
});
}
}
1.5、小結
那麼我們總結一下最簡單的ECS使用步驟:
- 參考1.1為你需要轉換的ECS實體挂上ConvertToEntity腳本,它會自動把能轉換的GameObject Component轉換為Entity Component
-
如果想要自己的元件,就參考1.2實作Convert接口
2.1 元件的寫法參考1.3
- 參考1.4通過最簡單的ComponentSystem來周遊所有實體,找出符合要求的實體,然後對它們進行更新
- 在Foreach中加入你想要的更新函數
那麼到此為止,我們就學會了最基本也是最簡單的ECS的用法
2、ForEachWithEntityChanges——Prefabs To Entity
為了看上去友善,我們将代碼分隔開
public class Spawner_ForEachWithEntityChanges : MonoBehaviour
{
public GameObject Prefab;
public int CountX = 100;
public int CountY = 100;
這裡都是基本寫法
void Start()
{
Entity prefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(Prefab, World.Active);
EntityManager entityManager = World.Active.EntityManager;
在這一步,我們做了兩件事:
- 擷取目前World(預設World)中的EntityManager
- 使用GameObjectConversionUtility.ConvertGameObjectHierarchy函數将Prefab轉化成Entity
for (int x = 0; x < CountX; x++)
{
for (int y = 0; y < CountX; y++)
{
Entity instance = entityManager.Instantiate(prefab);
float3 position = transform.TransformPoint(new float3(x - CountX/2, noise.cnoise(new float2(x, y) * 0.21F) * 10, y - CountY/2));
entityManager.SetComponentData(instance, new Translation(){ Value = position });
entityManager.AddComponentData(instance, new MoveUp_ForEachWithEntityChanges());
entityManager.AddComponentData(instance, new MovingCube_ForEachWithEntityChanges());
}
}
}
}
那麼最後就是利用已經轉化成Entity的prefab為原型來建立更多的Entity
position的位置是随機生成的,然後我們通過SetComponentData來修改Translation(類似原本的Transform)該Entity的位置
然後通過AddComponentData為Entity添加MoveUp_ForEachWithEntityChanges和MovingCube_ForEachWithEntityChanges這兩個自定義的元件
這兩個元件為空,它們隻是作為Tag友善後續的處理
2.1、ComponentSystem解析
為了看上去友善,我們将代碼分隔開
public class MovementSystem_ForEachWithEntityChanges : ComponentSystem
{
protected override void OnUpdate()
{
同樣在這裡用到的是ComponentSystem,用法已經在上面說過了
Entities.WithAllReadOnly<MovingCube_ForEachWithEntityChanges, MoveUp_ForEachWithEntityChanges>().ForEach(
(Entity id, ref Translation translation) =>
{
var deltaTime = Time.deltaTime;
translation = new Translation()
{
Value = new float3(translation.Value.x, translation.Value.y + deltaTime, translation.Value.z)
};
if (translation.Value.y > 10.0f)
EntityManager.RemoveComponent<MoveUp_ForEachWithEntityChanges>(id);
}
);
與之前不同的是,這次我們使用了WithAllReadOnly函數來擷取具有元件的實體——顯而易見,這兩個元件中沒有資料,是以隻讀相較于讀/寫可以更加節省性能(還有其他的優勢,請參考之前的文章)
那麼,現在我們已經擷取到了所有有這兩個元件(MovingCube和MoveUp) 的實體,然後讓他們上升,直到高度大于10之後,就移除MoveUp_ForEachWithEntityChanges元件
Entities.WithAllReadOnly<MovingCube_ForEachWithEntityChanges>().WithNone<MoveUp_ForEachWithEntityChanges>().ForEach(
(Entity id, ref Translation translation) =>
{
translation = new Translation()
{
Value = new float3(translation.Value.x, -10.0f, translation.Value.z)
};
EntityManager.AddComponentData(id, new MoveUp_ForEachWithEntityChanges());
}
);
那麼在下面這個周遊中,我們獲得了有MovingCube_ForEachWithEntityChanges元件,但是沒有MoveUp_ForEachWithEntityChanges元件的實體——由于上一次循環,高度大于10的實體的該元件都被益處了——是以,在這個循環中,我們需要為這些元件重新設定高度,然後重新為它們添加MoveUp_ForEachWithEntityChanges元件,那麼在下個循環,它們又能被上面那個循環周遊到,繼續向上移動
}
}
效果如下
2.2、小結
在這個案例中,我們可以将Prefab轉化成Entity,然後将其作為原型生成更多的Entity,具體步驟如下:
- 建立或擷取到一個EntityManager(用于管理Entity)
- 通過GameObjectConversionUtility.ConvertGameObjectHierarchy将Prefab轉化成Entity
- 為Entity設定或添加元件(也可以和案例1中的方式一樣,通過實作IConvertGameObjectToEntity接口來添加或設定元件)