天天看點

Unity 革命性技術DOST入門二 ECS簡單使用介紹1.什麼是ECS?2.ECS為何快?3.ECS架構生命周期示意圖4.ECS使用介紹

上一篇:Unity 革命性技術DOST入門一 使用介紹

1.什麼是ECS?

ECS是一種軟體架構模式,由三個元素組成:實體(Entity),元件(Component)和系統(System)(看起來和MVC很相似)。遊戲程式分為這三個主要元素,并且通過定義每個系統的責任和關系來管理遊戲。

實體代表遊戲世界中的事物。實體本身沒有特定功能,它們将會被元件填充來成為一個實體。

元件是附加到事物的資料。重點不是對象,而是資料,沒有辦法操縱它。比如操作遊戲的角色時,位置,速度和體力等每個狀态都将成為一個元件部分,并與稱為“角色”實體相關聯, 我們在修改角色狀态時隻需要修改其單獨的資料元件即可,不會觸碰到角色的邏輯。

下面為官方介紹:

實體元件系統(ECS)是Unity面向資料的技術堆棧的核心。顧名思義,ECS包含三個主要部分:
  • Entities:填充您的遊戲或程式的實體或事物。
  • Components:與您的實體相關聯的資料,但是應該有資料本身而不是實體來組織。(這種組織上的差異正是面向對象和面向資料的設計之間的關鍵差異之一)
  • Systems:主要邏輯所在,是把Components的資料從目前狀态轉換為下一個狀态的邏輯,例如,一個system可能會通過他們的速度乘以從前一幀到這一幀的時間間隔來更新所有的移動中的entities的位置。

這裡為實際開發中了解:

  • E: Entity 一個不代表任何意義的實體(可以了解為Unity裡的一個空的GameObject)
  • C: Component 一個隻包含資料的元件(可以了解為Unity的一個自定義元件,裡面隻有資料,沒有任何方法)
  • S: System 一個用來處理資料的系統(可以了解為Unity的一個自定義元件,裡面隻有方法,沒有任何資料)

2.ECS為何快?

要想知道ECS為和快,我們就需要去了解一下 CPU與緩存(Cache)

首先我們先來了解一下CPU讀取資料時的操作,首先CPU會先從自己的緩存中去查找,如下圖,若緩存中沒有找到需要的資料,則會去記憶體中查找,CPU在記憶體中找到資料後就會将新資料存放在緩存(Cache)當中。但是CPU通路記憶體的速度會比通路Cache的速度慢100倍,是以提高緩存命中率,避免頻繁去記憶體會大大提高性能。是以我們應該盡量使用數組,避免資料分散,盡量連續的進行處理。

最常見的例子就是在資料量小的情況下周遊數組會比周遊List快上很多,因為數組是有序的,而清單則是分散的,無序的。

Unity 革命性技術DOST入門二 ECS簡單使用介紹1.什麼是ECS?2.ECS為何快?3.ECS架構生命周期示意圖4.ECS使用介紹

在我們的傳統模式中,假設我們現在想要旋轉并移動場景中的一個物體,那麼我們會修改它的Position和Rotation,但是使用的時候整個Transform都會被加到緩存當中,而Transform中有很多我們不需要的屬性占用了很大的緩存空間,如下圖,是以就造成了嚴重的記憶體浪費。若有上千萬個這樣的方塊,那麼我們緩存中可能就會存在超50%以上的記憶體垃圾,在加上這些屬性在記憶體中的排放都是無序的,進而導緻緩存的資料命中率大大的降低,導緻我們的性能下降。

Unity 革命性技術DOST入門二 ECS簡單使用介紹1.什麼是ECS?2.ECS為何快?3.ECS架構生命周期示意圖4.ECS使用介紹

使用ECS就可以解決上述的這些問題,進而提高性能。

因為我們ECS是資料元件化的,需要哪些資料,就聲明哪些資料,不會造成上面那樣嚴重的記憶體浪費!

比如我們同樣需要修改位移和旋轉,但是我們隻需要聲明一個float3資料和一個float4的資料即可解決,與上面相比而言, 我們占的記憶體,微乎其微!

3.ECS架構生命周期示意圖

相信有不少人在初學ECS時,隻知道在System.Update中編寫邏輯就行,知道Update會執行,但可能不明白他是怎麼被調用的,于是部落客去剖析了一下Entities源碼,得到了下面這一張運作流程圖。

Unity 革命性技術DOST入門二 ECS簡單使用介紹1.什麼是ECS?2.ECS為何快?3.ECS架構生命周期示意圖4.ECS使用介紹

首先在遊戲啟動時,World腳本開始運作,初始化Entity世界。

接着World會初始化EntityManager腳本,也就ECS中的 E ,而EntityManager腳本負責所有Entity的管理,包括建立、銷毀、設定資料等。

