天天看點

Unity ECS+Jobs System筆記 案例解析1(十)

本章節為大家解析ECS案例,資源來自,大家自行擷取:

https://github.com/Unity-Technologies/EntityComponentSystemSamples

ECS案例解析

1、ForEach——ECS入門案例

Unity ECS+Jobs System筆記 案例解析1(十)

在該案例中有兩個元件,一個是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.1為你需要轉換的ECS實體挂上ConvertToEntity腳本,它會自動把能轉換的GameObject Component轉換為Entity Component
  2. 如果想要自己的元件,就參考1.2實作Convert接口

    2.1 元件的寫法參考1.3

  3. 參考1.4通過最簡單的ComponentSystem來周遊所有實體,找出符合要求的實體,然後對它們進行更新
  4. 在Foreach中加入你想要的更新函數

那麼到此為止,我們就學會了最基本也是最簡單的ECS的用法

Unity ECS+Jobs System筆記 案例解析1(十)

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;
           

在這一步,我們做了兩件事:

  1. 擷取目前World(預設World)中的EntityManager
  2. 使用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元件,那麼在下個循環,它們又能被上面那個循環周遊到,繼續向上移動

}
    }
           

效果如下

Unity ECS+Jobs System筆記 案例解析1(十)

2.2、小結

在這個案例中,我們可以将Prefab轉化成Entity,然後将其作為原型生成更多的Entity,具體步驟如下:

  1. 建立或擷取到一個EntityManager(用于管理Entity)
  2. 通過GameObjectConversionUtility.ConvertGameObjectHierarchy将Prefab轉化成Entity
  3. 為Entity設定或添加元件(也可以和案例1中的方式一樣,通過實作IConvertGameObjectToEntity接口來添加或設定元件)