文章目錄 前言 一、1. What is DOTS and why we use it? 1.DOTS包含的主要元素(三件套) 2.Why we use it? 3.Where we use it? (摘自Unity官方) (1)對于AEC(工程建設)應用 (2) 對于汽車應用 (3) 對于游戲獨立開發(fā)者和自由職業(yè)者 (4)對于游戲工作室 4.DOTS的優(yōu)劣(機遇以及風險) 二、DOTS-Man小游戲項目實戰(zhàn) 1.環(huán)境配置 2.游戲設計 3.正式開發(fā) 一些自帶腳本 Component Mono Behaviour System 最后需要進行的一些操作 太棒啦! 3. 參考文檔 
 
前言  
 DOTS是Unity在17年左右提出的一個概念,其核心是ECS。
 
 
提示:以下是本篇文章正文內(nèi)容,下面案例可供參考
 
一、1. What is DOTS and why we use it?  
全稱:(Multi-Thread)Data-Oriented-Tech-Stack
 
1.DOTS包含的主要元素(三件套)  
實體組件系統(tǒng)(ECS) - 提供使用面向數(shù)據(jù)的方法進行編碼的框架。在Unity中它通過Entities軟件包進行分發(fā),您可以通過Package Manager來添加編輯器。 C#作業(yè)系統(tǒng) (JobSystem)- 提供一種生成多線程代碼的簡單方法。它通過Jobs軟件包進行分發(fā)。 Burst編譯器 - 可生成快速、優(yōu)化的本機代碼。它通過Burst軟件包進行分發(fā),可通過Package Manager在編輯器中使用。 本機容器 - 屬于ECS數(shù)據(jù)結(jié)構(gòu),可提供對內(nèi)存的控制,值得注意的是Unity專門對內(nèi)存管理進行了一部分優(yōu)化以降低MissCache。  
2.Why we use it?  
許多并行編程范式,尤其是SIMD(單指令多數(shù)據(jù))型范式,更傾向于使用SoA(結(jié)構(gòu)體數(shù)組)。在CUDA C編程中也普遍傾向于SoA,一維數(shù)據(jù)元素是為全局內(nèi)存的有效合并訪問而預先準備好的,而相同內(nèi)存操作引用的同字段元素在存儲時時彼此相鄰的,使用SoA能夠顯著減少MissCache。 實體組件系統(tǒng)(ECS)提供了一種面向數(shù)據(jù)的編碼設計方法。利用面向數(shù)據(jù)的方法,可以對數(shù)據(jù)結(jié)構(gòu)加以組織,以免出現(xiàn)高速緩存未命中的情況,從而令隨后的數(shù)據(jù)訪問更加高效、快捷。由于面向?qū)ο蟮脑O計并不專注于數(shù)據(jù)的組織,因此高速緩存未命中的情況很常見,這樣就減慢了CPU訪問數(shù)據(jù)的速度,因為它必須頻繁地返回訪問主內(nèi)存中的數(shù)據(jù)。 C#作業(yè)系統(tǒng)可以輕松地用C#編寫快速、并行化的代碼,以充分利用當今的多核處理器。 Burst編譯器會生成高度優(yōu)化的代碼,而這些代碼可以利用您要編譯的平臺硬件。  
 Tips:
 jobsystem和ecs是兩個不同的東西,但是配合起來使用會有1+1>2的效果 burst與ecs的高度適配也使得ecs運行效率很高  
 
3.Where we use it? (摘自Unity官方)  
除非您在尋求短期或中期的性能改進,否則很難判定是否需要過渡到DOTS或何時過渡到DOTS。性能、電池使用壽命、迭代及項目可擴展性 。過渡到DOTS不會造成任何性能的下降,但評估過渡到DOTS所增加的費用卻至關重要,尤其是對于那些僅帶來較小改進的項目。
 
