Dots 常用操作

游戏中有多个蚂蚁群落,每个蚂蚁属于一个群落,如何设计数据结构?

  • 方法1:为蚂蚁组件添加一个属性 ID,会造成逻辑中大量分支语句,如果分支语句逻辑不平衡可能带来 Job 调度问题,每个蚂蚁会有一份蚂蚁群落 ID 属性的拷贝,海量蚂蚁时造成存储空间的浪费
  • 方法2:使用 TagComponent 标记不同的群落,蚂蚁群落个数不固定,运行时添加 TagComponent 不合适
    TagComponent 个数太多,造成 Archetype 数量爆炸
  • 方法3:使用 Shared Component 处理蚂蚁群落 ID,所有具有相同蚂蚁群落的蚂蚁共享相同的群落 ID 值,创建时确定,运行时不变,不会带来共享属性值频繁更新带来的 Structual Change 的影响,充分利用 WithShareComponentFilter 方法,快速通过一次查询处理所有蚂蚁初始化位置逻辑

读取工具选择

  • SystemAPl.Query+Foreach
  • IJobEntity / lJobChunk + EntityQuery
  • ComponentLookup

写入工具选择

  • EntityManager API
  • Entity Command Buffer
  • Entity Command Buffer ParallexWritter
  • 根据查询结果直接对引用组件的数据写入

DOTS程序的两种模式

  • Hybrid 混合模式:混合使用托管与非托管数据组件
  • Pure 纯净模式:只使用非托管数据组件

定义宏:UNITY_DISABLE_MANAGED_COMPONENTS,会禁用托管对象

DOTS调试宏

  • ENABLE_UNITY_COLLECTIONS_CHECKS
  • UNITY_DOTS_DEBUG

为 release 版本生成调试信息

查看泄露点

Benchmark

Entity 创建的几种方式

  1. 子场景 Bake 方式
  2. 通过 EntityManager 实例化 Prefab 的 Entity 原型方式
  3. 通过 ECS 的 parallelwriter 实例化 ParallelWriter.Instantiate(index, protoType)
  4. 通过 EntityManager 的 CreateEntity 接口以及 RenderMeshUtility 手动创建 Entity 对象

效率由高到低,大致是 1 > 2 > 3 > 4

创建Entity方案推荐与细节

  • 静态场景用子场景烘焙,动态场景对象用主线程创建
  • 多线程创建只在对象初始化需要大量额外计算时使用,这种情况较少
  • 通过 Prefab 实例化 Entity 的方式会在场景中额外保存一个 Entity,其 Archetype 与实例化后的 Entity 不同,所以对于有海量 prefab 对象的项目,注意 Archetype 数量对效率的影响。
  • 完全通过脚本化生成 Entity 不是不能用,但尽量少用。

Component组件添加的几种方式

  1. EntityManager.AddComponent(EntityQuery query, new Component)
  2. EntityManager.AddComponent(NativeArray<Entity> entitie, new Component)
  3. Foreach + EntityManager.AddComponent
  4. Ecb.AddComponent(EntityQuery query, new Component)
  5. Ecb.AddComponent(NativeArray<Entity> entities, new Component)
  6. Foreach + Ecb.AddComponent(entity, new Component)
  7. Ecb + lJobChunk ScheduleParallel
  8. Ecb + lJobEntity ScheduleParallel

EntityManager 接口添加 Component 总结

  • 使用上面 1 方式查询添加组件是最高效的方式
  • 缓存 Entity 数组无论是访问还是做添加删除是没有必要的,性能差
  • 多次调用为单个 Entity 添加组件接口会导致多次 structural change
  • 如果为单个 Entity 添加多个组件尽量通过以下两个接口:
    • CreateArchetype
    • AddComponent(EntityQuery,ComponentTypeset)
  • 先添加普通 Component,再添加 Enableable 修饰的 Component 比颠倒二者添加效率高
  • 通过 Enableable 组件禁用与启用组件比直接添加删除组件性能要高,因为避免 structural change
  • Structual Change 的操作主线程比工作线程更有效
  • 标签组件的性能开销比数据组件要低。

数据的存储与传递方式

静态共享数据区交互

  • static readonly xxx,只读
  • SharedStatic<T>,可变数据,可读写,可以是非托管或托管数据

