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 彼此是不能被引用的
相关推荐
牙膏上的小苏打233313 分钟前
Unity Surround开关后导致获取主显示器分辨率错误
unity·主屏幕
Unity大海2 小时前
诠视科技Unity SDK开发环境配置、项目设置、apk打包。
科技·unity·游戏引擎
浅陌sss8 小时前
Unity中 粒子系统使用整理(一)
unity·游戏引擎
维度攻城狮12 小时前
实现在Unity3D中仿真汽车,而且还能使用ros2控制
python·unity·docker·汽车·ros2·rviz2
为你写首诗ge15 小时前
【Unity网络编程知识】FTP学习
网络·unity
神码编程17 小时前
【Unity】 HTFramework框架(六十四)SaveDataRuntime运行时保存组件参数、预制体
unity·编辑器·游戏引擎
菲fay19 小时前
Unity 单例模式写法
unity·单例模式
火一线20 小时前
【Framework-Client系列】UIGenerate介绍
游戏·unity
ZKY_2421 小时前
【工具】Json在线解析工具
unity·json
ZKY_241 天前
【Unity】处理文字显示不全的问题
unity·游戏引擎