(1)對于AEC(工程建設)應用  
DOTS適合處理大型數(shù)據(jù)集并確保內(nèi)容的可擴展性。 DOTS非常適合進行大型交互式地圖和具有大量模型和重復內(nèi)容(例如建筑物和道路)的環(huán)境設計。 DOTS適用于復雜的工程可視化,可大規(guī)模地模擬現(xiàn)實環(huán)境。例如,DOTS非常適合進行粒度級工廠和基礎架構(gòu)設計。 (2) 對于汽車應用  
自動駕駛的仿真和可視化 DOTS非常適合進行大型交通和行人模擬,這需要成千上萬的志愿Agent以逼真的方式移動和交互。 (3) 對于游戲獨立開發(fā)者和自由職業(yè)者  
DOTS可以幫助您減輕游戲中一些高成本操作的負擔,并有助于提高性能,尤其是對于一些重復性進程。 許多輕量級游戲(例如用于移動設備的游戲)并不能最大限度地提高硬件性能。即使有些游戲能夠做到這一點,但這可能并不是它的主要關注點。不過,隨著游戲的不斷發(fā)展和硬件需求的持續(xù)增加,明智的做法是為將來使用DOTS做好準備。同樣,Project Tiny也提供了使用DOTS開發(fā)較小應用程序和游戲的解決方案。 如果您沒有使用DOTS的迫切需求,那么最好先未雨綢繆,提高自己的DOTS技能,以便在DOTS成為Unity開發(fā)的標準方法時能夠整裝待發(fā)。 (4)對于游戲工作室  
當前格式的DOTS可以幫助您逐步達到Unity或其他方式所無法達到的規(guī)模和性能。具體而言,更長的電池使用壽命、溫度控制以及DOTS所提供的代碼可重用性是其主要優(yōu)勢所在。這些方面的性能改進還使您可以開發(fā)更多的低端設備,尤其是在西方市場以外的地區(qū),這些設備會受到一定的硬件限制。 通過讓研發(fā)團隊以DOTS開展工作,可以幫助您逐步了解所能采取的最佳方法,以及哪些最新的功能和領域最具性能優(yōu)勢和發(fā)展影響力。 DOTS并非要取代引擎團隊的作用,而是讓工程師騰出更多精力在自己的專業(yè)領域(例如陰影或著色器)進行創(chuàng)新。 4.DOTS的優(yōu)劣(機遇以及風險)  
在改善Unity項目的績效方面,DOTS有著巨大的潛力。 但是,在使用DOTS時需要做出一些考量,它們會影響到項目的時間表、預算和開發(fā)團隊。以下是一些需要與項目優(yōu)先事項進行比較和對比的事項。這些事項可以歸類為風險與機遇。
 
