Unity DOTS子场景实体:PlayerPosition无法更新及Debug.Log失效问题
问题描述
开发包含大量动态雾对象的Unity项目,使用DOTS实现——雾对象为大量小Sprite,玩家靠近时缩小,该功能已正常运行,但需要让雾对象同步玩家位置。尝试直接传入Player的Transform失败,改为传入Vector2类型的PlayerPosition后,发现Authoring脚本中无法更新该Vector2,甚至Debug.Log无输出,怀疑是实体处于子场景或实体特性导致。
附原代码:
FogAuthoring.cs
using System.Collections; using System.Collections.Generic; using Unity.Entities; using Unity.Mathematics; using UnityEngine; public class FogAuthoring : MonoBehaviour { public float LerpSpeed; public float TargetScale; public float2 PlayerPosition; private Transform _player; private void Start() { _player = GameObject.FindWithTag("Player").transform; Debug.Log("A"); } private void Update() { PlayerPosition = new float2(_player.position.x, _player.position.y); Debug.Log("B"); } private class Baker : Baker<FogAuthoring> { public override void Bake(FogAuthoring authoring) { Entity entity = GetEntity(TransformUsageFlags.Dynamic); AddComponent(entity, new FogData { LerpSpeed = authoring.LerpSpeed, TargetScale = authoring.TargetScale, PlayerPosition = authoring.PlayerPosition }); } } } public struct FogData : IComponentData { public float LerpSpeed; public float TargetScale; public float2 PlayerPosition; }
FogControllerDOTS.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using Unity.Burst; using Unity.Entities; using Unity.Transforms; using Unity.Jobs; public partial struct FogControllerDOTS : ISystem { public const float distThreshold = 5; public void OnCreate(ref SystemState state) { state.RequireForUpdate<FogData>(); } [BurstCompile] public void OnUpdate(ref SystemState state) { FogParticleJob fogParticleJob = new FogParticleJob { deltaTime = SystemAPI.Time.DeltaTime }; fogParticleJob.ScheduleParallel(); } [BurstCompile] public partial struct FogParticleJob : IJobEntity { public float deltaTime; public void Execute(ref LocalTransform localTransform, in FogData fogData) { float currentScale = localTransform.Scale; float targetScale; if (Vector2.Distance(new Vector2(localTransform.Position.x, localTransform.Position.y), fogData.PlayerPosition) <= distThreshold) targetScale = Mathf.Lerp(currentScale, 0, fogData.LerpSpeed * deltaTime); else targetScale = Mathf.Lerp(currentScale, fogData.TargetScale, fogData.LerpSpeed * deltaTime); localTransform = localTransform.WithScale(targetScale); } } }
解决方案
问题根源
- Authoring脚本生命周期失效:实体从子场景加载后,原
FogAuthoring所在GameObject会被DOTS处理为实体,MonoBehaviour的Start/Update不再执行——原GameObject可能被销毁或标记为非活跃,导致Debug.Log无输出。 - PlayerPosition无法同步:Baker仅在烘焙阶段将
FogAuthoring.PlayerPosition赋值给FogData,之后MonoBehaviour更新的变量不会同步到ECS组件,两者是独立存储。 - 内存浪费:每个雾对象存储一份玩家位置完全冗余,应共享单一数据源。
修复步骤
1. 创建玩家位置组件与同步系统
为玩家实体添加位置组件,编写同步系统将玩家位置同步到ECS:
// PlayerPositionData.cs using Unity.Entities; using Unity.Mathematics; [UniqueComponent] public struct PlayerPositionData : IComponentData { public float2 Position; } // PlayerPositionAuthoring.cs using Unity.Entities; using Unity.Mathematics; using UnityEngine; public class PlayerPositionAuthoring : MonoBehaviour { private class Baker : Baker<PlayerPositionAuthoring> { public override void Bake(PlayerPositionAuthoring authoring) { Entity entity = GetEntity(TransformUsageFlags.Dynamic); AddComponent(entity, new PlayerPositionData { Position = new float2(authoring.transform.position.x, authoring.transform.position.y) }); } } } // PlayerPositionSyncSystem.cs using Unity.Burst; using Unity.Entities; using Unity.Transforms; [BurstCompile] public partial struct PlayerPositionSyncSystem : ISystem { public void OnCreate(ref SystemState state) { state.RequireForUpdate<PlayerPositionData>(); state.RequireForUpdate<LocalTransform>(); } [BurstCompile] public void OnUpdate(ref SystemState state) { foreach ((RefRW<PlayerPositionData> posData, RefRO<LocalTransform> transform) in SystemAPI.Query<RefRW<PlayerPositionData>, RefRO<LocalTransform>>()) { posData.ValueRW.Position = new float2(transform.ValueRO.Position.x, transform.ValueRO.Position.y); } } }
- 将
PlayerPositionAuthoring挂载到玩家GameObject,烘焙后玩家实体将拥有PlayerPositionData组件。 PlayerPositionSyncSystem每帧同步玩家Transform位置到ECS组件。
2. 修改雾对象组件与系统
移除FogData中的PlayerPosition字段,在雾系统中共享玩家位置:
// 修改后的FogData public struct FogData : IComponentData { public float LerpSpeed; public float TargetScale; } // 修改后的FogAuthoring.cs using Unity.Entities; using UnityEngine; public class FogAuthoring : MonoBehaviour { public float LerpSpeed; public float TargetScale; private class Baker : Baker<FogAuthoring> { public override void Bake(FogAuthoring authoring) { Entity entity = GetEntity(TransformUsageFlags.Dynamic); AddComponent(entity, new FogData { LerpSpeed = authoring.LerpSpeed, TargetScale = authoring.TargetScale }); } } } // 修改后的FogControllerDOTS.cs using Unity.Burst; using Unity.Entities; using Unity.Transforms; using Unity.Mathematics; public partial struct FogControllerDOTS : ISystem { public const float distThreshold = 5; public void OnCreate(ref SystemState state) { state.RequireForUpdate<FogData>(); state.RequireForUpdate<PlayerPositionData>(); } [BurstCompile] public void OnUpdate(ref SystemState state) { PlayerPositionData playerPos = SystemAPI.GetSingleton<PlayerPositionData>(); FogParticleJob fogParticleJob = new FogParticleJob { deltaTime = SystemAPI.Time.DeltaTime, playerPosition = playerPos.Position }; fogParticleJob.ScheduleParallel(); } [BurstCompile] public partial struct FogParticleJob : IJobEntity { public float deltaTime; public float2 playerPosition; public void Execute(ref LocalTransform localTransform, in FogData fogData) { float currentScale = localTransform.Scale; float targetScale; float2 fogPos = new float2(localTransform.Position.x, localTransform.Position.y); if (math.distance(fogPos, playerPosition) <= distThreshold) targetScale = math.lerp(currentScale, 0, fogData.LerpSpeed * deltaTime); else targetScale = math.lerp(currentScale, fogData.TargetScale, fogData.LerpSpeed * deltaTime); localTransform = localTransform.WithScale(targetScale); } } }
- 移除
FogAuthoring中无效的生命周期方法和冗余变量。 - 通过
SystemAPI.GetSingleton获取全局玩家位置,传递给Job。 - 用
math.distance替代Vector2.Distance,适配Burst编译提升性能。
额外注意事项
- 若玩家未用ECS实体管理,可修改
PlayerPositionSyncSystem直接查找玩家Transform同步(不推荐,尽量统一用ECS):[BurstCompile(CompileSynchronously = true)] public partial struct PlayerPositionSyncSystem : ISystem { private Transform _playerTransform; public void OnCreate(ref SystemState state) { _playerTransform = GameObject.FindWithTag("Player").transform; state.RequireForUpdate<PlayerPositionData>(); } [BurstCompile] public void OnUpdate(ref SystemState state) { var playerPos = SystemAPI.GetSingletonRW<PlayerPositionData>().ValueRW; playerPos.Position = new float2(_playerTransform.position.x, _playerTransform.position.y); } }
内容的提问来源于stack exchange,提问作者Orion TK
相关产品推荐
相关产品推荐