适合 SharedStatic<T> 的使用场景,网络数据读取

  • 不可避免要做随机访问
  • 网络数据要与 Entity 实体对象有映射关系
  • 有跨 System 访问的需求
  • 有数据共享和读写的需求
csharp 复制代码
public struct SharedCubesEntityColorMap
{
    public static readonly SharedStatic<SharedCubesEntityColorMap> SharedValue 
        = SharedStatic<SharedCubesEntityColorMap>.GetOrCreate<SharedCubesEntityColorMap>();
    
    public SharedCubesEntityColorMap(int capacity)
    {
        entityColorMap = new NativeHashMap<Entity, float3>(capacity, Allocator.Persistent);
    }
    
    //保存 entity 和 color 的映射关系
    public NativeHashMap<Entity, float3> entityColorMap; 
}

假设每个 entity 都有一个颜色属性,需要做随机访问,初始化时记录映射关系

csharp 复制代码
void Update()
{
    //获取某个entity
    float3 color = SharedCubesEntityColorMap.SharedValue.Data.entityColorMap[entity];
    //对颜色进行处理
}

之后就可以在 mono 或 System 中对共享数据进行读写

System复杂度拆分策略

  • 多个处理相同事务的 System 耗时远远小于 1ms,可以考虑将这些 System 合并成一个 System
  • 如果一个 System 处理的事务耗时大于 1ms,考虑使用 Job 并行或 burst 编译优化,优化后仍然远远大于 1ms,可以考虑对 System 做事务拆分
  • 耗时过低的 System 影响其中 Job 的并行程度,耗时过高的 System 影响 CPU 调度

Sub Scene 管理

Dots 中 Scene 结构,Section 用于对子场景进行分组,默认情况下,所有的 entity 都会在 section 0 上

可以在子场景物体上挂载 SceneSectionComponent 官方脚本,设置 Section Index,Section Index = 0 的会优先加载

Initialization 中的 Scene System Group

如何标识子场景

  • EntitySceneReference 直接引用子场景
  • Hash128 GUID 尽量避免使用
  • Entity 子场景加载后返回的 meta entity
csharp 复制代码
[Serializable]
public struct SubscenesReferences : IComponentData
{
    //sub scene资源引用
    public EntitySceneReference cubeSceneReference;
    //sub scene加载后返回的entity,卸载时需要
    public Entity cubeSceneMetaEntity;
    
    public EntitySceneReference sphereSceneReference;
    public Entity sphereSceneMetaEntity;
}
public class SubscenesLoaderAuthoring : MonoBehaviour
{
    [SerializeField] 
    public SubscenesReferences references;
    private class SubscenesLoaderBaker : Baker<SubscenesLoaderAuthoring>
    {
        public override void Bake(SubscenesLoaderAuthoring authoring)
        {
            var entity = GetEntity(TransformUsageFlags.None);
            AddComponent(entity, authoring.references);
        }
    }
}
csharp 复制代码
public partial struct ScenesLoadSystem : ISystem, ISystemStartStop
{
    [BurstCompile]
    public void OnCreate(ref SystemState state)
    {
        state.RequireForUpdate<SubscenesReferences>();
    }

    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        var references = SystemAPI.GetSingletonRW<SubscenesReferences>();
        //场景状态
        SceneSystem.SceneStreamingState ssstate = SceneSystem.GetSceneStreamingState(state.WorldUnmanaged, references.ValueRO.cubeSceneMetaEntity);
       
        if (ssstate == SceneSystem.SceneStreamingState.LoadedSuccessfully)
        {
            //卸载子场景内容,移除 meta entity 上的 RequestSceneLoaded
            //SceneSystem.UnloadScene(state.WorldUnmanaged, references.ValueRW.cubeSceneMetaEntity);
            //完全卸载
            //SceneSystem.UnloadScene(state.WorldUnmanaged, references.ValueRW.cubeSceneMetaEntity, SceneSystem.UnloadParameters.DestroyMetaEntities);
        }