機遇  
改進性能 。默認情況下,我們經(jīng)常使用“性能”一詞來描述DOTS。這是什么意思呢?借助面向數(shù)據(jù)的設計和多線程,DOTS可以顯著提升內(nèi)存、運行時間和電池性能。隨著游戲中顯示的項目數(shù)量不斷增加,提高性能的潛力也隨之上升。相反,對于項目較少的游戲,您會發(fā)現(xiàn)游戲性能的改善程度卻不太明顯。代碼控制 。隨著項目規(guī)模的不斷增大,DOTS可以更好地控制代碼的復雜性。為DOTS編寫的代碼通??梢愿玫胤蛛x關注點。因此,使用DOTS工作時,代碼重構(gòu)、編寫單元測試以及在開發(fā)人員之間分配工作就變得更加容易。風險  
學習成本 。如果您不熟悉DoD,那么面對DOTS時就會有一個學習曲線。盡管DoD在計算機科學領域有著良好的根基并已存在數(shù)年,而且DoD方法與OOP方法也有很大的不同,但DoD本質(zhì)上并不比OOP復雜。ECS是一種不同于當前Unity MonoBehaviour方法的代碼體系架構(gòu),因此學習需要一定的時間。目前,我們認為一名普通的Unity專業(yè)開發(fā)人員平均需要1個月才能熟練使用DOTS。這一準備時間可以被使用DOTS時的代碼質(zhì)量和性能改進所抵消。當然,具體要取決于項目。有限支持 。DOTS當前僅與Unity中一組有限的功能兼容。 最終,DOTS將與Unity的所有功能完全兼容,但我們目前尚無實現(xiàn)完全兼容的時間表。不過,DOTS允許在單個項目中同時使用游戲?qū)ο蠛虳OTS,因此您可以將DOTS用于最頻繁的處理任務,而將非DOTS Unity用于其余任務。過渡 。如果之前的項目是基于Mono開發(fā),那么跟ECS之間的轉(zhuǎn)換可能比較簡單,使用Unity自帶的一些Hybrid工具就可以較為簡單的做到,但是想要把ECS轉(zhuǎn)化為目前常用的Mono的話,我們認為可以做到,但是十分困難,而且也不建議這么做(為什么要嘗試把高效率轉(zhuǎn)為低效率呢)。目前比較推薦的是HybridECS開發(fā),ECS與Mono混合在一起,ECS再配合Jobsystem處理最需要多線程的那一部分。隨著時間的推移,晶體管電路逐漸接近性能極限,在摩爾定律逐漸失效的今天,人們面臨的數(shù)據(jù)也呈幾何倍數(shù)暴增,我們有理由去發(fā)明并且學習使用一種效率更高,更能完全發(fā)揮硬件性能的軟件編程方式,目前看來也許ECS也許能做到。
 
 
二、DOTS-Man小游戲項目實戰(zhàn)  
 想要熟悉DOTS以及ECS框架,最好還是要上手做一個小項目,使用部分基礎組件,想要熟悉以及精通還需要大量的練習以及使用,開發(fā)過程中要配合官方Entities文檔使用。
 
 
1.環(huán)境配置  
如果是Unity2020.X以下版本:  windows -> package manager advanced -> show preview package install三件套 (Entities,Jobs,Burst) install其他組件(Hybrid Renderer,Mathematics)  
 
 
2.游戲設計  
 我們準備做一個類似Pac-Man的小游戲,主要熟悉Physics包以及Entities的基本使用,所以不會開發(fā)怪物AI之類的,因為使用DOTS開發(fā)所以就叫DOTS-MAN好了。
 
 
需求分析  
主要功能有:玩家移動,鏡頭跟隨,分數(shù)顯示,因為如果用ECS來修改UGUI的TEXT可能比較麻煩,這里選擇使用HybridECS開發(fā),使用MonoBehaviours開發(fā)一些基礎功能比如鏡頭跟隨以及物體生產(chǎn)之類。
 
3.正式開發(fā)  
一些自帶腳本  
在開發(fā)過程中,因為收集物以及玩家還有地形之類的都要有碰撞,但是ECS無法使用object上面的collider之類的組件,所以就要用Entities包自帶的一些腳本。
 
 記得在掛Entities腳本之前刪掉不用的Object腳本,避免混淆以及無意義的空間占用
 
 
把Object轉(zhuǎn)化成Entity的腳本:
 
 
 添加physicsbody之后碰到List越界報錯問題解決方案:
 
 
Component  
組件只有三個,兩個存儲分別存儲移動和旋轉(zhuǎn)的速度,一個負責標記收集物(所以里面沒有數(shù)據(jù))
 
using  Unity
. Entities
; [ GenerateAuthoringComponent ] 
public  struct  MoveComponent 
:  IComponentData
{ public  float  moveSpeed
; 
} 
 
using  Unity
. Entities
; [ GenerateAuthoringComponent ] 
public  struct  RotationComponent 
:  IComponentData
{ public  float  rotateSpeed
;  
} 
 
Component配置:
 
 要注意在腳本中配置Collision Filter相關以及Collision Response相關,即某個entity屬于哪個標簽,他能與其他哪些標簽的entity發(fā)生碰撞
 
 
 
 因為mono和ECS是相互穿插的,所以如果mono中有需要的system可以直接先去看看system的代碼,配合官方文檔理解為何這么做,這樣才能把整個流程梳理清楚(至少我學習的時候是這樣的)
 
 
