Unity DOTS自定義Authoring類:連接GameObject與Entity的設(shè)計(jì)橋梁
1. 項(xiàng)目概述為什么我們需要自定義Authoring類如果你已經(jīng)開始接觸Unity的DOTSData-Oriented Technology Stack并且嘗試過將一些簡單的GameObject轉(zhuǎn)換為Entity那么你一定用過ConvertToEntity組件。這個(gè)組件很方便但它更像是一個(gè)“黑盒”把整個(gè)GameObject及其子物體一股腦地轉(zhuǎn)換成一個(gè)或多個(gè)Entity。對于快速原型或者簡單的對象這沒問題。但當(dāng)我們開始構(gòu)建一個(gè)稍微復(fù)雜點(diǎn)的系統(tǒng)比如一個(gè)RTS游戲里的士兵單位或者一個(gè)模擬城市里的建筑時(shí)問題就來了。這個(gè)士兵單位可能包含一個(gè)用于渲染的Mesh、一個(gè)用于碰撞的物理體、一個(gè)代表生命值的組件、一個(gè)代表所屬陣營的標(biāo)簽、一個(gè)存儲(chǔ)當(dāng)前攻擊目標(biāo)的引用以及一堆用于行為樹或狀態(tài)機(jī)的數(shù)據(jù)。如果只用ConvertToEntity這些數(shù)據(jù)要么散落在不同的MonoBehaviour里轉(zhuǎn)換邏輯混雜不清要么你不得不寫一個(gè)龐大而復(fù)雜的IConvertGameObjectToEntity實(shí)現(xiàn)里面充滿了if-else和GetComponent調(diào)用難以維護(hù)更違背了DOTS數(shù)據(jù)驅(qū)動(dòng)、清晰分離的初衷。這就是自定義Authoring類登場的時(shí)刻。它不是一個(gè)高深莫測的黑魔法而是DOTS工作流中連接我們熟悉的面向?qū)ο缶庉嬈魇澜鏕ameObject, MonoBehaviour與高效的面向數(shù)據(jù)運(yùn)行時(shí)世界Entity, ComponentData的設(shè)計(jì)橋梁。簡單說它允許你為特定的、復(fù)雜的游戲?qū)ο箢愋投x一個(gè)專屬的、可視化的、在Inspector里友好配置的“轉(zhuǎn)換藍(lán)圖”。想象一下你有一個(gè)“炮塔”預(yù)制體。通過自定義Authoring類TurretAuthoring你可以在Inspector里直接設(shè)置炮塔的傷害值、攻擊范圍、炮彈預(yù)制體引用和開火速率。點(diǎn)擊播放后TurretAuthoring會(huì)負(fù)責(zé)將這些配置值高效、準(zhǔn)確地填充到一個(gè)包含TurretComponentData存儲(chǔ)數(shù)值和TurretPrefabReference存儲(chǔ)Entity引用的純凈Entity中。這比在運(yùn)行時(shí)用GetComponent去一個(gè)個(gè)查找要高效和清晰得多。所以掌握自定義Authoring類核心是掌握一種設(shè)計(jì)模式。它讓你能以一種可控、可預(yù)測的方式將編輯器中的設(shè)計(jì)意圖翻譯成DOTS運(yùn)行時(shí)能高效處理的數(shù)據(jù)結(jié)構(gòu)。這是從“能用DOTS”到“善用DOTS”的關(guān)鍵一步。2. 核心概念與設(shè)計(jì)思路拆解在動(dòng)手寫代碼之前我們必須把幾個(gè)關(guān)鍵概念和它們之間的關(guān)系理清楚。DOTS的轉(zhuǎn)換流程看似復(fù)雜但一旦理解了其設(shè)計(jì)哲學(xué)就會(huì)變得非常直觀。2.1 Authoring、Runtime Data與Conversion的世界DOTS架構(gòu)明確區(qū)分了“編輯時(shí)”和“運(yùn)行時(shí)”。Authoring創(chuàng)作期這就是我們在Unity編輯器中操作的階段。我們使用GameObject、MonoBehaviour和預(yù)制體來搭建場景、配置參數(shù)。自定義的Authoring類本質(zhì)上就是一個(gè)特殊的MonoBehaviour它掛載在GameObject上唯一目的就是持有數(shù)據(jù)并告訴Unity“嘿我這個(gè)對象需要被轉(zhuǎn)換成Entity轉(zhuǎn)換規(guī)則在我這里定義”。Runtime Data運(yùn)行時(shí)數(shù)據(jù)這是SubsystemECS系統(tǒng)直接操作的對象。它們必須是IComponentData接口的結(jié)構(gòu)體或Blob Asset、Dynamic Buffer等特點(diǎn)是純數(shù)據(jù)、沒有方法、盡可能簡單以利于CPU緩存和并行處理。例如struct HealthComponent : IComponentData { public float Value; }。Conversion轉(zhuǎn)換期這是連接上述兩個(gè)世界的橋梁發(fā)生在進(jìn)入Play模式或烘焙Baking時(shí)。轉(zhuǎn)換系統(tǒng)會(huì)掃描場景中所有掛載了Authoring組件的GameObject并執(zhí)行其定義的轉(zhuǎn)換邏輯生成對應(yīng)的Entity和ComponentData。自定義Authoring類的核心工作就是在Conversion階段讀取自身及關(guān)聯(lián)GameObject上的配置數(shù)據(jù)然后創(chuàng)建或配置目標(biāo)Entity為其添加或設(shè)置相應(yīng)的Runtime Data。2.2 轉(zhuǎn)換工作流的三種模式Unity提供了不同粒度和控制級別的轉(zhuǎn)換方式理解它們的區(qū)別至關(guān)重要自動(dòng)轉(zhuǎn)換ConvertToEntity最簡單。掛上這個(gè)組件Unity會(huì)嘗試自動(dòng)將GameObject及其子物體轉(zhuǎn)換為Entity。它會(huì)為每個(gè)GameObject生成一個(gè)Entity并自動(dòng)轉(zhuǎn)換Transform、MeshRenderer等常見組件??刂屏ψ钊踹m合簡單對象或快速起步。基于接口的轉(zhuǎn)換IConvertGameObjectToEntity在Authoring類中實(shí)現(xiàn)這個(gè)接口。它提供了一個(gè)Convert方法讓你可以編寫自定義邏輯來操作轉(zhuǎn)換生成的Entity。這是自定義Authoring類最常用的方式。它給予你對單個(gè)GameObject如何轉(zhuǎn)換為其對應(yīng)Entity的完全控制權(quán)?;谙到y(tǒng)的轉(zhuǎn)換GameObjectConversionSystem最強(qiáng)大、最靈活。你可以創(chuàng)建自己的ConversionSystem在整個(gè)轉(zhuǎn)換過程中對所有GameObject進(jìn)行掃描和處理。它可以實(shí)現(xiàn)跨GameObject的依賴處理、批量操作、生成額外的管理Entity等復(fù)雜邏輯??刂屏ψ顝?qiáng)但復(fù)雜度也最高通常用于處理整個(gè)子系統(tǒng)或機(jī)制的轉(zhuǎn)換如導(dǎo)航網(wǎng)格、場景光照數(shù)據(jù)的轉(zhuǎn)換。對于大多數(shù)自定義游戲?qū)ο箢愋湍J?IConvertGameObjectToEntity是我們的最佳選擇。它平衡了控制力與復(fù)雜度讓我們能專注于定義“我這個(gè)類型的對象應(yīng)該包含什么數(shù)據(jù)”。2.3 設(shè)計(jì)一個(gè)自定義Authoring類的思考過程假設(shè)我們要為一個(gè)“收集品”比如金幣創(chuàng)建Authoring類。設(shè)計(jì)思路應(yīng)該是自頂向下的定義運(yùn)行時(shí)需要什么數(shù)據(jù)Runtime Data一個(gè)收集品Entity需要什么一個(gè)CollectibleTag用于系統(tǒng)篩選一個(gè)RotationSpeed旋轉(zhuǎn)速度一個(gè)PickupEffectPrefab拾取特效的Entity預(yù)制體引用先定義好這些IComponentData結(jié)構(gòu)體。設(shè)計(jì)編輯時(shí)的配置界面Authoring創(chuàng)建一個(gè)CollectibleAuthoring : MonoBehaviour, IConvertGameObjectToEntity。它應(yīng)該有哪些可配置的公共字段float RotationSpeed在Inspector里用滑塊控制、GameObject PickupEffectPrefab拖拽一個(gè)預(yù)制體進(jìn)來。這些字段就是為了方便設(shè)計(jì)師配置。實(shí)現(xiàn)轉(zhuǎn)換邏輯Conversion在Convert方法里將RotationSpeed賦值給Entity的RotationSpeedComponent將PickupEffectPrefab通過GetEntity轉(zhuǎn)換成一個(gè)EntityPrefabReference并附加給Entity。這個(gè)思考過程確保了我們的設(shè)計(jì)是數(shù)據(jù)驅(qū)動(dòng)的Authoring類僅僅是一個(gè)友好的數(shù)據(jù)輸入界面。3. 手把手創(chuàng)建第一個(gè)自定義Authoring類理論說再多不如動(dòng)手做一遍。我們來創(chuàng)建一個(gè)經(jīng)典的例子一個(gè)會(huì)上下漂浮、自身旋轉(zhuǎn)的“能量晶體”。3.1 步驟一定義運(yùn)行時(shí)組件首先我們定義這個(gè)晶體在運(yùn)行時(shí)所需要的數(shù)據(jù)。在Runtime文件夾建議的代碼組織方式下創(chuàng)建兩個(gè)組件。// FloatComponent.cs - 用于上下漂浮 using Unity.Entities; public struct FloatComponent : IComponentData { public float Amplitude; // 浮動(dòng)幅度 public float Frequency; // 浮動(dòng)頻率 public float InitialY; // 初始Y軸位置用于計(jì)算偏移 } // RotateComponent.cs - 用于自身旋轉(zhuǎn) using Unity.Entities; public struct RotateComponent : IComponentData { public float Speed; // 旋轉(zhuǎn)速度度/秒 }這兩個(gè)是純粹的標(biāo)簽數(shù)據(jù)不包含任何邏輯。3.2 步驟二創(chuàng)建Authoring類并實(shí)現(xiàn)轉(zhuǎn)換接下來創(chuàng)建Authoring類。我習(xí)慣把它放在Authoring文件夾下以明確其職責(zé)。// CrystalAuthoring.cs using UnityEngine; using Unity.Entities; public class CrystalAuthoring : MonoBehaviour, IConvertGameObjectToEntity { // 在Inspector中可配置的參數(shù) public float floatAmplitude 0.5f; public float floatFrequency 1.0f; public float rotateSpeed 90.0f; public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) { // 1. 添加浮動(dòng)組件并設(shè)置數(shù)據(jù) dstManager.AddComponentData(entity, new FloatComponent { Amplitude floatAmplitude, Frequency floatFrequency, InitialY transform.position.y // 記錄轉(zhuǎn)換時(shí)的初始高度 }); // 2. 添加旋轉(zhuǎn)組件并設(shè)置數(shù)據(jù) dstManager.AddComponentData(entity, new RotateComponent { Speed rotateSpeed }); // 注意這里我們沒有處理Transform。因?yàn)镃onvertToEntity或默認(rèn)轉(zhuǎn)換系統(tǒng) // 會(huì)自動(dòng)為帶有Transform的GameObject生成LocalToWorld等Transform組件。 // 我們的FloatSystem和RotateSystem會(huì)去讀寫這些組件。 } }關(guān)鍵點(diǎn)解析IConvertGameObjectToEntity接口強(qiáng)制實(shí)現(xiàn)Convert方法。這是轉(zhuǎn)換的核心。Convert方法參數(shù)entity: 這個(gè)GameObject將要轉(zhuǎn)換成的目標(biāo)Entity。系統(tǒng)已經(jīng)為我們創(chuàng)建好了。dstManager:目標(biāo)世界的EntityManager用于對目標(biāo)Entity進(jìn)行添加/移除組件、設(shè)置數(shù)據(jù)等操作。conversionSystem: 轉(zhuǎn)換系統(tǒng)本身可以用來查詢其他對象的轉(zhuǎn)換結(jié)果例如通過GetPrimaryEntity獲取另一個(gè)GameObject對應(yīng)的Entity。AddComponentData: 這是為Entity添加組件并同時(shí)設(shè)置其初始值的標(biāo)準(zhǔn)方法。transform.position.y: 我們在轉(zhuǎn)換時(shí)捕獲了GameObject的初始世界Y坐標(biāo)。這是一個(gè)非常重要的技巧。因?yàn)檗D(zhuǎn)換后GameObject的Transform數(shù)據(jù)會(huì)被處理成ECS的LocalToWorld等組件其值可能會(huì)被重置或基于父Entity重新計(jì)算。如果我們的浮動(dòng)邏輯需要基于原始位置就必須在轉(zhuǎn)換時(shí)把這個(gè)“初始狀態(tài)”保存到自定義組件里。3.3 步驟三創(chuàng)建對應(yīng)的ECS系統(tǒng)數(shù)據(jù)有了需要系統(tǒng)來驅(qū)動(dòng)它們。創(chuàng)建兩個(gè)簡單的Job System。// FloatSystem.cs using Unity.Entities; using Unity.Jobs; using Unity.Mathematics; using Unity.Transforms; public partial class FloatSystem : SystemBase { protected override void OnUpdate() { float elapsedTime (float)Time.ElapsedTime; Entities .ForEach((ref LocalToWorld transform, in FloatComponent floatData) { // 計(jì)算新的Y軸位置初始位置 正弦波偏移 float newY floatData.InitialY math.sin(elapsedTime * floatData.Frequency) * floatData.Amplitude; // 獲取當(dāng)前的位置和旋轉(zhuǎn) float3 position transform.Position; quaternion rotation transform.Rotation; // 構(gòu)造新的LocalToWorld矩陣 transform.Value float4x4.TRS( new float3(position.x, newY, position.z), rotation, new float3(1.0f, 1.0f, 1.0f) ); }).ScheduleParallel(); } } // RotateSystem.cs using Unity.Entities; using Unity.Jobs; using Unity.Mathematics; using Unity.Transforms; public partial class RotateSystem : SystemBase { protected override void OnUpdate() { float deltaTime Time.DeltaTime; Entities .ForEach((ref Rotation rotation, in RotateComponent rotateData) { // 繞Y軸旋轉(zhuǎn) quaternion deltaRotation quaternion.EulerXYZ(new float3(0, rotateData.Speed * deltaTime * math.PI / 180f, 0)); rotation.Value math.mul(rotation.Value, deltaRotation); }).ScheduleParallel(); } }3.4 步驟四在場景中測試在場景中創(chuàng)建一個(gè)Cube或Sphere命名為“EnergyCrystal”。移除默認(rèn)的MeshRenderer和MeshFilter如果你用了其他渲染方案如Hybrid Renderer則按需處理。掛載ConvertToEntity組件這是觸發(fā)轉(zhuǎn)換的開關(guān)。掛載我們剛寫好的CrystalAuthoring組件。在Inspector中調(diào)整floatAmplitude、floatFrequency和rotateSpeed參數(shù)。點(diǎn)擊播放你應(yīng)該能看到這個(gè)晶體一邊旋轉(zhuǎn)一邊上下漂浮。至此你已經(jīng)完成了第一個(gè)功能完整的自定義Authoring類。它成功地將編輯器中的配置參數(shù)轉(zhuǎn)換成了ECS世界里驅(qū)動(dòng)行為的純凈數(shù)據(jù)。4. 處理復(fù)雜依賴與引用現(xiàn)實(shí)項(xiàng)目中的對象很少是孤立的。一個(gè)炮塔需要引用炮彈預(yù)制體一個(gè)技能需要引用特效一個(gè)生產(chǎn)建筑需要引用它產(chǎn)出的單位。在DOTS中這些“引用”不再是GameObject或MonoBehaviour而是Entity或BlobAssetReference。處理這些依賴是自定義Authoring類的進(jìn)階課題。4.1 引用其他預(yù)制體Prefab Reference這是最常見的需求。我們希望Authoring類里能拖拽一個(gè)Prefab然后在運(yùn)行時(shí)實(shí)例化它。錯(cuò)誤做法在Convert方法里直接保存GameObject或Prefab的引用。這行不通因?yàn)檗D(zhuǎn)換后GameObject世界不存在了。正確做法使用轉(zhuǎn)換系統(tǒng)GameObjectConversionSystem提供的GetPrimaryEntity方法將Prefab也轉(zhuǎn)換成一個(gè)Entity并獲取其引用。這個(gè)引用是一個(gè)Entity但它代表的是預(yù)制體模板。// TurretAuthoring.cs using UnityEngine; using Unity.Entities; public class TurretAuthoring : MonoBehaviour, IConvertGameObjectToEntity { public GameObject projectilePrefab; // 在Inspector中拖拽炮彈預(yù)制體 public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) { // 關(guān)鍵步驟將GameObject預(yù)制體轉(zhuǎn)換為Entity預(yù)制體引用 Entity projectileEntityPrefab conversionSystem.GetPrimaryEntity(projectilePrefab); // 將Entity引用存儲(chǔ)到一個(gè)組件中 dstManager.AddComponentData(entity, new TurretShootingComponent { ProjectilePrefab projectileEntityPrefab, Cooldown 1.0f, Damage 10.0f }); } } // TurretShootingComponent.cs using Unity.Entities; public struct TurretShootingComponent : IComponentData { public Entity ProjectilePrefab; // 這里存儲(chǔ)的是Entity引用 public float Cooldown; public float Damage; public float CurrentCooldown; }在炮塔的射擊系統(tǒng)里你可以使用EntityManager.Instantiate(ProjectilePrefab)來創(chuàng)建炮彈Entity。重要提示GetPrimaryEntity方法要求被引用的Prefab也參與到轉(zhuǎn)換流程中。通常這意味著它也需要掛載ConvertToEntity組件或者通過其他方式如放在Subscene中確保它被轉(zhuǎn)換。如果引用的Prefab沒有被轉(zhuǎn)換GetPrimaryEntity可能會(huì)返回Entity.Null導(dǎo)致運(yùn)行時(shí)錯(cuò)誤。4.2 引用場景中的其他對象非預(yù)制體有時(shí)你需要引用場景中另一個(gè)特定的GameObject比如一個(gè)目標(biāo)點(diǎn)、一個(gè)出生點(diǎn)。處理方式類似但需要確保被引用的對象先于引用它的對象完成轉(zhuǎn)換。這通常不是問題因?yàn)檗D(zhuǎn)換順序默認(rèn)是基于場景層級或依賴關(guān)系的但為了安全你可以使用DeclareReferencedPrefab或依賴GameObjectConversionSystem的Dependency屬性來處理。更簡單的做法是如果這個(gè)引用是靜態(tài)的比如場景裝飾物之間的關(guān)聯(lián)可以在Authoring中記錄對方的GameObject然后在Convert中通過conversionSystem.GetPrimaryEntity(targetGameObject)來獲取其Entity。同樣要確保targetGameObject也被轉(zhuǎn)換。4.3 處理Blob Asset引用復(fù)雜共享數(shù)據(jù)對于更復(fù)雜的、只讀的共享數(shù)據(jù)如武器屬性表、技能配置、AI行為樹BlobAsset是比IComponentData更高效的選擇。Authoring類也可以參與BlobAsset的創(chuàng)建。流程通常是在Authoring類中定義配置數(shù)據(jù)結(jié)構(gòu)使用GameObject和普通字段。在Convert方法中使用BlobAssetBuilder來構(gòu)造BlobAsset數(shù)據(jù)。通過conversionSystem.BlobAssetStore.AddUniqueBlobAsset來注冊這個(gè)BlobAsset并獲取一個(gè)BlobAssetReferenceT。將這個(gè)引用存儲(chǔ)到目標(biāo)Entity的某個(gè)組件中。由于BlobAsset的創(chuàng)建涉及非托管內(nèi)存和序列化代碼稍復(fù)雜但它對于共享只讀數(shù)據(jù)的性能提升是巨大的。一個(gè)典型的例子是為所有同類型敵人共享一套屬性// 1. 定義Blob數(shù)據(jù)結(jié)構(gòu) public struct EnemyStatsBlob { public float MaxHealth; public float MoveSpeed; public float AttackDamage; public BlobString Name; } // 2. 在Authoring的Convert中創(chuàng)建 public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) { var builder new BlobBuilder(Allocator.Temp); ref EnemyStatsBlob stats ref builder.ConstructRootEnemyStatsBlob(); stats.MaxHealth maxHealth; stats.MoveSpeed moveSpeed; stats.AttackDamage attackDamage; builder.AllocateString(ref stats.Name, enemyName); var blobReference builder.CreateBlobAssetReferenceEnemyStatsBlob(Allocator.Persistent); builder.Dispose(); // 存儲(chǔ)引用 dstManager.AddComponentData(entity, new EnemyStatsReference { Value blobReference }); }5. 高級技巧與最佳實(shí)踐當(dāng)你熟悉了基礎(chǔ)操作后下面這些技巧能讓你的Authoring類更健壯、更高效。5.1 利用[GenerateAuthoringComponent]屬性簡化流程對于非常簡單的組件其Authoring類可能只是幾個(gè)公共字段加上一個(gè)Convert方法。Unity提供了一個(gè)便捷屬性[GenerateAuthoringComponent]可以自動(dòng)為你生成這個(gè)Authoring類。// 在你的IComponentData結(jié)構(gòu)體上添加這個(gè)屬性 [GenerateAuthoringComponent] public struct HealthComponent : IComponentData { public float Value; }編譯后Unity會(huì)自動(dòng)生成一個(gè)名為HealthComponentAuthoring的MonoBehaviour它包含一個(gè)float Value字段并自動(dòng)實(shí)現(xiàn)轉(zhuǎn)換邏輯。你只需要把這個(gè)生成的組件掛到GameObject上即可。這極大地簡化了簡單組件的創(chuàng)建工作流。注意事項(xiàng)自動(dòng)生成的Authoring類功能有限。它只能處理簡單的字段類型float, int, Entity等無法處理復(fù)雜邏輯如引用其他Prefab、創(chuàng)建BlobAsset。對于復(fù)雜組件仍需手動(dòng)編寫Authoring類。5.2 轉(zhuǎn)換中的依賴管理與執(zhí)行順序當(dāng)你的轉(zhuǎn)換邏輯依賴于其他GameObject的轉(zhuǎn)換結(jié)果時(shí)比如上面獲取其他Entity的引用執(zhí)行順序就變得重要。IConvertGameObjectToEntity的Convert方法調(diào)用順序是不確定的。解決方案使用GameObjectConversionSystem。你可以創(chuàng)建自己的ConversionSystem并覆寫OnUpdate方法。在這個(gè)系統(tǒng)里你可以通過Dependency屬性確保所有IConvertGameObjectToEntity都執(zhí)行完畢后再執(zhí)行你的邏輯或者通過Entities.ForEach遍歷所有具有特定Authoring組件的Entity來進(jìn)行后處理。例如你需要在所有“隊(duì)伍”對象轉(zhuǎn)換完成后為每個(gè)“士兵”對象分配其所屬隊(duì)伍的Entity引用。這需要在ConversionSystem中分兩步完成。5.3 調(diào)試轉(zhuǎn)換過程Debug.Log與Entity Debugger轉(zhuǎn)換過程在編輯模式下進(jìn)行Debug.Log是可以正常工作的。在Convert方法中添加日志可以幫助你理解轉(zhuǎn)換的觸發(fā)時(shí)機(jī)和順序。更強(qiáng)大的工具是Unity編輯器的Entity Debugger(Window Analysis Entity Debugger)。在播放模式下你可以查看所有存在的Entity、它們的組件數(shù)據(jù)。通過對比轉(zhuǎn)換前后你可以清晰地看到你的Authoring類是否正確地為Entity添加了預(yù)期的組件和數(shù)據(jù)。這是調(diào)試DOTS轉(zhuǎn)換問題的首選工具。5.4 性能考量與常見陷阱避免在Convert中進(jìn)行昂貴操作Convert方法在進(jìn)入播放模式時(shí)運(yùn)行雖然不直接影響運(yùn)行時(shí)幀率但過長的轉(zhuǎn)換時(shí)間會(huì)影響測試迭代速度。避免在這里進(jìn)行復(fù)雜的計(jì)算、同步加載資源等。謹(jǐn)慎使用GetComponent在Convert方法中如果需要獲取同一GameObject上其他MonoBehaviour的數(shù)據(jù)使用GetComponent是可行的。但頻繁調(diào)用或在不必要的對象上調(diào)用會(huì)影響轉(zhuǎn)換性能。盡量將配置數(shù)據(jù)集中在一個(gè)Authoring類中。處理好Null引用Inspector中配置的字段可能為空。在Convert方法中務(wù)必對可能的null引用進(jìn)行檢查提供默認(rèn)值或跳過某些邏輯避免運(yùn)行時(shí)崩潰。Subscene與Live Conversion對于大型場景使用Subscene進(jìn)行分批轉(zhuǎn)換和流式加載是推薦做法。自定義Authoring類在Subscene中工作方式相同。注意開啟“Live Conversion”時(shí)對Authoring組件屬性的修改會(huì)實(shí)時(shí)觸發(fā)部分轉(zhuǎn)換這可能帶來一些復(fù)雜性但也是強(qiáng)大的迭代工具。6. 實(shí)戰(zhàn)案例構(gòu)建一個(gè)簡單的RTS單位讓我們綜合運(yùn)用所學(xué)為一個(gè)極簡的RTS游戲創(chuàng)建一個(gè)“士兵”單位的完整Authoring方案。這個(gè)士兵需要視覺表現(xiàn)、生命值、移動(dòng)速度、攻擊力、以及所屬隊(duì)伍的標(biāo)識(shí)。6.1 定義運(yùn)行時(shí)組件// Runtime/Components/UnitComponents.cs using Unity.Entities; // 生命值 public struct Health : IComponentData { public float Value; public float MaxValue; } // 移動(dòng)屬性 public struct MoveSpeed : IComponentData { public float Value; } // 攻擊屬性 public struct AttackDamage : IComponentData { public float Value; } // 隊(duì)伍歸屬使用SharedComponent便于按隊(duì)伍篩選 public struct Team : ISharedComponentData { public int Id; }6.2 創(chuàng)建士兵的Authoring類// Authoring/UnitAuthoring.cs using UnityEngine; using Unity.Entities; public class UnitAuthoring : MonoBehaviour, IConvertGameObjectToEntity { [Header(Basic Stats)] public float maxHealth 100f; public float moveSpeed 5f; public float attackDamage 10f; [Header(Team)] public int teamId 0; [Header(Visuals (Optional))] public GameObject selectionRingPrefab; // 選中時(shí)的光環(huán)效果預(yù)制體 public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) { // 添加基礎(chǔ)屬性組件 dstManager.AddComponentData(entity, new Health { Value maxHealth, MaxValue maxHealth }); dstManager.AddComponentData(entity, new MoveSpeed { Value moveSpeed }); dstManager.AddComponentData(entity, new AttackDamage { Value attackDamage }); // 添加共享組件注意頻繁改變SharedComponent會(huì)影響性能這里假設(shè)隊(duì)伍不變 dstManager.AddSharedComponentData(entity, new Team { Id teamId }); // 處理可選視覺效果引用 if (selectionRingPrefab ! null) { Entity ringPrefabEntity conversionSystem.GetPrimaryEntity(selectionRingPrefab); if (ringPrefabEntity ! Entity.Null) { dstManager.AddComponentData(entity, new SelectionRingPrefab { Value ringPrefabEntity }); } else { Debug.LogWarning($Selection ring prefab for {gameObject.name} is not a convertible GameObject. Reference ignored., this); } } // 可以添加一個(gè)標(biāo)簽組件方便系統(tǒng)查詢“所有士兵單位” dstManager.AddComponentUnitTag(entity); } } // 標(biāo)簽組件和預(yù)制體引用組件 public struct UnitTag : IComponentData {} public struct SelectionRingPrefab : IComponentData { public Entity Value; }6.3 創(chuàng)建對應(yīng)的系統(tǒng)示例移動(dòng)系統(tǒng)// Systems/UnitMoveSystem.cs using Unity.Entities; using Unity.Jobs; using Unity.Mathematics; using Unity.Transforms; public partial class UnitMoveSystem : SystemBase { protected override void OnUpdate() { float deltaTime Time.DeltaTime; Entities .WithName(MoveUnits) .ForEach((ref Translation translation, in MoveSpeed speed, in UnitMoveTarget target) { // 簡單的向目標(biāo)點(diǎn)移動(dòng)邏輯 float3 direction target.Value - translation.Value; float distance math.length(direction); if (distance 0.1f) { direction math.normalize(direction); translation.Value direction * speed.Value * deltaTime; } }).ScheduleParallel(); } } // 移動(dòng)目標(biāo)組件可由其他系統(tǒng)如玩家輸入系統(tǒng)設(shè)置 public struct UnitMoveTarget : IComponentData { public float3 Value; }6.4 在編輯器中配置與測試創(chuàng)建一個(gè)士兵的預(yù)制體例如一個(gè)膠囊體加上顏色材質(zhì)。為預(yù)制體根節(jié)點(diǎn)添加ConvertToEntity組件。添加UnitAuthoring組件。在Inspector中配置maxHealth、moveSpeed、attackDamage和teamId。可選創(chuàng)建一個(gè)表示選中光環(huán)的簡單圓形預(yù)制體拖拽到selectionRingPrefab字段。將預(yù)制體拖入場景點(diǎn)擊播放。通過Entity Debugger你可以看到這個(gè)士兵Entity已經(jīng)擁有了Health、MoveSpeed、AttackDamage、Team、UnitTag等所有組件。這個(gè)案例展示了一個(gè)中等復(fù)雜度的自定義Authoring類如何將設(shè)計(jì)師友好的編輯器配置系統(tǒng)地、結(jié)構(gòu)化地轉(zhuǎn)換為一套高效的ECS運(yùn)行時(shí)數(shù)據(jù)為后續(xù)實(shí)現(xiàn)移動(dòng)、攻擊、選擇等游戲邏輯打下了堅(jiān)實(shí)的基礎(chǔ)。通過這種方式即使是非常復(fù)雜的游戲?qū)ο笃滢D(zhuǎn)換邏輯也能保持清晰和可維護(hù)。

