Unity ECS中实体间属性设置与访问的性能优化咨询
针对ECS AI目标缓存与性能优化的解决方案
核心思路:用ECS原生组件+结构化更新替代BlobArray缓存
BlobArray是只读不可变结构,完全不适合存储需实时更新的Transform数据。我们需要通过NativeArray结合ECS系统的结构化更新实现高效的目标位置缓存,同时兼容Burst编译器与Job系统。
问题1:BlobArray无法动态更新LocalTransform
BlobAsset的设计初衷是存储初始化后不再修改的只读数据,因此直接放弃用它存储动态位置,改用单例组件托管的NativeArray,通过专用系统定期同步实体位置到数组中。
问题2:Job中无法传入BlobArray更新
BlobArray的指针特性导致无法安全传入Job,且本身不支持修改。解决方式如下:
- 创建单例缓存组件,存储人类实体的位置与对应实体引用;
- 编写后台Job同步位置数据,完全兼容Burst编译。
示例代码结构:
// 单例缓存组件:存储人类实体的位置与实体关联 public struct HumanPositionCache : IComponentData { public NativeArray<LocalTransform> Positions; public NativeArray<Entity> Entities; } // 人类实体标记组件 public struct HumanTag : IComponentData {} // 定期更新位置缓存的系统 [BurstCompile] public partial struct HumanPositionUpdateSystem : ISystem { private EntityQuery _humanQuery; public void OnCreate(ref SystemState state) { _humanQuery = state.GetEntityQuery(ComponentType.ReadOnly<HumanTag>(), ComponentType.ReadOnly<LocalTransform>()); // 创建单例缓存实体 var cacheEntity = state.EntityManager.CreateEntity(); state.EntityManager.AddComponentData(cacheEntity, new HumanPositionCache()); state.RequireForUpdate<HumanPositionCache>(); } [BurstCompile] public void OnUpdate(ref SystemState state) { var cacheEntity = SystemAPI.GetSingletonEntity<HumanPositionCache>(); var cache = state.EntityManager.GetComponentData<HumanPositionCache>(cacheEntity); var humanCount = _humanQuery.CalculateEntityCount(); // 动态调整NativeArray大小以匹配当前人类实体数量 if (!cache.Positions.IsCreated || cache.Positions.Length != humanCount) { cache.Positions.Dispose(); cache.Entities.Dispose(); cache.Positions = new NativeArray<LocalTransform>(humanCount, Allocator.Persistent); cache.Entities = new NativeArray<Entity>(humanCount, Allocator.Persistent); state.EntityManager.SetComponentData(cacheEntity, cache); } // 执行后台Job同步位置数据 var job = new UpdateHumanPositionJob { Positions = cache.Positions, Entities = cache.Entities, TransformLookup = state.GetComponentLookup<LocalTransform>(true) }; state.Dependency = job.ScheduleParallel(_humanQuery, state.Dependency); } public void OnDestroy(ref SystemState state) { var cache = SystemAPI.GetSingleton<HumanPositionCache>(); cache.Positions.Dispose(); cache.Entities.Dispose(); } [BurstCompile] private struct UpdateHumanPositionJob : IJobEntity { public NativeArray<LocalTransform> Positions; public NativeArray<Entity> Entities; [ReadOnly] public ComponentLookup<LocalTransform> TransformLookup; public void Execute(Entity entity, [IndexInQuery] int index) { Positions[index] = TransformLookup[entity]; Entities[index] = entity; } } }
问题3:SystemAPI.Query无法传入Job
SystemAPI.Query是主线程查询API,不能直接传入Job,但可以通过EntityQuery结合IJobEntity/IJobChunk实现Job内的实体遍历。AI目标选择逻辑可直接基于缓存的NativeArray实现,避免双重循环:
示例:AI寻找最近目标的Job
[BurstCompile] public partial struct AiFindTargetSystem : ISystem { private EntityQuery _aiQuery; public void OnCreate(ref SystemState state) { _aiQuery = state.GetEntityQuery(ComponentType.ReadWrite<AiState>(), ComponentType.ReadOnly<LocalTransform>()); state.RequireForUpdate<HumanPositionCache>(); } [BurstCompile] public void OnUpdate(ref SystemState state) { var cache = SystemAPI.GetSingleton<HumanPositionCache>(); if (!cache.Positions.IsCreated || cache.Positions.Length == 0) return; var job = new AiFindTargetJob { HumanPositions = cache.Positions, HumanEntities = cache.Entities, AiTransformLookup = state.GetComponentLookup<LocalTransform>(true) }; state.Dependency = job.ScheduleParallel(_aiQuery, state.Dependency); } [BurstCompile] private struct AiFindTargetJob : IJobEntity { [ReadOnly] public NativeArray<LocalTransform> HumanPositions; [ReadOnly] public NativeArray<Entity> HumanEntities; [ReadOnly] public ComponentLookup<LocalTransform> AiTransformLookup; public void Execute(ref AiState aiState, Entity aiEntity) { if (aiState.CurrentState != AiStateType.Idle) return; var aiPos = AiTransformLookup[aiEntity].Position; float closestDistance = float.MaxValue; int closestIndex = -1; // 遍历缓存数组快速查找最近目标 for (int i = 0; i < HumanPositions.Length; i++) { var distance = math.distance(aiPos, HumanPositions[i].Position); if (distance < closestDistance) { closestDistance = distance; closestIndex = i; } } if (closestIndex != -1) { aiState.TargetEntity = HumanEntities[closestIndex]; aiState.TargetIndex = closestIndex; // 存储索引用于后续快速取位置 aiState.CurrentState = AiStateType.Chase; } } } } // AI状态组件示例 public struct AiState : IComponentData { public AiStateType CurrentState; public Entity TargetEntity; public int TargetIndex; } public enum AiStateType { Idle, Chase, Attack }
问题4:单例C#类的NativeArray与Burst不兼容
独立C#单例的内存管理无法被Burst安全识别,改用ECS单例组件(如HumanPositionCache)即可解决:这类组件由Unity实体管理器托管内存,可通过SystemAPI.GetSingleton在Job中安全访问,完全兼容Burst编译。
额外优化建议
- 为人类实体添加
ChangeFilter<LocalTransform>,让位置更新系统只处理位置发生变化的实体,减少无效计算; - 若目标实体可能被销毁,需在AI目标选择Job中添加
EntityManager.Exists检查,或在位置缓存系统中清理无效实体索引; - 针对大规模实体场景,可引入**空间划分(如网格分区)**进一步优化目标查找,避免遍历整个数组。
内容的提问来源于stack exchange,提问作者Uğur Tuna Koca
相关产品推荐
相关产品推荐