Mono Behaviour  
這里需要一個全局的mono behaviour來控制游戲,例如entity與object的連接,這里我們換一種方式,把之前的玩家小球弄成prefab,然后在這個全局mono控制玩家的生成,起名就叫做GameManager吧(具體說明看注釋 ):
 
GameManager:  
using  System
. Collections
; 
using  UnityEngine
; 
using  Unity
. Entities
; 
using  Unity
. Mathematics
; 
using  Unity
. Physics
; 
using  UnityEngine
. UI
; 
using  Unity
. Transforms
; public  class  GameManager  :  MonoBehaviour 
{ public  static  GameManager  instance
; public  bool  insaneMode
; public  GameObject  ballPrefab
; public  GameObject  cubePrefab
; public  Text  scoreText
; public  int  maxScore
; public  int  cubesPerFrame
; public  float  cubeSpeed 
=  3f ; private  int  curScore
; private  Entity  ballEntityPrefab
; private  Entity  cubeEntityPrefab
; private  EntityManager  entityManager
; private  BlobAssetStore  blobAssetStore
; private  void  Awake ( ) { if  ( instance 
!=  null  &&  instance 
!=  this ) { Destroy ( gameObject
) ; return ; } instance 
=  this ; entityManager 
=  World
. DefaultGameObjectInjectionWorld
. EntityManager
; blobAssetStore 
=  new  BlobAssetStore ( ) ; GameObjectConversionSettings  settings 
=  GameObjectConversionSettings
. FromWorld ( World
. DefaultGameObjectInjectionWorld
,  blobAssetStore
) ; ballEntityPrefab 
=  GameObjectConversionUtility
. ConvertGameObjectHierarchy ( ballPrefab
,  settings
) ; cubeEntityPrefab 
=  GameObjectConversionUtility
. ConvertGameObjectHierarchy ( cubePrefab
,  settings
) ; } private  void  OnDestroy ( ) { blobAssetStore
. Dispose ( ) ; } private  void  Start ( ) { curScore 
=  0 ; insaneMode 
=  false ; DisplayScore ( ) ; SpawnBall ( ) ; } private  void  Update ( ) { if  ( insaneMode
) { StartCoroutine ( SpawnLotsOfCubes ( ) ) ; } } IEnumerator  SpawnLotsOfCubes ( ) { while  ( insaneMode
) { for  ( int  i 
=  0 ;  i 
<  cubesPerFrame
;  i
++ ) { SpawnNewCube ( ) ; } yield  return  null ; } } void  SpawnNewCube ( ) { Entity  newCubeEntity 
=  entityManager
. Instantiate ( cubeEntityPrefab
) ; Vector3  direction 
=  Vector3
. up
; Vector3  speed 
=  direction 
*  cubeSpeed
; PhysicsVelocity  velocity 
=  new  PhysicsVelocity ( ) { Linear 
=  speed
, Angular 
=  float3
. zero
} ; entityManager
. AddComponentData ( newCubeEntity
,  velocity
) ; } public  void  IncreaseScore ( ) { curScore
++ ; DisplayScore ( ) ; } private  void  DisplayScore ( ) { scoreText
. text 
=  "Score: "  +  curScore
; } void  SpawnBall ( ) { Entity  newBallEntity 
=  entityManager
. Instantiate ( ballEntityPrefab
) ; Translation  ballTrans 
=  new  Translation { Value 
=  new  float3 ( 0f ,  0.5f ,  0f ) } ; entityManager
. AddComponentData ( newBallEntity
,  ballTrans
) ; CameraFollow
. instance
. ballEntity 
=  newBallEntity
; } 
} 
 