然後 World在初始化或銷毀的時候又在 AddSystem() 和 DestroySystem() 中調用 ComponentSystemBase基類的 OnCreate() 和OnDestroy(),這裡由于咱們的System是繼承自ComponentSystem的,而ComponentSystem又是繼承于ComponentSystemBase基類的,是以咱們的System得到了初始化。

AddSystem和DestoroySystem的調用由系統自動配置設定,AddSystem在World初始化時自動調用,DestoroySystem則在World生命周期結束時自動調用,不受其他特性控制。

System腳本生命周期

Unity 革命性技術DOST入門二 ECS簡單使用介紹1.什麼是ECS?2.ECS為何快?3.ECS架構生命周期示意圖4.ECS使用介紹
方法名 觸發時機
OnCreate System被建立的時候調用
OnStartRunning 在第一次OnUpdate之前和System恢複運作的時候調用
OnUpdate System的Enabled為true時,每幀調用
OnStopRunning System的Enabled為false時,或者沒有找到相對應的Entity會調用,OnDestroy前也會調用
OnDestroy System被銷毀的時候調用

緊接着World 在初始化完成之後 又會在Update中調用InitializationSystemGroup、SimulationSystemGroup、PresentationSystemGroup這三個腳本的 Update()

public void Update()
        {
            GetExistingSystem<InitializationSystemGroup>()?.Update();
            GetExistingSystem<SimulationSystemGroup>()?.Update();
            GetExistingSystem<PresentationSystemGroup>()?.Update();
        #if ENABLE_UNITY_COLLECTIONS_CHECKS
            Assert.IsTrue(EntityManager.GetBuffer<WorldTimeQueue>(TimeSingleton).Length == 0, "PushTime without matching PopTime");
        #endif
        }
           

而上面三個系統組腳本在ECS中被分切為三個執行順序

邏輯群組 優先級
InitializationSystemGroup 1
SimulationSystemGroup 2
PresentationSystemGroup 3

各自作用如下圖

Unity 革命性技術DOST入門二 ECS簡單使用介紹1.什麼是ECS?2.ECS為何快?3.ECS架構生命周期示意圖4.ECS使用介紹

比如在遊戲中我們有三個System,一個用來生成敵人,一個用來移動,還有一個用來處理死亡。而且我們要保證必須先生成敵人,在移動,最後死亡。

是以,當我們想控制自己的System的執行順序時,可以通過以下三個特性進行控制

名稱 介紹
UpdateInGroup 指定目前System在哪個分組下運作
UpdateBefore 指定目前System在哪個System之前執行
UpdateAfter 指定目前System在哪個System之後執行

使用方式如下:

[UpdateInGroup(typeof(InitializationSystemGroup))]
public class EnemyCreateSystem : ComponentSystem{}
           
[UpdateInGroup(typeof(SimulationSystemGroup))]
public class EnemyMoveSystem : ComponentSystem{}
           
[UpdateInGroup(typeof(PresentationSystemGroup))]
public class EnemyNomalSystem : ComponentSystem{}
           

注意:該特性隻能控制我們的 OnUpdate 更新接口,OnCreate和OnDestroy以及其他的接口由ECS系統内部控制,不受特性控制!

4.ECS使用介紹

EntityManager常用方法 介紹
CreateEntity() 該方法可以為我們建立一個Entity實體,相當于New GameObjcet
CreateArchetype() 該方法可以建立我們的Entity的原型,聲名出生時附帶的資料元件
SetComponentData() 該方法則是可以設定Entity資料元件的初始值
SetSharedComponentData() 該方法用來設定共享資料,比如Mesh、Material等
DestroyEntity() 該方法用來銷毀Enitity實體

下面就以一個實際案例來介紹下ECS的基本使用

1.Components資料元件

純資料,不含有其他邏輯行為。 例如:旋轉速度,縮放大小之類的。

using Unity.Entities;

public struct MoveSpeedComponent : IComponentData
{
    public float mMoveSpeed;//移動速度
}
           

2. System

主要邏輯所在,根據元件的集合(Enitites)和純資料(Components)編寫對應的邏輯,例如旋轉,移動等一些邏輯。

這裡我們通過ForEach()方法去周遊所有Entity上的資料元件,并且修改他們的數值!

下面的移動代碼種用到了quaternion旋轉函數,它是依賴于Unity.Mathematics命名空間的,quaternion為我們提供了極多的關于處理旋轉和角度的函數,給我們帶來了很大的便利,他為我們提供的歐拉角修改的方式以及四元數修改的方式,對于用不慣四元數修改的旋轉的,這無疑是個最好的方式,具體的這裡就不一一介紹了。