相關(guān)新聞

模擬電路反饋設(shè)計(jì):從原理到實(shí)戰(zhàn),解決自激振蕩與性能優(yōu)化

模擬電路反饋設(shè)計(jì):從原理到實(shí)戰(zhàn),解決自激振蕩與性能優(yōu)化

1. 項(xiàng)目概述:為什么反饋是放大電路的靈魂? 干了這么多年硬件設(shè)計(jì),畫過的板子、調(diào)過的電路不計(jì)其數(shù),要說哪個(gè)概念貫穿始終、最讓人又愛又恨,那絕對是“反饋”。新手看模電書,學(xué)到反饋這一章往往一頭霧水&…

2026/8/2 20:17:04 閱讀更多
【單片機(jī)課程設(shè)計(jì)/畢業(yè)設(shè)計(jì)】基于 51/STM32 單片機(jī)的醫(yī)護(hù)端優(yōu)先級呼叫處理設(shè)備設(shè)計(jì) 無線傳輸型 8 床位智能呼叫與定時(shí)播報(bào)系統(tǒng)開發(fā)(020301)

【單片機(jī)課程設(shè)計(jì)/畢業(yè)設(shè)計(jì)】基于 51/STM32 單片機(jī)的醫(yī)護(hù)端優(yōu)先級呼叫處理設(shè)備設(shè)計(jì) 無線傳輸型 8 床位智能呼叫與定時(shí)播報(bào)系統(tǒng)開發(fā)(020301)

