最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

Unity 實(shí)體組件系統(tǒng)(ECS)——預(yù)覽與體驗(yàn)

2018-10-23 17:48 作者:皮皮關(guān)做游戲  | 我要投稿

作者:ProcessCA


Hi,大家好。吃飽喝足,該寫(xiě)點(diǎn)東西了。

這次給大家?guī)?lái)一期"新技術(shù)"的介紹。沒(méi)錯(cuò),主角就是Unity官方正在推行的ECS框架(Entity-Component-System)。

相信大家多少聽(tīng)說(shuō)過(guò)ECS(實(shí)體組件系統(tǒng)),或者在網(wǎng)絡(luò)上查找過(guò)相關(guān)資料,甚至動(dòng)手實(shí)現(xiàn)過(guò)一個(gè)自己的簡(jiǎn)易ECS框架。如果沒(méi)有聽(tīng)說(shuō)過(guò)也沒(méi)有關(guān)系,可以通過(guò)實(shí)踐可以更好地理解它。

簡(jiǎn)單介紹一下ECS的核心概念:

Entity(實(shí)體):由一個(gè)唯一ID所標(biāo)識(shí)的一系列組件的集合。

Component(組件):一系列數(shù)據(jù)的集合,本身沒(méi)有任何方法。只能用于存儲(chǔ)狀態(tài)。

System(系統(tǒng)):只有方法,沒(méi)有狀態(tài)的工具,類(lèi)似靜態(tài)類(lèi)。

這種設(shè)計(jì)看上去很新穎且奇特,具體到游戲開(kāi)發(fā)的環(huán)節(jié)中是什么樣的呢?

簡(jiǎn)單來(lái)說(shuō),Entity相當(dāng)于一個(gè)只有唯一ID的GameObject,Component就是一個(gè)只有字段的Struct,System只有方法沒(méi)有任何字段。Entity通過(guò)不同Component的組合可以被不同的System關(guān)注。

如下圖所示:

Player(Entity)擁有Position,MoveSpeedVelocity,Player這些Component。那么他就會(huì)被PlayerInputSystem,MoveSystem所關(guān)注,這些系統(tǒng)在Update時(shí)會(huì)對(duì)該實(shí)體的組件進(jìn)行讀寫(xiě)操作。

系統(tǒng)的調(diào)用順序也可以打亂。PlayerInputSystemAIInputSystem由于寫(xiě)的是不同的實(shí)體的Velocity所以可以并行,可以把他們歸到一個(gè)Group里面。MoveSystem由于需要讀取Velocity,所以得等待Group中所有寫(xiě)入操作都完成后才能Update。


至于為什么要使用ECS,相信很多熟悉OOP(面向?qū)ο缶幊蹋┑耐瑢W(xué)開(kāi)發(fā)稍微復(fù)雜點(diǎn)的游戲時(shí)都遇到過(guò):一大堆類(lèi)不知道繼承哪一個(gè),為了解耦寫(xiě)一大堆管理器等。

ECS這種反直覺(jué)的設(shè)計(jì)理念在游戲開(kāi)發(fā)中比起OOP有這些顯而易見(jiàn)的優(yōu)點(diǎn):

  1. 沒(méi)有大量的管理器或者中間件,簡(jiǎn)單地說(shuō)就是避免了OOP中常見(jiàn)的過(guò)度抽象。

  2. 比起繼承,組合的方式更容易塑造新的實(shí)體類(lèi)型。

  3. 數(shù)據(jù)驅(qū)動(dòng),因?yàn)镃omponent沒(méi)有方法且被統(tǒng)一管理,方便利用Excel配置數(shù)據(jù)。

  4. 可以利用Utils(工具類(lèi))抽出System的共有方法,加上SingletonComponent(單例組件)提供全局訪問(wèn)進(jìn)行解耦。

ECS在1998年就已經(jīng)被應(yīng)用在一款叫做:Thief : The Dark Project 的游戲中。直到2017年在 GDC 2017上的演講:Overwatch Gameplay Architecture and Netcode

http://gad.qq.com/article/detail/28682



