You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 12:24:59