        ssstate = SceneSystem.GetSceneStreamingState(state.WorldUnmanaged, references.ValueRO.sphereSceneMetaEntity);
        if (ssstate == SceneSystem.SceneStreamingState.LoadedSectionEntities)
        {
            //根据 meta entity 加载场景
            SceneSystem.LoadSceneAsync(state.WorldUnmanaged, references.ValueRW.sphereSceneMetaEntity);
        }
    }

    public void OnStartRunning(ref SystemState state)
    {
        var references = SystemAPI.GetSingletonRW<SubscenesReferences>();
        if (references.ValueRO.cubeSceneReference.IsReferenceValid)
        {
            references.ValueRW.cubeSceneMetaEntity =
                SceneSystem.LoadSceneAsync(state.WorldUnmanaged, references.ValueRO.cubeSceneReference);
        }
        if (references.ValueRO.sphereSceneReference.IsReferenceValid)
        {
            references.ValueRW.sphereSceneMetaEntity =
                SceneSystem.LoadSceneAsync(state.WorldUnmanaged, references.ValueRO.sphereSceneReference, 
                new SceneSystem.LoadParameters
                {
                    //不自动加载
                    AutoLoad = false
                });
        }
    }

    public void OnStopRunning(ref SystemState state) {}
}

子场景加载后,会生成两个额外的 entity,即 meta entity 和 section entity

csharp 复制代码
public partial struct SceneSectionsLoadSystem : ISystem
{
    private float timer = 1f;

    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        var sectionEntities = SystemAPI.GetSingletonBuffer<ResolvedSectionEntity>();
        NativeArray<Entity> sectionEntitiesArray = CollectionHelper.CreateNativeArray<Entity>(sectionEntities.Length, Allocator.Temp);
        for (int i = 0; i < sectionEntities.Length; i++)
        {
            sectionEntitiesArray[i] = sectionEntities[i].SectionEntity;
        }

        timer -= SystemAPI.Time.DeltaTime;
        if (timer < 0)
        {
            for (int i = 0; i < sectionEntitiesArray.Length; i++)
            {
                var sectionState = SceneSystem.GetSectionStreamingState(state.WorldUnmanaged, sectionEntitiesArray[i]);
                if (sectionState == SceneSystem.SectionStreamingState.Loaded)
                {
                    state.EntityManager.RemoveComponent<RequestSceneLoaded>(sectionEntitiesArray[i]);
                    if (i == sectionEntitiesArray.Length - 1)
                        timer = 1.0f;
                }
                else if (sectionState == SceneSystem.SectionStreamingState.Unloaded)
                {
                    state.EntityManager.AddComponent<RequestSceneLoaded>(sectionEntitiesArray[i]);
                    if (i == sectionEntitiesArray.Length - 1)
                        timer = 1.0f;
                }
            }
        }
        sectionEntitiesArray.Dispose();
    }
}

section 的动态加载和卸载,实际开发中可以把地图分成不同的 scetion,然后按需加载

Scene section上Entity的交叉引用关系

  • Section 0 里的 Entity 可以被其他 Section 的 Entity 引用
  • 单个 Section 内的 Entity 之间可以彼此引用
  • 除 Section 0 外其他 Section 间的 Entity 彼此是不能被引用的
相关推荐
南無忘码至尊2 小时前
Unity学习90天 - 第 6 天 -学习物理 Material + 重力与阻力并实现弹跳球和冰面滑动效果
学习·unity·游戏引擎
mxwin5 小时前
Unity 单通道立体渲染(Single Pass Instanced)对 Shader 顶点布局的特殊要求
unity·游戏引擎·shader
魔士于安7 小时前
unity 低多边形 无人小村 木质建筑 晾衣架 盆子手推车,桌子椅子,罐子,水井
游戏·unity·游戏引擎·贴图·模型
RReality7 小时前
【Unity Shader URP】简易卡通着色(Simple Toon)实战教程
ui·unity·游戏引擎·图形渲染·材质
魔士于安8 小时前
unity 骷髅人 连招 武器 刀光 扭曲空气
游戏·unity·游戏引擎·贴图·模型
瑞瑞小安10 小时前
Unity功能篇:文本框随文字内容动态调整
ui·unity
南無忘码至尊11 小时前
Unity学习90天-第7天-学习委托与事件(简化版)
学习·unity·游戏引擎
君莫愁。11 小时前
【Unity】解决UGUI的Button无法点击/点击无反应的排查方案
unity·c#·游戏引擎·解决方案·ugui·按钮·button
南無忘码至尊1 天前
Unity学习90天 - 第 6天 - 学习协程 Coroutine并实现每隔 2 秒生成一波敌人
学习·unity·c#·游戏引擎
张老师带你学1 天前
unity 老版本资源迁移,第一人称,完整城市,有出身点房内视图,有gun shop视图,urp
科技·游戏·unity·模型·游戏美术