守望先鋒團(tuán)隊(duì)向大家分享了在守望先鋒中使用的ECS以及一系列實(shí)現(xiàn)上的細(xì)節(jié)。這下才被廣大開(kāi)發(fā)者熟知。

由于ECS架構(gòu)的一些特點(diǎn),他可以很容易利用多個(gè)CPU實(shí)現(xiàn)邏輯并行緊湊且連續(xù)的內(nèi)存布局,比起OOP可以更方便地獲得更大的性能提升。


Unity2018中,伴隨Unity ECS推出的還有Burst編譯器與C# Job System。下面列出了一部分Unity ECS的愿景:

  1. 我們相信我們可以快速編寫(xiě)高性能代碼,就像MonoBehaviour.Update一樣簡(jiǎn)單。

  2. 我們相信,在基礎(chǔ)層面,這將使Unity比現(xiàn)在更加靈活。

  3. 我們會(huì)立即為您提供有關(guān)任何競(jìng)態(tài)條件的錯(cuò)誤信息。

  4. 對(duì)于小內(nèi)容,我們希望Unity在不到1秒的時(shí)間內(nèi)加載。

  5. 在大型項(xiàng)目中更改單個(gè).cs文件時(shí)。組合編譯和熱重載時(shí)間應(yīng)小于500毫秒。

Unity ECS現(xiàn)階段并不推薦直接用于生產(chǎn),但是了解他的使用方法還是很有用處的,因?yàn)镋CS不僅可以提高性能,還可以幫助你編寫(xiě)更清晰,更易于維護(hù)的代碼。

看到這里有沒(méi)有很想體驗(yàn)一下Unity的ECS?

下面我們就來(lái)寫(xiě)一些Unity ECS-Style風(fēng)格的代碼。



首先我們下載一個(gè)Unity 2018.X,新建一個(gè)工程在Window -> Package Manager?中選擇Advanced -> Show Preview Packages,然后選擇Entities并點(diǎn)擊Install。

(在2018.1中點(diǎn)擊All可以看到Entities

看到Jobs出現(xiàn)就說(shuō)明Entities已經(jīng)安裝完畢


準(zhǔn)備就緒后,我們直接創(chuàng)建一個(gè)腳本并命名Bootstrap。

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using Unity.Entities;

?

public class Bootstrap

{

??? [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]

??? public static void Awake()

??? {

?

??? }

?

??? [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]

??? public static void Start()

??? {

?

??? }

}

?


ECS不同于以往的Monobehavior,他有一套自己的生命周期。

代碼中的Awake跟Start方法都是我自己編寫(xiě)的,只要給他們打上一個(gè)特性就會(huì)被Unity在場(chǎng)景加載的前后時(shí)機(jī)進(jìn)行調(diào)用。(方法必須為靜態(tài)方法)

我們?cè)趫?chǎng)景中創(chuàng)建一個(gè)空物體并命名Player,加上Mesh Instance Renderer Component(渲染組件)。

Mesh選擇球形,新建一個(gè)材質(zhì)球Red并且放在Material中,再把Cast Shadows(投影)設(shè)置為開(kāi)啟。


打開(kāi)Bootstrap腳本,在Awake中創(chuàng)建出EntityManagerEntityArchetype

? ? private static EntityManager entityManager;???? //所有實(shí)體的管理器, 提供操作Entity的API

?

??? private static EntityArchetype playerArchetype; //Entity原型, 可以看成由組件組成的數(shù)組

?

??? [RuntimeInitializeOnLoadMethod(loadType: RuntimeInitializeLoadType.BeforeSceneLoad)]

??? public static void Awake()

??? {

??????? entityManager = World.Active.GetOrCreateManager<EntityManager>();

?

??????? //下面的的Position類(lèi)型需要引入U(xiǎn)nity.Transforms命名空間

??????? playerArchetype = entityManager.CreateArchetype(typeof(Position));

??? }

?


通過(guò)World創(chuàng)建出EntityManager,EntityManager的對(duì)象提供了創(chuàng)建實(shí)體,給實(shí)體添加組件,獲取組件,移除組件,實(shí)例化與銷(xiāo)毀實(shí)體等功能。

