为何Unity中C#传统方式与ECS实现AI寻路性能相近?
Unity ECS 与传统C#实现AI逻辑的性能测试
我分别采用Unity ECS架构与C#传统方式实现了同一套简单AI逻辑:立方体处于Idle状态时,会寻找最近的立方体作为目标并跟随,到达目标后切换回Idle状态,再寻找新的目标。测试后发现二者性能表现相近,帧率均约为70fps。
核心实现代码
传统C#实现
public class SpawnCubes : MonoBehaviour { public GameObject prefab; public float radius; // Start is called before the first frame update public CubeClass[] cubes; public float speed, stoppingDistance; void Start() { int count = 2000; cubes = new CubeClass[count]; int id = 0; for(int i=0; i<count; i++) { Vector3 spawnVec = new Vector3(Random.Range(-radius, radius), 0, Random.Range(-radius, radius)); Transform t = Instantiate(prefab, spawnVec, Quaternion.identity).transform; CubeClass c = new CubeClass(); c.id = id; c.self = t; cubes[i] = c; id++; } } private void Update() { for(int i=0; i<cubes.Length; i++) { CubeClass cube = cubes[i]; if(cube.state == HumanState.Idle) { float distance = 9999999f; int closest = -1; for (int k = 0; k < cubes.Length; k++) { CubeClass cube2 = cubes[k]; float d = Vector3.SqrMagnitude(cube.self.position- cube2.self.position); if (d < distance && d > stoppingDistance * 2f) { distance = d; closest = cube2.id; } } if(closest != -1) { cube.targetId = closest; cube.state = HumanState.Chasing; } } else { for (int k = 0; k < cubes.Length; k++) { CubeClass cube2 = cubes[k]; if(cube2.id == cube.targetId) { Vector3 dir = cube2.self.position - cube.self.position; if(dir.sqrMagnitude < stoppingDistance) { cube.state = HumanState.Idle; break; } dir = dir.normalized; cube.self.position += dir * Time.deltaTime * speed; break; } } } } } } public class CubeClass { public HumanState state; public Transform self; public int id; public int targetId; }
Unity ECS实现
public partial struct HumanMoveSystem : ISystem { [BurstCompile] public void OnCreate(ref SystemState state) { state.RequireForUpdate<HumanProperties>(); } [BurstCompile] public void OnDestroy(ref SystemState state) { } [BurstCompile] public void OnUpdate(ref SystemState state) { Entity humanP = SystemAPI.GetSingletonEntity<HumanProperties>(); GameStateAspect gameStateAspect = SystemAPI.GetAspect<GameStateAspect>(humanP); GameStateType type = gameStateAspect.Value; if (type != GameStateType.Started) { return; } float deltaTime = SystemAPI.Time.DeltaTime; NativeList<float3> aspects = new NativeList<float3>(Allocator.Persistent); NativeList<int> ids = new NativeList<int>(Allocator.Persistent); foreach (var human in SystemAPI.Query<HumanActiveAspect>()) { aspects.Add(human.transform.ValueRO.Position); ids.Add(human.stats.ValueRO.id); } MoveJob job = new MoveJob(); job.aspects = aspects; job.ids = ids; job.deltaTime = deltaTime; job.Schedule(); } [BurstCompile] public partial struct MoveJob : IJobEntity { public float deltaTime; public NativeList<float3> aspects; public NativeList<int> ids; void Execute(HumanActiveAspect aspect) { if (aspect.Value != HumanState.Chasing) return; for (int i = 0; i < aspects.Length; i++) { if (ids[i] == aspect.stats.ValueRO.targetID) { if(aspect.Move(deltaTime, aspects[i])) { aspect.stats.ValueRW.state = HumanState.Idle; } } } } } [BurstCompile] public partial struct HumanIdleSystem : ISystem { [BurstCompile] public void OnCreate(ref SystemState state) { state.RequireForUpdate<HumanProperties>(); } [BurstCompile] public void OnDestroy(ref SystemState state) { } [BurstCompile] public void OnUpdate(ref SystemState state) { Entity humanP = SystemAPI.GetSingletonEntity<HumanProperties>(); GameStateAspect gameStateAspect = SystemAPI.GetAspect<GameStateAspect>(humanP); GameStateType type = gameStateAspect.Value; if (type != GameStateType.Started) { return; } float deltaTime = SystemAPI.Time.DeltaTime; NativeList<float3> aspects = new NativeList<float3>(Allocator.Persistent); NativeList<int> ids = new NativeList<int>(Allocator.Persistent); foreach (var human in SystemAPI.Query<HumanActiveAspect>()) { aspects.Add(human.transform.ValueRO.Position); ids.Add(human.stats.ValueRO.id); } HumanChooseTargetJob job = new HumanChooseTargetJob(); job.ids = ids; job.deltaTime = deltaTime; job.aspects = aspects; job.Schedule(); } [BurstCompile] public partial struct HumanChooseTargetJob : IJobEntity { public float deltaTime; public NativeList<float3> aspects; public NativeList<int> ids; void Execute(HumanActiveAspect aspect) { if (aspect.Value != HumanState.Idle) return; int closest = -1; float distance = 99999999f; for(int i=0; i<aspects.Length; i++) { float3 dir = aspect.transform.ValueRO.Position - aspects[i]; float d = math.lengthsq(dir); if (d < distance && d > aspect.stats.ValueRO.stopDistance * 2f) { distance = d; closest = ids[i]; } } if (closest == -1) return; aspect.SetTarget(closest); aspect.stats.ValueRW.state = HumanState.Chasing; } } } public readonly partial struct HumanActiveAspect : IAspect { public readonly Entity entity; public readonly RefRW<LocalTransform> transform; public readonly RefRW<HumanStats> stats; public HumanState Value => stats.ValueRO.state; public int ID => stats.ValueRO.id; public float ReturnDistance(float3 pos) { return math.distance(pos, transform.ValueRO.Position); } public bool Move(float deltaTime, float3 target) { float3 direction = target - transform.ValueRW.Position; float magnitude = math.lengthsq(direction); if (magnitude < stats.ValueRW.stopDistance) return true; transform.ValueRW.Position += direction * deltaTime * stats.ValueRW.moveSpeed / magnitude; return false; } public void SetTarget(int id) { stats.ValueRW.targetID = id; } } public enum HumanState { Idle, Chasing, Attacking, None } public struct HumanStats : IComponentData { public HumanState state; public int health, maxHealth; public float moveSpeed; public float stopDistance; public int targetID; public int id; }
内容的提问来源于stack exchange,提问作者Uğur Tuna Koca
相关产品推荐
相关产品推荐