博主介紹:??碼農(nóng)一枚 ,專注于大學(xué)生項(xiàng)目實(shí)戰(zhàn)開發(fā)、講解和畢業(yè)🚢文撰寫修改等。全棧領(lǐng)域優(yōu)質(zhì)創(chuàng)作者,博客之星、掘金/華為云/阿里云/InfoQ等平臺(tái)優(yōu)質(zhì)作者、專注于嵌入式單片機(jī),Java、小程序技術(shù)領(lǐng)域和畢業(yè)項(xiàng)目實(shí)戰(zhàn) ??…

2026/8/2 20:07:03 閱讀更多
GMR:機(jī)器人運(yùn)動(dòng)重定向技術(shù)革命 - 通用化運(yùn)動(dòng)遷移的突破性解決方案

GMR:機(jī)器人運(yùn)動(dòng)重定向技術(shù)革命 - 通用化運(yùn)動(dòng)遷移的突破性解決方案

GMR:機(jī)器人運(yùn)動(dòng)重定向技術(shù)革命 - 通用化運(yùn)動(dòng)遷移的突破性解決方案 【免費(fèi)下載鏈接】GMR [ICRA 2026] GMR: General Motion Retargeting. Retarget human motions into diverse humanoid robots in real time on CPU. Retargeter for TWIST. 項(xiàng)目地址: https://git…