按照Unity的說(shuō)法,默認(rèn)情況下會(huì)在進(jìn)入播放模式時(shí)創(chuàng)建好World,因此我們直接在Awake使用World創(chuàng)建EntityManager就好了。

所以EntityManager就是一個(gè)實(shí)體的管理器。

上個(gè)版本的Unity ECS還是靜態(tài)類(lèi),現(xiàn)在已經(jīng)該為由World創(chuàng)建的實(shí)例了。

等待場(chǎng)景加載完成之后會(huì)調(diào)用Start方法:


? ? [RuntimeInitializeOnLoadMethod(loadType: RuntimeInitializeLoadType.AfterSceneLoad)]

??? public static void Start()

??? {

??????? //把GameObect.Find放在這里因?yàn)閳?chǎng)景加載完成前無(wú)法獲取游戲物體。

??????? GameObject playerGo = GameObject.Find("Player");

?

??????? //下面的類(lèi)型是一個(gè)Struct, 需要引入U(xiǎn)nity.Rendering命名空間

??????? MeshInstanceRenderer playerRenderer =

??????????? playerGo.GetComponent<MeshInstanceRendererComponent>().Value;

?

??????? //獲取到渲染數(shù)據(jù)后可以銷(xiāo)毀空物體

??????? Object.Destroy(playerGo);

?

??????? Entity player = entityManager.CreateEntity(playerArchetype);

?

??????? //修改實(shí)體的Position組件

??????? entityManager.SetComponentData(player, new Position

??????????? { Value = new Unity.Mathematics.float3(0, 2, 0) });

?

??????? // 向?qū)嶓w添加共享數(shù)據(jù)組件

??????? entityManager.AddSharedComponentData(player, playerRenderer);

??? }

float3 是Unity新推出的數(shù)學(xué)庫(kù)(Unity.Mathematics)中的類(lèi)型,用法跟Vector3基本一致。
Unity建議在ECS中使用該數(shù)學(xué)庫(kù)。

通過(guò)entityManager對(duì)象創(chuàng)建的實(shí)體都會(huì)被管理起來(lái),在創(chuàng)建Entity時(shí)我們可以在CreateEntity方法的參數(shù)中填上之前創(chuàng)建好的playerArchetype(玩家原型),即按照原型中包含的組件依次添加到實(shí)體上。

在Update方法中獲取了之前在場(chǎng)景中設(shè)置的渲染組件,并且作為AddSharedComponentData的參數(shù)。

這時(shí)我們的player實(shí)體已經(jīng)擁有了兩個(gè)組件:PositionMeshInstanceRenderer。

關(guān)于ISharedComponentData接口:他的作用是當(dāng)實(shí)體擁有屬性時(shí),比如:球形的實(shí)體共享同樣的Mesh網(wǎng)格數(shù)據(jù)時(shí)。這些數(shù)據(jù)會(huì)儲(chǔ)存在一個(gè)Chuck中并非每個(gè)實(shí)體上,因此在每個(gè)實(shí)體上可以實(shí)現(xiàn)0內(nèi)存開(kāi)銷(xiāo)。

值得注意的是:如果Entity不包含Position組件,這個(gè)實(shí)體是不會(huì)被Unity的渲染系統(tǒng)關(guān)注的。因此想在屏幕上看見(jiàn)這個(gè)實(shí)體,必須確保MeshInstanceRendererPosition都添加到了實(shí)體上。


我們運(yùn)行游戲就會(huì)看到我們創(chuàng)建的Player被顯示出來(lái)了:

細(xì)心的你也發(fā)現(xiàn)了,在Hierarchy中并沒(méi)有這個(gè)實(shí)體的信息。

原因是現(xiàn)在Unity編輯器還沒(méi)有與ECS整合,因此我們需要打開(kāi)Window -> Analysis -> Entity Debugger面板查看我們的系統(tǒng)與實(shí)體。

在EntityManager中可以看到Entity 0,那就是我們創(chuàng)建的player實(shí)體。此時(shí)他的Inspector菜單也會(huì)有數(shù)據(jù)填充:


每個(gè)Value對(duì)應(yīng)一個(gè)Component及其具體的值??梢钥吹剿淖鴺?biāo),Mesh與Material都被更改了。