using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
public class MoveSystem : ComponentSystem
{
    protected override void OnUpdate()
    {
        Entities.ForEach((ref MoveSpeedComponent moveCmpt ,ref Translation translation,ref Rotation rotation) =>
        {
            translation.Value.x += moveCmpt.mMoveSpeed * Time.DeltaTime;

            if (translation.Value.x>30)
            {
                //反轉移動速度和旋轉
                rotation.Value=quaternion.Euler(new float3(0,130,0));
                moveCmpt.mMoveSpeed = -math.abs(moveCmpt.mMoveSpeed);
            }
            else if (translation.Value.x < -30)
            {
                //反轉移動速度和旋轉
                rotation.Value = quaternion.Euler(new float3(0, -130, 0));
                moveCmpt.mMoveSpeed =math.abs(moveCmpt.mMoveSpeed);
            }
        });
    }
}
           

3.Entity

最後是Entity啟動類,他為我們初始化出了我們的Entity,以及Entity原型的建立和資料設定,這個類我把他挂載到在了相機上,友善我們進行初始化。

可以看到我們通過SetComponentData()方法初始化了我們的旋轉、移動速度、和出生位置。

using UnityEngine;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Collections;
using Unity.Transforms;
using Unity.Rendering;
public class EntityMain : MonoBehaviour
{
    EntityManager mEentityManager;
   [SerializeField]
    private Mesh[] mFishMeshArray;//模型Mesh數組
    [SerializeField]
    private Material[] mFishMaterialArray;//模型材質數組
    void Start()
    {

         mEentityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
        //建立原型
        EntityArchetype entityArchetype = mEentityManager.CreateArchetype(
            typeof(MoveSpeedComponent),
            typeof(Translation),
            //下面三個實體一定要加不然是看不到執行個體化的物體的
            typeof(RenderBounds),
            typeof(RenderMesh),
            typeof(LocalToWorld),
            //添加旋轉資料
            typeof(Rotation)
            );
        //建立實體數組 5000代表就生成5000個
        NativeArray<Entity> entityArray = new NativeArray<Entity>(5000, Allocator.Temp);
        //建立實體
        mEentityManager.CreateEntity(entityArchetype, entityArray);
        for (int i = 0; i < entityArray.Length; i++)
        {
            Entity entity = entityArray[i];
            //設定資料
            mEentityManager.SetComponentData(entity, new Rotation { Value = quaternion.Euler(0, -130, 0) });
            mEentityManager.SetComponentData(entity, new MoveSpeedComponent { mMoveSpeed = UnityEngine.Random.Range(3, 10) });
            mEentityManager.SetComponentData(entity, new Translation { Value = new float3(UnityEngine.Random.Range(-20, 20), UnityEngine.Random.Range(-15, 15), UnityEngine.Random.Range(5, 100)) });
            int index = UnityEngine.Random.Range(0, 3);
            //設定材質和渲染網格
            mEentityManager.SetSharedComponentData(entity, new RenderMesh { mesh = mFishMeshArray[index], material = mFishMaterialArray[index] });
        }
        //一定要記得釋放
        entityArray.Dispose();
    }
}
           

在Entity的Inspector面闆我們可以看到我們的資料元件:

我們添加的資料元件都在上面,并且我們修改的數值,也有所變化

Unity 革命性技術DOST入門二 ECS簡單使用介紹1.什麼是ECS?2.ECS為何快?3.ECS架構生命周期示意圖4.ECS使用介紹

一共有三種魚,每種材質都不同。

Unity 革命性技術DOST入門二 ECS簡單使用介紹1.什麼是ECS?2.ECS為何快?3.ECS架構生命周期示意圖4.ECS使用介紹

最後是我們的實際運作效果,性能并不是最優,因為我們隻使用了ECS和Burst

并且我們的主要目的是去了解如何使用ECS。

下面是同屏1000條魚的截圖

Unity 革命性技術DOST入門二 ECS簡單使用介紹1.什麼是ECS?2.ECS為何快?3.ECS架構生命周期示意圖4.ECS使用介紹

下面是 标準性能 下實際運作視屏示範

注意:視屏種的不流暢是錄屏軟體幀率不夠導緻的不流暢,并不是因為遊戲的幀率不夠。

5000隻魚:

DOTS 5000隻魚示範(注意:錄屏幀率低導緻不流暢)

一萬隻魚:

DOTS 10000隻魚示範(注意:錄屏幀率低導緻不流暢)

15000隻魚:

DOTS 15000千隻魚性能的展示

可以看到當使用三種模型材質的魚數量一共達到15000隻時,并且在位移邏輯和遊動動畫都在的情況下,我們還能穩定在30多幀,可以說很不錯了。

并且我們隻是簡單寫了點代碼 開了Burst進行的測試。

意味着我們還有很大的提升空間。

關于ECS的簡單使用就到這裡了

DOTS入門視屏教程

下一篇:Unity 革命性技術DOST 入門三JobSystem系統