CameraFollow:  
using  Unity
. Entities
; 
using  Unity
. Transforms
; 
using  Unity
. Mathematics
; 
using  UnityEngine
; public  class  CameraFollow  :  MonoBehaviour 
{ public  static  CameraFollow  instance
; public  Entity  ballEntity
; public  float3 offset
; private  EntityManager  manager
; private  void  Awake ( ) { if  ( instance 
!=  null  &&  instance 
!=  this ) { Destroy ( gameObject
) ; return ; } instance 
=  this ; manager 
=  World
. DefaultGameObjectInjectionWorld
. EntityManager
; } private  void  LateUpdate ( ) { if  ( ballEntity 
==  null )  {  return ;  } Translation  ballPos 
=  manager
. GetComponentData < Translation > ( ballEntity
) ; transform
. position 
=  ballPos
. Value 
+  offset
; } 
} 
 
 記得把相機腳本掛到main camera上!
 
 
System  
MoveSystem:  
using  Unity
. Entities
; 
using  Unity
. Jobs
; 
using  Unity
. Mathematics
; 
using  Unity
. Physics
; 
using  UnityEngine
; public  class  MoveSystem  :  SystemBase 
{ protected  override  void  OnUpdate ( ) { float  deltaTime 
=  Time
. DeltaTime
; float2 curInput 
=  new  float2 ( Input
. GetAxis ( "Horizontal" ) ,  Input
. GetAxis ( "Vertical" ) ) ; Entities
. ForEach ( ( ref  PhysicsVelocity  vel
,  ref  MoveComponent  speedData
)  = > { float2 newVel 
=  vel
. Linear
. xz
; newVel 
+ =  curInput 
*  speedData
. moveSpeed 
*  deltaTime
; vel
. Linear
. xz 
=  newVel
; } ) . Run ( ) ; } 
} 
 
 相關要點:
 
 
RotateSystem:  
using  Unity
. Entities
; 
using  Unity
. Jobs
; 
using  Unity
. Mathematics
; 
using  Unity
. Transforms
; public  class  RotateSystem  :  SystemBase 
{ protected  override  void  OnUpdate ( ) { float  deltaTime 
=  Time
. DeltaTime
; Entities
. ForEach ( ( ref  Rotation  rotation
,  in  RotationComponent  rotationSpeed
)  = > { rotation
. Value 
=  math
. mul ( rotation
. Value
,  quaternion
. RotateX ( math
. radians ( rotationSpeed
. rotateSpeed 
*  deltaTime
) ) ) ; rotation
. Value 
=  math
. mul ( rotation
. Value
,  quaternion
. RotateY ( math
. radians ( rotationSpeed
. rotateSpeed 
*  deltaTime
) ) ) ; rotation
. Value 
=  math
. mul ( rotation
. Value
,  quaternion
. RotateZ ( math
. radians ( rotationSpeed
. rotateSpeed 
*  deltaTime
) ) ) ; } ) . Run ( ) ; } 
} 
 
 記得這時候往你的object上面掛component!如果想讓玩家移動就掛movecomponent,讓收集物旋轉(zhuǎn)就掛上rotationcomponent??梢韵胍幌?#xff0c;如果你往收集物上掛了movecomponent會發(fā)生什么?為什么會這樣?
 
 
這時候你的收集物應該是旋轉(zhuǎn)的,玩家小球可以通過wasd或者方向鍵控制移動:
 