現(xiàn)在我們搭建一個(gè)簡(jiǎn)易場(chǎng)景,首先創(chuàng)建一個(gè)Plane并命名為Ground。然后創(chuàng)建一個(gè)灰色的材質(zhì)球掛上去:

同時(shí)保持他的默認(rèn)組件就好了:

運(yùn)行游戲看一下效果:

我們只需要修改攝像機(jī)的Transform就能讓游戲畫(huà)面呈現(xiàn)出俯視角的效果:

視角調(diào)的還不錯(cuò):

這時(shí)如果我們想在ECS框架中控制這個(gè)小球(Player)的移動(dòng)該怎么實(shí)現(xiàn)呢?

我們之前在Bootstrap腳本中已經(jīng)實(shí)現(xiàn)了AwakeStart了,其實(shí)每一個(gè)System都會(huì)實(shí)現(xiàn)Update方法。并且會(huì)在Start調(diào)用后開(kāi)始調(diào)用。

player現(xiàn)在只包含兩個(gè)組件:PositionMeshInstanceRenderer,顯然缺乏一個(gè)標(biāo)識(shí)組件,我們創(chuàng)建一個(gè)腳本并命名為PlayerComponent

using Unity.Entities;

?

//組件必須是struct并且得繼承IComponentData接口

public struct PlayerComponent : IComponentData

{

}

?

在Bootstrap.Start方法中,在player創(chuàng)建出來(lái)后加上一句:

entityManager.AddComponentData(player, new PlayerComponent()); //添加PlayerComponent組件

?

現(xiàn)在我們創(chuàng)建一個(gè)腳本命名為MovementSystem,繼承自ComponentSystem:

類(lèi)似繼承Monobehavior,我們的系統(tǒng)只要繼承了這個(gè)基類(lèi)就會(huì)被Unity識(shí)別,并且每一幀都調(diào)用OnUpdate。

using UnityEngine;

using Unity.Entities;

using Unity.Transforms;

using Unity.Mathematics;

?

public class MovementSystem : ComponentSystem

{

??? protected override void OnUpdate()

??? {

??? }

}

?

Unity ECS幫我們簡(jiǎn)化了獲取實(shí)體再獲取組件的過(guò)程,現(xiàn)在可以直接獲取不同的組件。也就是說(shuō)我們可以獲取被EntityManager管理的實(shí)體上我們想要的組件組成的集合。

利用一個(gè)特性:[Inject]

? ? //這里聲明一個(gè)結(jié)構(gòu), 其中包含我們定義的過(guò)濾條件, 也就是必須擁有CameraComponent組件才會(huì)被注入。

??? public struct Group

??? {

??????? public readonly int Length;

?

??????? public ComponentDataArray<Position> Positions;

??? }

?

??? //然后聲明結(jié)構(gòu)類(lèi)型的字段, 并且加上[Inject]

??? [Inject] Group data;

?

??? protected override void OnUpdate()

??? {

??????? float deltaTime = Time.deltaTime;

?

??????? for (int i = 0; i < data.Length; i++)

??????? {

??????????? float3 up = new float3(0, 1, 0);

?

??????????? float3 pos = data.Positions[i].Value; //Read

?

??????????? pos += up * deltaTime;

?

??????????? data.Positions[i] = new Position { Value = pos }; //Write

??????? }

??? }

?

聲明了Length屬性后,Length會(huì)被自動(dòng)注入,它代表結(jié)構(gòu)中每個(gè)數(shù)組的總元素?cái)?shù)量,方便進(jìn)行for循環(huán)迭代。

[Inject]會(huì)從所有Entity中尋找同時(shí)擁有PlayerComponentPosition組件的實(shí)體,接著獲取他們的這些組件,注入我們聲明的不同數(shù)組中。

我們只需要在結(jié)構(gòu)中聲明好篩選的條件與我們需要的組件,ECS就會(huì)在背后幫我們處理,給我們想要的結(jié)果。

運(yùn)行后player果然升天了:

趁熱打鐵,現(xiàn)在我們想自己通過(guò)輸入控制小球在平面上移動(dòng)。