2026/8/2 21:27:11 閱讀更多
單片機(jī)畢設(shè)項(xiàng)目:基于 STM32/51 單片機(jī)的酒駕預(yù)防酒精檢測硬件系統(tǒng) 融合聲光提示與語音播報(bào)的單片機(jī)酒精超標(biāo)報(bào)警設(shè)計(jì)(020401)

單片機(jī)畢設(shè)項(xiàng)目:基于 STM32/51 單片機(jī)的酒駕預(yù)防酒精檢測硬件系統(tǒng) 融合聲光提示與語音播報(bào)的單片機(jī)酒精超標(biāo)報(bào)警設(shè)計(jì)(020401)

博主介紹:??碼農(nóng)一枚 ,專注于大學(xué)生項(xiàng)目實(shí)戰(zhàn)開發(fā)、講解和畢業(yè)🚢文撰寫修改等。全棧領(lǐng)域優(yōu)質(zhì)創(chuàng)作者,博客之星、掘金/華為云/阿里云/InfoQ等平臺(tái)優(yōu)質(zhì)作者、專注于嵌入式單片機(jī),Java、小程序技術(shù)領(lǐng)域和畢業(yè)項(xiàng)目實(shí)戰(zhàn) ??…

2026/8/2 21:27:11 閱讀更多
MoneyPrinterPlus實(shí)戰(zhàn)指南:AI視頻批量生成與自動(dòng)化發(fā)布完整解決方案

