目錄
一.概述
二.DOTS詳解
1.開發(fā)環(huán)境搭建
2.簡單ECS程序
?2.調(diào)試功能
3.常用API
?4.系統(tǒng)之間的互相干擾
5.實(shí)體Entity的創(chuàng)建查找刪除
6.使用批處理Gpu Instancing優(yōu)化性能
7.World,System,Group
1)World
2)System
3)Group
8.組件Component介紹
1)組件Component的添加修改刪除
2)ShareComponent
3)狀態(tài)組件
4)BufferElement
5)ChunkComponent介紹
一.概述
傳統(tǒng)方式問題
1.數(shù)據(jù)冗余:unity腳本含有大量的冗余信息,比如說我們?nèi)绻獙⒛_本掛載在物體上,腳本需要繼承自MonoBehaviour類,而MonoBehaviour類中有很多的函數(shù)調(diào)用,其中相當(dāng)一部分我們并不需要。
2.單線程處理:Unity中的腳本大多都是在主線程運(yùn)行的,無法發(fā)揮cpu多核的優(yōu)勢
3.編譯器問題:unity對于c#的代碼調(diào)用是相對低效的。
DOTS技術(shù)
為了解決上述問題,unity推出了DOTS技術(shù)(Data-Oriented Technology Stack),中文名稱:數(shù)據(jù)導(dǎo)向型技術(shù)堆棧。它主要包括一下三點(diǎn)
1.ECS(Entity Component System):數(shù)據(jù)和行為分離
2.Job System:多線程,充分發(fā)揮多核cpu的特性
3.Burst complier:編譯生成高效的代碼
二.DOTS詳解
1.開發(fā)環(huán)境搭建
PackManager中安裝Entites和Hybrid Renderer,若當(dāng)前unity版本沒有此安裝包請查看預(yù)覽包。
2.簡單ECS程序
ECS(E:實(shí)體,C:組件,S:系統(tǒng)),ECS實(shí)際上是將數(shù)據(jù)和方法實(shí)現(xiàn)分離,數(shù)據(jù)放在組件里,具體實(shí)現(xiàn)的方法放在系統(tǒng)里,組件掛載在實(shí)體上,系統(tǒng)通過實(shí)體找到組件。
下面通過代碼實(shí)現(xiàn)向Console打印的效果
//DataComponent.cs
//存儲數(shù)據(jù)組件
using Unity.Entities;
public struct DataComponent:IComponentData
{
public float printData;
}
//DataSystem.cs
//數(shù)據(jù)系統(tǒng)
using UnityEngine;
using Unity.Entities;
public class DataSystem : ComponentSystem
{
/// <summary>
/// 相當(dāng)于update
/// </summary>
protected override void OnUpdate()
{
//獲取所有DataCompoent類
Entities.ForEach((ref DataComponent datacomponent) =>
{
Debug.Log(datacomponent.printData);
});
}
}
//DataMono.cs
//掛載在物體上
using UnityEngine;
using Unity.Entities;
public class DataMono : MonoBehaviour, IConvertGameObjectToEntity
{
/// <summary>
/// 當(dāng)前物體轉(zhuǎn)化為實(shí)體時會被調(diào)用一次
/// </summary>
/// <param name="entity"></param>
/// <param name="dstManager"></param>
/// <param name="conversionSystem"></param>
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
//entity需要掛載的實(shí)體,new DataComponent():需要掛載的組件
dstManager.AddComponentData(entity, new DataComponent());
}
}
Convert To Entity組件,將物體轉(zhuǎn)換為實(shí)體。
Console輸出如下圖所示
?2.調(diào)試功能
菜單欄Window->Analysis->Entity Debugger
?左邊表示系統(tǒng)中有哪些正在運(yùn)行的系統(tǒng)。All Entityes(Default World)點(diǎn)擊后中間顯示場景中所有的實(shí)體。右邊Chunk Utiilization表示場景中所有實(shí)體的組件
3.常用API
using Unity.Transforms;
Translation
成員變量
Translation.value | 物體的位置相當(dāng)于position |
RotationEulerXYZ
成員變量
RotationEulerXYZ.value | 控制物體的旋轉(zhuǎn) |
4.系統(tǒng)之間的互相干擾
[DisaleAutoCreation]??????? 避免其他場景的系統(tǒng)的干擾
5.實(shí)體Entity的創(chuàng)建查找刪除
1.創(chuàng)建
創(chuàng)建10000個物體
//ESCPrefabCreator.cs
using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
public class ESCPrefabCreator : MonoBehaviour
{
public GameObject cube;
private void Start()
{
//獲得當(dāng)前世界的設(shè)置
GameObjectConversionSettings tempSettings = GameObjectConversionSettings.FromWorld(World.DefaultGameObjectInjectionWorld, null);
//將cube轉(zhuǎn)換為實(shí)體存儲下來
Entity tempEntityPrefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(cube, tempSettings);
//獲得實(shí)體管理器
EntityManager tempEntityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
//設(shè)置位置
Translation translation = new Translation();
//實(shí)例化10000個實(shí)體
for (int i = 0;i < 100;i++)
{
translation.Value.x = 0;
for (int j = 0;j < 100;j++)
{
Entity tempCube = tempEntityManager.Instantiate(tempEntityPrefab);
//設(shè)置組件的值
tempEntityManager.SetComponentData(tempCube, translation);
translation.Value.x += 2;
}
translation.Value.y += 2;
}
}
}
上面是通過代碼轉(zhuǎn)化實(shí)體,那么我們改如何將場景里存在的物體轉(zhuǎn)換成實(shí)體呢?我在上面說過給物體添加Convert To Entity組件即可進(jìn)行轉(zhuǎn)換,但通常場景中會包含著大量物體,給每一個物體手動添加Convert To Entity不太現(xiàn)實(shí),于是Unity設(shè)計了一個Subscene的方法批量轉(zhuǎn)換實(shí)體。
操作:將需要被轉(zhuǎn)化的物體設(shè)置一個父物體,右鍵創(chuàng)建一個Subscene即可
介紹完如何轉(zhuǎn)化實(shí)體,下面我來介紹從0開始創(chuàng)建實(shí)體
//CreateSinglon.cs
using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
public class CreateSinglon : MonoBehaviour
{
void Start()
{
//創(chuàng)建一個實(shí)體
Entity tempEntity = World.DefaultGameObjectInjectionWorld.EntityManager.CreateEntity(typeof(DataComponent), typeof(RotationEulerXYZ));
//通過一個實(shí)體再創(chuàng)建一個實(shí)體
World.DefaultGameObjectInjectionWorld.EntityManager.Instantiate(tempEntity);
}
}
?我們通過這串代碼創(chuàng)建了兩個實(shí)體,如果我們想大批量創(chuàng)建實(shí)體怎么辦?
接下來我們引入一個概念Chunk
chunk相當(dāng)于一個塊,ECS會將相同組件的實(shí)體添加到同一個chunk中,例如Entity1,Entity2都有A,B組件,Entity3,Entity4都有C組件,那么Entity1,2放置在Chunk1中,Entity3,4放置在Chunk2中。chunk具有一定的容量,當(dāng)chunk放滿時ECS會再開一個chunk放置實(shí)體,新開的塊和原來的塊在類型上是相同的,我們將同種類型的Chunk稱之為ArchType(原型),我們可通過ArchType訪問相同類型的塊。ECS這樣做的目的是為了提高創(chuàng)建和訪問實(shí)體的性能。
//CreateEntites.cs
using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
using Unity.Collections;
public class CreateEntites : MonoBehaviour
{
void Start()
{
//創(chuàng)建一個實(shí)體
Entity tempEntity = World.DefaultGameObjectInjectionWorld.EntityManager.
CreateEntity(typeof(DataComponent), typeof(RotationEulerXYZ));
//通過一個實(shí)體再創(chuàng)建一個實(shí)體
World.DefaultGameObjectInjectionWorld.EntityManager.Instantiate(tempEntity);
//創(chuàng)建一個原型,包括DataCompont和RotationXYZ組件
EntityArchetype tempEntityArchetype = World.DefaultGameObjectInjectionWorld.EntityManager.
CreateArchetype(typeof(DataComponent), typeof(RotationEulerXYZ));
//創(chuàng)建一個NativeArray,將實(shí)體添加到數(shù)組中,Allocator決定實(shí)體存儲的模式
//NativeArray和數(shù)組差不多,用來存儲物體,而JobSystem多線程無法使用普通數(shù)組,于是用NativeArray代替
NativeArray<Entity> tempNativeArray = new NativeArray<Entity>(5, Allocator.Temp);
//通過原型創(chuàng)建5個實(shí)體
World.DefaultGameObjectInjectionWorld.EntityManager.CreateEntity(tempEntityArchetype, tempNativeArray);
//通過實(shí)體創(chuàng)建5個實(shí)體
World.DefaultGameObjectInjectionWorld.EntityManager.Instantiate(tempEntity, tempNativeArray);
}
}
?2.查找
//FindEntities.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
using Unity.Collections;
public class FindEntities : MonoBehaviour
{
void Start()
{
#region 創(chuàng)建
/*和CreateEntites創(chuàng)建代碼相同*/
#endregion
#region 查找
//方法1
//獲得世界全部的實(shí)體
NativeArray<Entity> tempEntits = World.DefaultGameObjectInjectionWorld.EntityManager.GetAllEntities();
foreach(var item in tempEntits)
{
//打印實(shí)體的序號和名字
Debug.Log(item.Index + World.DefaultGameObjectInjectionWorld.EntityManager.GetName(item));
}
//方法2
//根據(jù)該組建創(chuàng)建一個實(shí)體的查詢
EntityQuery tempEntityQuery = World.DefaultGameObjectInjectionWorld.EntityManager.
CreateEntityQuery(typeof(DataComponent), typeof(RotationEulerXYZ));
//創(chuàng)建一個NativeArray存儲所有的查詢結(jié)果
NativeArray<Entity> tempEntits2 = tempEntityQuery.ToEntityArray(Allocator.TempJob);
foreach(var item in tempEntits2)
{
Debug.Log(item.Index + World.DefaultGameObjectInjectionWorld.EntityManager.GetName(item));
}
//釋放NativeArray
tempEntits2.Dispose();
#endregion
}
}
3.刪除
//刪除
//方法1:刪除單個物體
World.DefaultGameObjectInjectionWorld.EntityManager.DestroyEntity(tempEntity);
//方法2:通過NativeArray刪除
World.DefaultGameObjectInjectionWorld.EntityManager.DestroyEntity(tempEntityNativeArray);
//方法3:通EntityQuery刪除
World.DefaultGameObjectInjectionWorld.EntityManager.DestroyEntity(tempEntityQuery);
6.使用批處理Gpu Instancing優(yōu)化性能
?勾選材質(zhì)里的Enable GPU Instancing
7.World,System,Group
World:相當(dāng)于對ECS進(jìn)行一個全局管理,場景里可以擁有多個world但是world之間無法進(jìn)行通信。
System:只能存在與一個世界,同一世界不能有重復(fù)的系統(tǒng)
Group:一個特殊的系統(tǒng),用來給系統(tǒng)做分類或者改變系統(tǒng)的執(zhí)行順序
1)World
構(gòu)造函數(shù)
World(string) | 創(chuàng)建一個名字為string的新世界 |
靜態(tài)變量
World.DefaultGameObjectInjectionWorld | 返回默認(rèn)世界 |
World.All | 返回所有世界的集合 |
成員變量
World.Name | 獲得該世界的名字 |
World.Systems | 返回當(dāng)前世界的所有系統(tǒng) |
成員函數(shù)
World.Dispose() | 銷毀世界 |
World.GetOrCreateSystem<T>() | 尋找世界里有沒有該系統(tǒng),若沒有重新創(chuàng)建一個 |
World.AddSystem<T>(T:ComponentSystemBase) | 添加系統(tǒng) |
World.DestroySystem(ComponentSystemBase) | 刪除系統(tǒng) |
World.GetExistingSystem<T>() | 獲得該系統(tǒng) |
2)System
系統(tǒng)包括:SystemBase,ComponentSystem,JobComponentSystem,其中ComponentSystem只能在主線程上運(yùn)行,JobComponentSytem可以提供多線程,但是需要手動管理JOB依賴,容易出錯,SystemBase可以兼容以上兩種,unity推薦使用SystemBase來進(jìn)行管理
(1)三種系統(tǒng)聲明
ComponentSystem
//DataSystem.cs
//數(shù)據(jù)系統(tǒng)
using UnityEngine;
using Unity.Entities;
public class DataSystem : ComponentSystem
{
/// <summary>
/// 相當(dāng)于update
/// </summary>
protected override void OnUpdate()
{
//獲取所有DataCompoent類
Entities.ForEach((ref DataComponent datacomponent) =>
{
Debug.Log(datacomponent.printData);
});
}
}
JobSytem和Burst編譯器優(yōu)化性能
//JobSystem.cs
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using Unity.Entities;
public class JobSystem : JobComponentSystem
{
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
//使用JobSystem的方法修改物體的角度,WithBurst()用上Burst編譯器,Schedule用上JobSystem
JobHandle tempJobHandle = Entities.ForEach((ref RotationEulerXYZ rotation) =>
{
rotation.Value = new float3(0, 45, 0);
}).WithBurst().Schedule(inputDeps);
return tempJobHandle;
}
}
?SystemBase
//SystemBaseTest.cs
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
public class SystemBaseTest : SystemBase
{
protected override void OnUpdate()
{
Entities.ForEach((ref Translation trans) =>
{
trans.Value = new float3(1, 1, 1);
}).Schedule();
}
}
?(2)系統(tǒng)的生命周期
OnCreate->OnStartRunning->OnUpdate->OnStopRunning->OnDestroy
//SystemBaseTest.cs
using Unity.Entities;
public class SystemBaseTest : SystemBase
{
//創(chuàng)建時調(diào)用一次
protected override void OnCreate()
{
}
//開始運(yùn)行時調(diào)用一次
protected override void OnStartRunning()
{
}
//持續(xù)調(diào)用
protected override void OnUpdate()
{
}
//停止運(yùn)行時創(chuàng)建一次
protected override void OnStopRunning()
{
}
//摧毀時調(diào)用一次
protected override void OnDestroy()
{
}
}
(3)系統(tǒng)對組件,實(shí)體的操控
1.World.EntityManager進(jìn)行操控
2.Sytem自帶方法
3.System成員變量Entites來訪問
(4)SystemBase對實(shí)體進(jìn)行修改等操作簡述
EntityQuery entityQuery;
//持續(xù)調(diào)用
protected override void OnUpdate()
{
//ref :讀寫
//in : 只讀
Entities.ForEach((ref Translation trans, in DataComponent data) =>
{
Debug.Log(trans.Value + " " + data.printData);//在ECS里面少用unity里面的操作
}).WithAll<DataComponent, Rotation>()//對含有DataCompoent,Rotation的實(shí)體進(jìn)行操作,可以對篩選的組件做限制,<>可以繼續(xù)添加
.WithAll<DataComponent, Rotation>()//篩選只要包含這兩個組件當(dāng)中任何一個的實(shí)體,可以在<>繼續(xù)擴(kuò)展
.WithNone<DataComponent>()//篩選不包含這個組件的實(shí)體,同樣可以擴(kuò)展
.WithChangeFilter<DataComponent>() //對參數(shù)值發(fā)生變化的實(shí)體做篩選,可以擴(kuò)展
//使用這個篩選里面的組件必須在ForEach()里進(jìn)行聲明,比如說in DataComponent data
.WithSharedComponentFilter(new ShareComponent() { data = 3 }) //選出具有特定共享組件的實(shí)體
.WithStoreEntityQueryInField(ref entityQuery) //將查詢結(jié)果存儲在一個變量上
//.Run(); //在主線程上運(yùn)行
//.Schedule(); //多開一個線程運(yùn)行
.WithBurst() //支持Burst編譯器
.ScheduleParallel(); //多線程運(yùn)行,在Burst編譯器下運(yùn)行效果好
}
?多線程訪問共享數(shù)據(jù)介紹
當(dāng)ECS開啟多線程時會涉及到對數(shù)據(jù)的讀寫沖突,假如說1號線程在讀,這時2號線程修改了數(shù)據(jù),這樣就會導(dǎo)致1號數(shù)據(jù)讀取異常。在傳統(tǒng)方法中Unity采用了鎖的方式來解決這個問題,但是在ECS中并不是這樣處理的。ECS提供了一個Native Container容器的方法,其中包含了四種容器,NativeArray,NativeHashMap,NativeMultiHashMap,NativeQueue這四個容器類似與c#的傳統(tǒng)容器,但是不同的是在創(chuàng)建這四種容器時會創(chuàng)建分配器(Temp,JobTemp,Persistent),這三個分配器生命周期Temp<JobTemp <Persistent,性能Temp > JobTemp > Persistent,Temp使用與1幀內(nèi)進(jìn)行銷毀,JobTemp適用于四幀內(nèi)進(jìn)行銷毀,Persistent適合長久存在
多線程訪問共享數(shù)據(jù)實(shí)操
NativeArray
方法1:通過WithDeallovateOnJobCompletion進(jìn)行釋放
protected override void OnUpdate()
{
NativeArray<int> tempInt = new NativeArray<int>(5, Allocator.TempJob);
Entities.ForEach((ref Translation trans, in DataComponent data) =>
{
tempInt[0] = 5;
})
.WithDeallocateOnJobCompletion(tempInt) //線程運(yùn)行完自動釋放
.WithBurst() //支持Burst編譯器
.ScheduleParallel(); //多線程運(yùn)行,在Burst編譯器下運(yùn)行效果好
}
?方法二:通過CompleteDependency將線程阻塞到CompleteDependency之前,只有線程完成后才可以通過
protected override void OnUpdate()
{
NativeArray<int> tempInt = new NativeArray<int>(5, Allocator.TempJob);
//ref :讀寫
//in : 只讀
Entities.ForEach((ref Translation trans, in DataComponent data) =>
{
tempInt[0] = 5;
})
.WithBurst() //支持Burst編譯器
.ScheduleParallel(); //多線程運(yùn)行,在Burst編譯器下運(yùn)行效果好
CompleteDependency();
tempInt.Dispose();//釋放
}
Entites中對實(shí)體進(jìn)行操作
方法1:直接修改
protected override void OnUpdate()
{
Entities.ForEach((Entity entity, in DataComponent data) =>
{
EntityManager.AddComponentData(entity, new DataComponent() { printData = 3 });
})
.WithStructuralChanges() //修改實(shí)體時需要加上這個
.WithoutBurst()
.Run();
}
?方法2:通過EntityCommandBuffer進(jìn)行修改,性能更高,推薦使用
protected override void OnUpdate()
{
EntityCommandBuffer entityCommandBuffer = new EntityCommandBuffer(Allocator.TempJob);
Entities.ForEach((Entity entity, in DataComponent data) =>
{
entityCommandBuffer.AddComponent(entity, new DataComponent() { printData = 3 });
})
.WithoutBurst()
.Run();
entityCommandBuffer.Playback(EntityManager); //在Foreach里設(shè)置命令,用這句代碼進(jìn)行執(zhí)行
entityCommandBuffer.Dispose();
}
?JobWithCode的使用
用法和Entities類似,很多函數(shù)功能名字相同
protected override void OnUpdate()
{
Job.WithCode(() =>
{
})
.Schedule();
}
?(5)ComponentSystem和JobCompentSytem調(diào)用實(shí)體簡述
ComponentSystem中沒有JobWithCode只有Entities,且沒有限制修飾等其他操作
protected override void OnUpdate()
{
Entities.ForEach((Entity entity) =>
{
});
}
JobSystem中OnUpdate存在參數(shù)和返回值
protected override JobHandle OnUpdate(JobHandle inputDeps)
{
JobHandle value = Entities.ForEach((Entity entity) =>
{
}).Schedule(inputDeps);
return value;
}
(6)Job接口使用
Job相當(dāng)與一個線程
//JobTest.cs
using UnityEngine;
using Unity.Jobs;
using Unity.Collections;
using Unity.Burst;
public class JobTest : MonoBehaviour
{
/// <summary>
/// 一個Job代表一個線程
/// </summary>
[BurstCompile] //使用Busrt編譯器
public struct Job1 : IJob
{
public int i, j;
public int result;
public void Execute()
{
result = i * j;
}
}
void Update()
{
NativeList<JobHandle> jobHandlesList = new NativeList<JobHandle>(Allocator.Temp);
for (int i = 0;i <10;i++)
{
Job1 job1 = new Job1 { i = 6, j = 7 };
JobHandle jobHandle = job1.Schedule();
jobHandlesList.Add(jobHandle);
}
//阻塞線程,等待所有線程完成
JobHandle.CompleteAll(jobHandlesList);
jobHandlesList.Dispose();
}
}
?運(yùn)行結(jié)果
?上述代碼只創(chuàng)建了一個線程,那么我們?nèi)绾蝿?chuàng)建多個線程呢?我們可以通過一個容器將所有線程存儲起來進(jìn)行遍歷或者釋放
//JobTest.cs
using UnityEngine;
using Unity.Jobs;
using Unity.Collections;
public class JobTest : MonoBehaviour
{
/// <summary>
/// 一個Job代表一個線程
/// </summary>
public struct Job1 : IJob
{
public int i, j;
public int result;
public void Execute()
{
result = i * j;
}
}
void Update()
{
NativeList<JobHandle> jobHandlesList = new NativeList<JobHandle>(Allocator.Temp);
for (int i = 0;i <10;i++)
{
Job1 job1 = new Job1 { i = 6, j = 7 };
JobHandle jobHandle = job1.Schedule();
jobHandlesList.Add(jobHandle);
}
//阻塞線程,等待所有線程完成
JobHandle.CompleteAll(jobHandlesList);
jobHandlesList.Dispose();
}
}
?(7)ParallelJbob
如果場景里有很多物體,我們要修改物體每次都需要遍歷,而遍歷是unity中非常常用的操作,于是Unity設(shè)計了一個IJobParallelFor接口來解決這個問題
經(jīng)過測試,在場景中創(chuàng)建1000個物體并控制旋轉(zhuǎn),傳統(tǒng)方法幀率穩(wěn)定在50-60,使用上ParallelJob和Burst編譯器后幀率穩(wěn)定在230以上,性能提升非常明顯
//JobTest.cs
using System.Collections.Generic;
using UnityEngine;
using Unity.Jobs;
using Unity.Collections;
using Unity.Burst;
using Unity.Mathematics;
public class JobTest : MonoBehaviour
{
public float interval;
public GameObject cubePrefab;
private List<GameObject> goList = new List<GameObject>();
private void Start()
{
//在start函數(shù)中創(chuàng)建1000個物體
Vector3 tempVector3 = new Vector3(-interval, 0, 0);
for (int i = 0;i <100;i++)
{
for (int j = 0;j <10;j++)
{
GameObject tempCube = GameObject.Instantiate(cubePrefab);
goList.Add(tempCube);
tempVector3.x += interval;
tempCube.transform.position = tempVector3;
}
tempVector3.x = -interval;
tempVector3.y += interval;
}
}
void Update()
{
ParallelJob parallelJob = new ParallelJob();
NativeArray<float3> eulerAngles = new NativeArray<float3>(goList.Count,Allocator.TempJob);
//初始化
for (int i = 0;i < goList.Count;i++)
{
eulerAngles[i] = goList[i].transform.eulerAngles;
}
parallelJob.eulerAngles = eulerAngles;
parallelJob.daltaTime = Time.deltaTime;
//第二個參數(shù)是批處理數(shù)量,批處理計數(shù)控制你將獲得多少個Job
//以及線程之間的工作量新分配的細(xì)化程度如何.
//擁有較低的批處理數(shù)量,會使你在線程之間進(jìn)行更均勻的工作分配.
//但是它會帶來一些開銷,因此在某先情況下,稍微增加批次數(shù)量會更好
JobHandle jobHandle = parallelJob.Schedule(goList.Count, 10);
jobHandle.Complete();
//將更改的角度賦值給物體,控制物體旋轉(zhuǎn)
for (int i = 0; i < goList.Count; i++)
{
goList[i].transform.eulerAngles = eulerAngles[i];
}
eulerAngles.Dispose();
}
[BurstCompile]
public struct ParallelJob : IJobParallelFor
{
public NativeArray<float3> eulerAngles;
public float daltaTime;
/// <summary>
/// 將創(chuàng)建好ParalelJob后,就會并行處理數(shù)組或者一些操作
/// </summary>
/// <param name="index"></param>
public void Execute(int index)
{
//更改數(shù)組的角度
eulerAngles[index] += new float3(0, 30 * daltaTime, 0);
}
}
}
3)Group
[UpdateInGroup(typeof(系統(tǒng)))] | 用來給系統(tǒng)分類,默認(rèn)SimulationSystemGroup |
注意:關(guān)于刪除系統(tǒng)的一個坑,刪除系統(tǒng)前需要將系統(tǒng)移除Group
//TestWorld.cs
using UnityEngine;
using Unity.Entities;
public class TestWorld : MonoBehaviour
{
void Start()
{
World tempWorld = new World("new");
//獲取默認(rèn)世界的一個系統(tǒng)
DataSystem dataSystem = World.DefaultGameObjectInjectionWorld.GetExistingSystem<DataSystem>();
//添加該系統(tǒng)到新世界
tempWorld.AddSystem(dataSystem);
//找到組
InitializationSystemGroup group = World.DefaultGameObjectInjectionWorld.GetExistingSystem<InitializationSystemGroup>();
//從組中移除系統(tǒng)
group.RemoveSystemFromUpdateList(dataSystem);
//銷毀系統(tǒng)
tempWorld.DestroySystem(dataSystem);
}
}
8.組件Component介紹
1)組件Component的添加修改刪除
添加
//為單個實(shí)體添加組件
//方法1
World.DefaultGameObjectInjectionWorld.EntityManager.AddComponent(tempEntity, typeof(DataComponent));
//方法2
World.DefaultGameObjectInjectionWorld.EntityManager.AddComponent<DataComponent>(tempEntity);
//批量添加組件
//方法1:通過NativeArray
World.DefaultGameObjectInjectionWorld.EntityManager.AddComponent<DataComponent>(tempNativeArray);
//方法2:通過EntityQuery
World.DefaultGameObjectInjectionWorld.EntityManager.AddComponent<DataComponent>(tempEntityQuery);
//為一個實(shí)體添加多個組件
World.DefaultGameObjectInjectionWorld.EntityManager.AddComponents(tempEntity,
new ComponentTypes(typeof(DataComponent), typeof(RotationEulerXYZ)));
數(shù)據(jù)初始化
數(shù)據(jù)初始化有兩個方法,一是通過AddComponentData通過代碼在添加組件的同時初始化,二是為組件類添加[GenerateAuthoringComponent]特性,這樣該組件就可以被掛載在物體上通過Inspector面板賦值
World.DefaultGameObjectInjectionWorld.EntityManager.AddComponentData(entity, new DataComponent()
{
printData = 5;
});
組件的獲取和修改
//組件獲取
RotationEulerXYZ rotationEulerXYZ = World.DefaultGameObjectInjectionWorld.EntityManager.GetComponentData<RotationEulerXYZ>(tempEntity);
//設(shè)置組件的值
World.DefaultGameObjectInjectionWorld.EntityManager.SetComponentData(tempEntity,
new RotationEulerXYZ()
{
Value = new float3(1, 1, 1)
});
組件的刪除
World.DefaultGameObjectInjectionWorld.EntityManager.RemoveComponent<RotationEulerXYZ>(tempEntity);
World.DefaultGameObjectInjectionWorld.EntityManager.RemoveComponent<RotationEulerXYZ>(tempEntityNativeArray);
World.DefaultGameObjectInjectionWorld.EntityManager.RemoveComponent<RotationEulerXYZ>(tempEntityQuery);
World.DefaultGameObjectInjectionWorld.EntityManager.RemoveComponent(tempEntityQuery,
new ComponentTypes(typeof(RotationEulerXYZ)));
2)ShareComponent
在創(chuàng)建實(shí)體的時候我們可能創(chuàng)建的實(shí)體擁有的組件相同,而組件時繼承ICompoentData接口,相當(dāng)于聲明了兩個一模一樣的實(shí)體,為了避免這種情況提高性能,unity設(shè)計了一個ShareComponent組件。
ShareCompoent的簡單使用
聲明一個ShareComponent組件
//ShareComponent.cs
using Unity.Entities;
using System;
//添加ISharedComponentData接口,必須實(shí)現(xiàn)IEquatable:判斷相等接口
public struct ShareComponent : ISharedComponentData,IEquatable<ShareComponent>
{
public int data;
public bool Equals(ShareComponent other)
{
return data == other.data;
}
//Hash算法
public override int GetHashCode()
{
int tempHash = 0;
tempHash ^= data.GetHashCode();
return tempHash;
}
}
?聲明一個代理掛載在含有Convert To Entity組件的物體上
//ShareMono.cs
using UnityEngine;
using Unity.Entities;
public class ShareMono : MonoBehaviour, IConvertGameObjectToEntity
{
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
dstManager.AddSharedComponentData(entity, new ShareComponent() { data = 5 });
}
}
基本操作
//添加組件
World.DefaultGameObjectInjectionWorld.EntityManager.AddSharedComponentData(tempEntities);
//設(shè)置組件
World.DefaultGameObjectInjectionWorld.EntityManager.SetSharedComponentData(tempEntities,
new ShareComponent() { data = 10 });
//刪除組件
World.DefaultGameObjectInjectionWorld.EntityManager.RemoveComponent<ShareComponent>(tempEntities);
//查找組件
ShareComponent shareComponent = World.DefaultGameObjectInjectionWorld.EntityManager.GetSharedComponentData<ShareComponent>(tempEntities);
注意1:使用共享組件時我們要盡量選擇哪些不會經(jīng)常變動的組件作為共享組件,因?yàn)樾薷膶?shí)體共享組件的值,會導(dǎo)致這個實(shí)體被移動到新的塊中,這種移動塊的操作是比較消耗時間的(修改普通組件的是是不會改變塊的實(shí)體)。共享實(shí)體可以節(jié)省內(nèi)存,特別是要創(chuàng)建大量相同物體時,也不要濫用,否則有可能降低效率
注意2:一旦修改了實(shí)體的共享組件的值,則該實(shí)體會被存放到一個新的塊中,因?yàn)樗墓蚕斫M件發(fā)生了變化,相當(dāng)于使用了新的共享組件
3)狀態(tài)組件
ECS系統(tǒng)中沒有回調(diào),這就意味著Entity銷毀時不會發(fā)出任何通知,我們?nèi)绻獙?shí)現(xiàn)一些銷毀后的操作就比較麻煩,所以unity設(shè)計了一個狀態(tài)組件ISystemStateComponentData,當(dāng)實(shí)體被銷毀時狀態(tài)組件不會被銷毀,所以我們就可以在Entity銷毀時在ISystemStateComponentData中留下一些實(shí)體銷毀的信息。ISystemStateComponentData需要手動進(jìn)行銷毀
//Statement.cs
using Unity.Entities;
public struct StateComponent : ISystemStateComponentData
{
public int data;
}
//StateMono
using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
public class StateMono : MonoBehaviour
{
void Start()
{
//創(chuàng)建一個實(shí)體,添加三個組件
Entity tempEntity = World.DefaultGameObjectInjectionWorld.EntityManager.
CreateEntity(typeof(StateComponent),typeof(RotationEulerXYZ),typeof(DataComponent));
//刪除實(shí)體
World.DefaultGameObjectInjectionWorld.EntityManager.DestroyEntity(tempEntity);
//為StateCompent修改值
World.DefaultGameObjectInjectionWorld.EntityManager.SetComponentData(tempEntity,
new StateComponent() { data = 10 });
//刪除StateCompoent后實(shí)體就被完全刪除了
World.DefaultGameObjectInjectionWorld.EntityManager.RemoveComponent<StateComponent>(tempEntity);
}
}
當(dāng)我們刪除實(shí)體操作沒有進(jìn)行的時候
?可以從分析面板看出該實(shí)體掛在了三個組件
當(dāng)我們執(zhí)行刪除實(shí)體后
?可以看出實(shí)體并沒有完全被刪除,上面添加了CleanupEntity組件代表未完全刪除實(shí)體,還保留了StateComponent組件用來保留一些數(shù)據(jù),我們可以通過更改StateComponent里面的數(shù)據(jù)來判斷該實(shí)體是否被銷毀。
如果要完全刪除實(shí)體,在刪除實(shí)體后再刪除StateCompoent,就能完全刪除.
4)BufferElement
介紹
上面我們說過一個Entity不能創(chuàng)建相同的組件,那如果我們想創(chuàng)建多個相同的組件怎么辦?unity設(shè)計了一個接口IBuffElementData(動態(tài)緩沖區(qū))來存儲很多組件,其中可以包含相同的,這個緩沖區(qū)可以類比與List集合
//BufferComponent.cs
using Unity.Entities;
[GenerateAuthoringComponent]
public struct BufferComponent : IBufferElementData
{
public int data;
}
?Values里Element的值對應(yīng)的就是BufferComponent里的data,總共創(chuàng)建了3個data
可以看出運(yùn)行時該實(shí)體被添加了一個BufferComponent組件
通過[GenerateAuthoringComponent]特性的方法掛載組件,組件里面只能寫一個變量,如果我們想寫多個變量怎么辦,那我們只能自己寫代理了
//BufferMono.cs
using UnityEngine;
using Unity.Entities;
public class BufferMono : MonoBehaviour, IConvertGameObjectToEntity
{
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
DynamicBuffer<BufferComponent> tempBuffer = dstManager.AddBuffer<BufferComponent>(entity);
tempBuffer.Add(new BufferComponent() { data = 1,data2 = 3 });
tempBuffer.Add(new BufferComponent() { data = 2,data2 = 5});
tempBuffer.Add(new BufferComponent() { data = 3 });
}
}
上述代碼往實(shí)體里面添加了三個相同的BuffeCompoent組件并且進(jìn)行了賦值
BufferElement常用操作
//BufferTest
using UnityEngine;
using Unity.Entities;
public class BufferTest : MonoBehaviour
{
private void Start()
{
//創(chuàng)建一個實(shí)體
Entity tempEntity = World.DefaultGameObjectInjectionWorld.EntityManager.CreateEntity();
//添加三個buffercomponent組件
DynamicBuffer<BufferComponent> tempBuffer = World.DefaultGameObjectInjectionWorld.EntityManager.AddBuffer<BufferComponent>(tempEntity);
tempBuffer.Add(new BufferComponent() { data = 1, data2 = 3 });
tempBuffer.Add(new BufferComponent() { data = 2, data2 = 5 });
tempBuffer.Add(new BufferComponent() { data = 3 });
//獲得該實(shí)體的所有BufferComponent
DynamicBuffer<BufferComponent> bufferComponents = World.DefaultGameObjectInjectionWorld.EntityManager.
GetBuffer<BufferComponent>(tempEntity);
//遍歷集合內(nèi)容
foreach(var item in bufferComponents)
{
Debug.Log(item.data + ":" + item.data2);
}
//刪除第0個組件
tempBuffer.RemoveAt(0);
//插入新的組件
tempBuffer.Insert(0, new BufferComponent() { data = 11, data2 = 12 });
}
}
5)ChunkComponent介紹
作用與共享組件類似,區(qū)別是對塊組件修改時并不會像共享組件一樣創(chuàng)建一個新的組件
//ChunkCompoent.cs
using Unity.Entities;
using UnityEngine;
public class ChunkComponent : IComponentData
{
public int data;
}
//ChunkTest.cs
using UnityEngine;
using Unity.Entities;
public class ChunkTest : MonoBehaviour
{
void Start()
{
//創(chuàng)建原型
EntityArchetype entityArchetype = World.DefaultGameObjectInjectionWorld.EntityManager.
CreateArchetype(ComponentType.ChunkComponent(typeof(ChunkComponent)));
//用原型創(chuàng)建實(shí)體
Entity entity = World.DefaultGameObjectInjectionWorld.EntityManager.CreateEntity(entityArchetype);
//創(chuàng)建第二個實(shí)體
World.DefaultGameObjectInjectionWorld.EntityManager.CreateEntity(entityArchetype);
//獲得ArchtypeChunk并且修改值
ArchetypeChunk tempChunk = World.DefaultGameObjectInjectionWorld.EntityManager.GetChunk(entity);
World.DefaultGameObjectInjectionWorld.EntityManager.SetChunkComponentData(tempChunk,
new ChunkComponent() { data = 10 });
//移除組件
World.DefaultGameObjectInjectionWorld.EntityManager.RemoveChunkComponent<ChunkComponent>(entity);
//獲得ChunkComponent
World.DefaultGameObjectInjectionWorld.EntityManager.GetChunkComponentData<ChunkComponent>(tempChunk);
//給實(shí)體添加ChunkComponent
World.DefaultGameObjectInjectionWorld.EntityManager.AddChunkComponentData<ChunkComponent>(entity);
//實(shí)體存不存在ChunkComponent
World.DefaultGameObjectInjectionWorld.EntityManager.HasChunkComponent<ChunkComponent>(entity);
}
}
9.總結(jié)
到這里DOTS的教學(xué)已經(jīng)完結(jié)了,下面附上一個我用于測試的實(shí)例
三.DOTS測試案例
案例1:1萬個小球下落
準(zhǔn)備工作
在Package Manager中安裝Unity Physics包,安裝好后重啟unity
制作小球預(yù)制體,掛載上Physics Shape(ECS中的碰撞器),Physics Body(ECS中的物理碰撞系統(tǒng)),Convert To Entity
?設(shè)置Physics Shape的彈力值,Resitiution值越大彈力越大
在場景中掛載代碼,并在Inspector面板賦值
//SphereManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
public class SphereManager : MonoBehaviour
{
public GameObject spb; //球體預(yù)制體
public int nums; //生成的球體數(shù)量
public float interval; //球體間隔比例
private BlobAssetStore blobAssetStore;
private void Start()
{
//將小球轉(zhuǎn)化為實(shí)體
blobAssetStore = new BlobAssetStore();
GameObjectConversionSettings setting = GameObjectConversionSettings.FromWorld(World.DefaultGameObjectInjectionWorld, blobAssetStore);
Entity entity = GameObjectConversionUtility.ConvertGameObjectHierarchy(spb, setting);
Translation translation = new Translation();
//生成1萬個小球
for (int i = 0;i < nums;i++)
{
EntityManager entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
Entity spbEntity = entityManager.Instantiate(entity);
translation.Value = new float3(i % 16 *interval + UnityEngine.Random.Range(-0.1f,0.1f) ,
i / (16 * 16) * interval + UnityEngine.Random.Range(-0.1f, 0.1f),
i / 16 % 16 * interval + UnityEngine.Random.Range(-0.1f, 0.1f));
entityManager.SetComponentData(spbEntity, translation);
}
}
private void OnDestroy()
{
blobAssetStore.Dispose();
}
}
效果圖 ,運(yùn)行時記得開Burst編譯器優(yōu)化性能
?
?案例2:魚群移動
10000個魚的魚群移動,幀率穩(wěn)定在150幀
//FishManager.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
public class FishManager : MonoBehaviour
{
public GameObject fishPrb;
public int sum,radius;
private BlobAssetStore blobAssetStore;
public Transform center;
EntityManager entityManager;
private void Start()
{
//將小球轉(zhuǎn)化為實(shí)體
blobAssetStore = new BlobAssetStore();
GameObjectConversionSettings setting = GameObjectConversionSettings.FromWorld(World.DefaultGameObjectInjectionWorld, blobAssetStore);
Entity fishentity = GameObjectConversionUtility.ConvertGameObjectHierarchy(fishPrb, setting);
Translation translation = new Translation();
entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
//生產(chǎn)球形魚群
for (int i = 0;i < sum;i++)
{
Entity fish = entityManager.Instantiate(fishentity);
Vector3 tempDir = Vector3.Normalize(new Vector3(UnityEngine.Random.Range(-1f, 1f),
UnityEngine.Random.Range(-1f, 1f),
UnityEngine.Random.Range(-1f, 1f))) * radius;
Vector3 final = center.position + tempDir;
translation.Value = new float3(final.x, final.y, final.z);
entityManager.SetComponentData(fish, translation);
//設(shè)置每個魚的位置
Rotation rotation = new Rotation();
rotation.Value = quaternion.LookRotationSafe(tempDir, math.up());
entityManager.SetComponentData(fish, rotation);
}
}
private void OnDestroy()
{
blobAssetStore.Dispose();
}
}
//TargetComponent.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
[GenerateAuthoringComponent]
public struct TargetComponent : IComponentData
{
}
//FishComponent.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
[GenerateAuthoringComponent]
public struct FishComponent : IComponentData
{
public float fishMoveSpeed, fishRotationSpeed;
public float targetDirWeight, farAwayWeight, centerWeight;
}
//BoidMoveSystem.cs
using System.Collections;
using System.Collections.Generic;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
public class BoidMoveSystem : SystemBase
{
protected override void OnUpdate()
{
float3 tempSumPos = float3.zero;
float3 tempCenterPos = float3.zero;
float3 tempTargetPos = float3.zero;
float tempTime = Time.DeltaTime;
int tempSumFish = 0;
Entities.ForEach((Entity pEntity, ref Translation translation,ref Rotation rotation, ref FishComponent pfishComponent) =>
{
tempSumPos += translation.Value;
tempSumFish++;
}).Run();
Entities.ForEach((Entity pEntity, ref Translation translation, ref Rotation rotation, ref TargetComponent targetComponent) =>
{
tempTargetPos = translation.Value;
}).Run();
tempCenterPos = tempSumPos / tempSumFish;
Entities.ForEach((Entity pEntity, ref Translation translation, ref Rotation rotation, ref LocalToWorld pLocalToWorldComponentData, ref FishComponent pfishComponent) =>
{
pLocalToWorldComponentData.Value = float4x4.TRS(translation.Value,rotation.Value,new float3(1, 1, 1));
float3 tempTargetDir = math.normalize(tempTargetPos - pLocalToWorldComponentData.Position);
float3 tempFarAwayDir = math.normalize(tempSumFish * pLocalToWorldComponentData.Position - tempSumPos);
float3 tempCenterDir = math.normalize(tempCenterPos - pLocalToWorldComponentData.Position);
float3 tempSumDir = math.normalize(tempTargetDir * pfishComponent.targetDirWeight +
tempFarAwayDir * pfishComponent.farAwayWeight +
tempCenterDir * pfishComponent.centerWeight);
float3 tempOffsetRotation = math.normalize(tempSumDir - pLocalToWorldComponentData.Forward);
pLocalToWorldComponentData.Value = float4x4.TRS(pLocalToWorldComponentData.Position + pLocalToWorldComponentData.Forward * pfishComponent.fishMoveSpeed * tempTime,
quaternion.LookRotationSafe(pLocalToWorldComponentData.Forward + tempOffsetRotation * pfishComponent.fishRotationSpeed * tempTime, math.up()),
new float3(1, 1, 1));
translation.Value = pLocalToWorldComponentData.Position;
rotation.Value = pLocalToWorldComponentData.Rotation;
}).ScheduleParallel();
}
}
文章來源:http://www.zghlxwxcb.cn/news/detail-743338.html
?文章來源地址http://www.zghlxwxcb.cn/news/detail-743338.html
到了這里,關(guān)于Unity DOTS技術(shù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!