先聲明一個(gè)組件InputComponent作為一個(gè)標(biāo)識(shí):

using Unity.Entities;

?

public struct InputComponent : IComponentData

{

}

?

然后再聲明一個(gè)組件VelocityComponent保存我們的輸入向量:

using Unity.Entities;

using Unity.Mathematics;

?

public struct VelocityComponent : IComponentData

{

??? public float3 moveDir;??

}

?

我們默認(rèn)player的速度為1就不單獨(dú)聲明速度值了。

接著創(chuàng)建InputSystem來(lái)更改VelocityComponent的值,接下來(lái)的工作就是照貓畫(huà)虎了:

using UnityEngine;

using Unity.Entities;

using Unity.Mathematics;

?

public class InputSystem : ComponentSystem

{

??? public struct Group

??? {

??????? public readonly int Length;

?

??????? public ComponentDataArray<PlayerComponent> Players;

?

??????? public ComponentDataArray<InputComponent> Inputs;

?

??? ????public ComponentDataArray<VelocityComponent> Velocities;

??? }

?

??? [Inject] Group data;

?

??? protected override void OnUpdate()

??? {

??????? for (int i = 0; i < data.Length; i++)

??????? {

??????????? float x = Input.GetAxisRaw("Horizontal");

?????? ?????float z = Input.GetAxisRaw("Vertical");

?

??????????? float3 normalized = math.normalize(new float3(x, 0, z));

?

??????????? //Write

??????????? data.Velocities[i] = new VelocityComponent { moveDir = normalized };

??????? }

??? }

}

?

比較麻煩的一點(diǎn)就是,在游戲初期沒(méi)有確立基礎(chǔ)的組件與系統(tǒng)時(shí)需要頻繁修改。

移動(dòng)系統(tǒng)也需要修改:

? ? //這里聲明一個(gè)結(jié)構(gòu), 其中包含我們定義的過(guò)濾條件, 也就是必須擁有CameraComponent組件才會(huì)被注入。

??? public struct Group

??? {

??????? public readonly int Length;

?

??????? public ComponentDataArray<VelocityComponent> Velocities;

?

??????? public ComponentDataArray<Position> Positions;

??? }

?

?? ?//然后聲明結(jié)構(gòu)類(lèi)型的字段, 并且加上[Inject]

??? [Inject] Group data;

?

??? protected override void OnUpdate()

??? {

??????? float deltaTime = Time.deltaTime;

?

??????? for (int i = 0; i < data.Length; i++)

??????? {

??????????? float3 pos = data.Positions[i].Value;??????? ?????//Read

??????????? float3 vector = data.Velocities[i].moveDir;?????? //Read

?

??????????? pos += vector * deltaTime; //Move

?

??????????? data.Positions[i] = new Position { Value = pos }; //Write

??????? }

??? }

?

還要回到Bootstrap.Start中,向我們的player繼續(xù)添加這兩個(gè)組件:

? ? ? ? Entity player = entityManager.CreateEntity(playerArchetype);??? // Position

??????? //添加PlayerComponent組件

??????? entityManager.AddComponentData(player, new PlayerComponent());? // PlayerComponent

??????? entityManager.AddComponentData(player, new VelocityComponent());// VelocityComponent

??????? entityManager.AddComponentData(player, new InputComponent());?? // InputComponent

??????? // 向?qū)嶓w添加共享的數(shù)據(jù)

??????? entityManager.AddSharedComponentData(player, playerRenderer);?? // MeshInstanceRenderer

?

??????? //修改實(shí)體的Position組件

??????? entityManager.SetComponentData(player, new Position

??????????? { Value = new Unity.Mathematics.float3(0, 0.5f, 0) });

?

Duang:

Unity ECS只提供了渲染系統(tǒng)并沒(méi)有提供物理系統(tǒng),如果要跟以前的項(xiàng)目結(jié)合,我們還需要能夠訪問(wèn)場(chǎng)景中的游戲物體,比如一個(gè)經(jīng)典的Cube

設(shè)置一下參數(shù)
我們的目的是讓這個(gè)立方體升天

在Bootstrap.Start中獲取我們的Cube,并且加上GameObjectEntity組件。