MoneyPrinterPlus實(shí)戰(zhàn)指南:AI視頻批量生成與自動(dòng)化發(fā)布完整解決方案

MoneyPrinterPlus實(shí)戰(zhàn)指南:AI視頻批量生成與自動(dòng)化發(fā)布完整解決方案 【免費(fèi)下載鏈接】MoneyPrinterPlus AI一鍵批量生成各類短視頻,自動(dòng)批量混剪短視頻,自動(dòng)把視頻發(fā)布到抖音,快手,小紅書,視頻號上,賺錢從來沒有這么容易過! 支持本地語音模型chatTTS,fasterwhisper,…

2026/8/2 0:04:00 閱讀更多
3分鐘搞定!QQ空間歷史說說完整備份終極指南

3分鐘搞定!QQ空間歷史說說完整備份終極指南

3分鐘搞定!QQ空間歷史說說完整備份終極指南 【免費(fèi)下載鏈接】GetQzonehistory 獲取QQ空間發(fā)布的歷史說說 項(xiàng)目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 你是否曾想過,那些年發(fā)過的QQ空間說說,那些記錄青春的文字…

2026/8/2 0:04:01 閱讀更多
MoneyPrinterPlus實(shí)戰(zhàn)指南:AI視頻批量生成與自動(dòng)化發(fā)布完整解決方案