using  Unity
. Entities
; 
using  Unity
. Collections
; 
using  Unity
. Physics
; 
using  Unity
. Physics
. Systems
; [ UpdateInGroup ( typeof ( FixedStepSimulationSystemGroup
) ) ] 
public  class  CollectSystem  :  SystemBase 
{    private  EndFixedStepSimulationEntityCommandBufferSystem  bufferSystem
; private  BuildPhysicsWorld  buildPhysicsWorld
; private  StepPhysicsWorld  stepPhysicsWorld
; protected  override  void  OnCreate ( ) { bufferSystem 
=  World
. GetOrCreateSystem < EndFixedStepSimulationEntityCommandBufferSystem > ( ) ; buildPhysicsWorld 
=  World
. GetOrCreateSystem < BuildPhysicsWorld > ( ) ; stepPhysicsWorld 
=  World
. GetOrCreateSystem < StepPhysicsWorld > ( ) ; } protected  override  void  OnUpdate ( ) { Dependency 
=  new  TriggerJob { speedEntities 
=  GetComponentDataFromEntity < MoveComponent > ( ) , entitiesToDelete 
=  GetComponentDataFromEntity < DeleteTag > ( ) , commandBuffer 
=  bufferSystem
. CreateCommandBuffer ( ) , } . Schedule ( stepPhysicsWorld
. Simulation
,  ref  buildPhysicsWorld
. PhysicsWorld
,  Dependency
) ; bufferSystem
. AddJobHandleForProducer ( Dependency
) ; } private  struct  TriggerJob 
:  ITriggerEventsJob
{    public  ComponentDataFromEntity
< MoveComponent
>  speedEntities
; [ ReadOnly ]  public  ComponentDataFromEntity
< DeleteTag
>  entitiesToDelete
; public  EntityCommandBuffer  commandBuffer
; public  void  Execute ( TriggerEvent  triggerEvent
) { TestEntityTrigger ( triggerEvent
. EntityA
,  triggerEvent
. EntityB
) ; TestEntityTrigger ( triggerEvent
. EntityB
,  triggerEvent
. EntityA
) ; } private  void  TestEntityTrigger ( Entity  entity1
,  Entity  entity2
) { if  ( speedEntities
. HasComponent ( entity1
) ) { if  ( entitiesToDelete
. HasComponent ( entity2
) )  {  return ;  } commandBuffer
. AddComponent < DeleteTag > ( entity2
) ; commandBuffer
. RemoveComponent < PhysicsCollider > ( entity2
) ; } } } 
} 
 
DeleteSystem:  
using  Unity
. Entities
; [ UpdateInGroup ( typeof ( FixedStepSimulationSystemGroup
) ) ] 
[ UpdateAfter ( typeof ( CollectSystem
) ) ] 
public  class  DeleteSystem  :  SystemBase 
{ private  EndFixedStepSimulationEntityCommandBufferSystem  _endSimulationECBSystem
; protected  override  void  OnStartRunning ( ) { _endSimulationECBSystem 
=  World
. GetOrCreateSystem < EndFixedStepSimulationEntityCommandBufferSystem > ( ) ; } protected  override  void  OnUpdate ( ) { var  ecb 
=  _endSimulationECBSystem
. CreateCommandBuffer ( ) ; Entities
. WithAll < DeleteTag > ( ) . WithoutBurst ( ) . ForEach ( ( Entity  entity
)  = > {     GameManager
. instance
. IncreaseScore ( ) ; ecb
. DestroyEntity ( entity
) ; } ) . Run ( ) ; _endSimulationECBSystem
. AddJobHandleForProducer ( Dependency
) ; } 
} 
 
 這里ForEach之前有一系列限定條件,比如.WithAll()的意思就是對帶有deletetag的entity執(zhí)行下面的操作,這樣能更加方便的進行處理,所以大部分情況下entity都會被打一個標簽來區(qū)別其他entity
 
 
最后需要進行的一些操作  
創(chuàng)建一個空物體放入GameManager,并且進行相關配置(可以在play模式下打開insaneMode看看ECS的強大性能提升): 在play模式下調(diào)整Camera中的相機跟隨參數(shù),讓鏡頭舒服: 
 
太棒啦!  
太棒啦!你成功的使用了目前領先的開發(fā)模式開發(fā)了一個小游戲,雖然這個小游戲的功能在mono中實現(xiàn)的話可以很簡單的實現(xiàn),但是隨著工程規(guī)模的擴大以及性能需求的提高,ECS只會愈發(fā)強大!因為目前DOTS相關教程不完善,所以如果在上述開發(fā)中碰到問題主要需要參考官方文檔以及一些論壇大牛的解答,想要更深入的理解還需要更多項目的磨練。
 
3. 參考文檔  
 Entities最新版本0.17的官方說明文檔:
                            總結(jié) 
                            
                                以上是生活随笔 為你收集整理的DOTS介绍+Unity DOTS-MAN小游戏项目实战 的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                            
                                如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔 推薦給好友。