GameObjectEntity 確實(shí)叫這個(gè)名, 是Unity提供的組件。

添加上這個(gè)組件后Cube就可以被entityManager關(guān)注,并且可以獲取Cube上的任意組件:

? ? ? ? //獲取Cube???????

??????? GameObjectEntity cubeEntity = GameObject.Find("Cube").AddComponent<GameObjectEntity>();

?

????? ??//添加Velocity組件

??????? entityManager.AddComponentData(cubeEntity.Entity, new VelocityComponent

??????????? { moveDir = new Unity.Mathematics.float3(0, 1, 0) });

?

我們向Cube添加了一個(gè)VelocityComponent組件,在MovementSystem加上這些代碼:


? ? public struct GameObject

??? {

??????? public readonly int Length;

?

??????? public ComponentArray<Transform> Transforms; //該數(shù)組可以獲取傳統(tǒng)的Component

?

??????? public ComponentDataArray<VelocityComponent> Velocities;//該數(shù)組獲取繼承IComponentData的

??? }

?

??? [Inject] GameObject go;

?

在OnUpdate中加上這些代碼,針對(duì)Transform進(jìn)行操作:


? ? ? ? for (int i = 0; i < go.Length; i++)

??????? {

??????????? float3 pos = go.Transforms[i].position; //Read

??????????? float3 vector = go.Velocities[i].moveDir; //Read

?

??????????? pos += vector * deltaTime; //Move

?

??????????? go.Transforms[i].position = pos; //Write

??????? }

?

運(yùn)行游戲后,我們可以看到:

Cube跟player的移動(dòng)其實(shí)是被不同的系統(tǒng)實(shí)現(xiàn)的,player是因?yàn)楸荒J(rèn)存在的渲染系統(tǒng)關(guān)注了所以實(shí)現(xiàn)了移動(dòng),而Cube是我們自己的MovementSystem實(shí)現(xiàn)的。

如果想在ECS中用到之前的物理系統(tǒng)最好是自己寫(xiě)一個(gè)單獨(dú)的系統(tǒng)并關(guān)注Rigidbody,BoxCollider這些傳統(tǒng)組件,然后在OnUpdate中使用它們。


看到這里你應(yīng)該已經(jīng)明白了ECS特點(diǎn)與Unity ECS的用法了,希望可以勾起你們對(duì)于ECS的興趣,在以后針對(duì)多核開(kāi)發(fā)的時(shí)代,相信ECS會(huì)成為高性能的代表。

介于篇幅原因,JobComponentSystem,NativeArraySystem并行,組件的先后順序讀寫(xiě)權(quán)限這些跟性能優(yōu)化相關(guān)的點(diǎn)就沒(méi)有介紹了,感興趣的話可以去Unity ECS官網(wǎng)了解。

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


附上項(xiàng)目下載地址:Mystery Code:0nl0

https://pan.baidu.com/share/init?surl=GfoxEaWWLDThXa4lkwQumA

等以后Unity ECS更完善時(shí)再出一期。這期文章就到這里了,拜拜咯。



想系統(tǒng)學(xué)習(xí)游戲開(kāi)發(fā)的童鞋,歡迎訪問(wèn)?http://levelpp.com/? ? ? ? ? ? ? ??

游戲開(kāi)發(fā)攪基QQ群:869551769? ? ? ? ? ? ? ??

微信公眾號(hào):皮皮關(guān)

Unity 實(shí)體組件系統(tǒng)(ECS)——預(yù)覽與體驗(yàn)的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
乌拉特后旗| 微博| 若尔盖县| 安塞县| 航空| 普洱| 潜山县| 兴海县| 韶关市| 宜兴市| 台山市| 色达县| 根河市| 麻江县| 泊头市| 政和县| 宝丰县| 五莲县| 时尚| 将乐县| 九台市| 鸡西市| 黎城县| 华坪县| 临澧县| 高安市| 丹东市| 进贤县| 师宗县| 定州市| 淳安县| 海口市| 吉林省| 巴塘县| 宜章县| 焦作市| 商洛市| 杭州市| 保亭| 资中县| 浦县|