MoneyPrinterPlus實(shí)戰(zhàn)指南:AI視頻批量生成與自動(dòng)化發(fā)布完整解決方案

MoneyPrinterPlus實(shí)戰(zhàn)指南:AI視頻批量生成與自動(dòng)化發(fā)布完整解決方案 【免費(fèi)下載鏈接】MoneyPrinterPlus AI一鍵批量生成各類短視頻,自動(dòng)批量混剪短視頻,自動(dòng)把視頻發(fā)布到抖音,快手,小紅書,視頻號上,賺錢從來沒有這么容易過! 支持本地語音模型chatTTS,fasterwhisper,…

2026/8/2 0:04:00 閱讀更多
3分鐘搞定!QQ空間歷史說說完整備份終極指南

3分鐘搞定!QQ空間歷史說說完整備份終極指南

3分鐘搞定!QQ空間歷史說說完整備份終極指南 【免費(fèi)下載鏈接】GetQzonehistory 獲取QQ空間發(fā)布的歷史說說 項(xiàng)目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 你是否曾想過,那些年發(fā)過的QQ空間說說,那些記錄青春的文字…

2026/8/2 0:04:01 閱讀更多
AMAT 0100-02186 I/O 分配 PCB

AMAT 0100-02186 I/O 分配 PCB

AMAT 0100-02186 I/O分配PCB板是應(yīng)用材料(Applied Materials)公司生產(chǎn)的一款用于半導(dǎo)體設(shè)備的I/O信號分配電路板。該型號(0100-02186)的核心特點(diǎn)如下:專用于Endura等半導(dǎo)體工藝腔室。集成信號路由與分配功能。連接控制…

2026/8/2 2:51:21 閱讀更多
Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動(dòng)機(jī)

Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動(dòng)機(jī)

Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動(dòng)機(jī)是日本日清(Nissei)品牌的一款工業(yè)用三相異步電機(jī),適用于自動(dòng)化設(shè)備及通用機(jī)械驅(qū)動(dòng)。該型號(FFMN-32L-10-T0 40AX)的核心特點(diǎn)如下:三相交流異步電動(dòng)機(jī)。額定…

2026/8/2 2:52:49 閱讀更多