【Unity】十万人同屏寻路? 基于Dots技术的多线程RVO2避障

效果见视频:

Unity Dots 10万人同屏RVO避障是一种什么体验? 3D人物带动画,不使用ECS

不使用Entities(ECS),只使用Batch Renderer Group、Job System和Burst加速,实现万人同屏RVO避障。

前面博文中尝试过使用传统多线程RVO避障,PC端5000人帧数100多帧:【Unity】万人同屏, 从入门到放弃之——多线程RVO避障_TopGames的博客-CSDN博客

RVO是算力开销大头,能不能用炸裂的Burst + Job System并行计算RVO算法呢?但是要想把RVO2替换为Job System实现也绝非易事,因为JobSystem限制非常多,用来传递处理数据的NativeContainer只允许值类型,好在github上有国外大佬早已做过相同的事。

RVO2 JobSystem实现:https://github.com/Nebukam/com.nebukam.orca

 由于原版API过时,下面是我修改适配最新dots插件的版本:com.nebukam.orca: https://github.com/Nebukam/com.nebukam.orca修改适配Unity 2022.3最新的dots以及其它改动

插件依赖Burst + Job System,安装URP会自动依赖这些库,除此之外还依赖两个包:

1.GitHub - Nebukam/com.nebukam.common 

2. com.nebukam.job-assist: https://github.com/Nebukam/com.nebukam.job-assist

使用Unity PackageManager从github地址添加即可。

使用方法:

1. 初始化

            agents = new AgentGroup<Agent>();
            obstacles = new ObstacleGroup();
            
            simulation = new ORCA();
            simulation.plane = axis; //设置XY方向寻路还是XZ方向
            simulation.agents = agents;
            simulation.staticObstacles = obstacles;

 2. 在Update中执行JobSystem Schedule:
 

        private void Update()
        {
            //Schedule the simulation job. 
            simulation.Schedule(Time.deltaTime);
        }

3. 在LateUpdate中把RVO计算位置/选装结果设置给物体Transform:

if (m_orca.TryComplete())
        {
            if (m_AutoUpdateEntityTrans)
            {
                RVOAgent agent;
                for (int i = 0; i < m_agents.Count; i++)
                {
                    agent = m_agents[i];
                    agent.CachedTransform.position = agent.pos;
                    if (agent.IsMoving)
                    {
                        agent.CachedTransform.rotation = agent.rotation;
                    }
                }
            }
        }

使用URP默认渲染 + Burst加速的Jobs RVO后,5千数量级帧数比之前多线程提高了20帧左右。

使用自定义Batch Renderer Group合批:

主要是获取所有RVO Agent的位置和方向,使用JobSystem并行把这些位置和方向信息写入Batch Renderer Group的矩阵列表,这样就完成了并行将RVO的位置/方向同步到合批渲染的海量物体:

using Nebukam.ORCA;
using System;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;
using UnityEngine.Rendering;
namespace BRG
{
    public class PlayerRenderGroup : MonoBehaviour
    {
        public Mesh mesh;
        public Material material;
        public ORCASetupRing orcaTest;
        private BatchRendererGroup m_BRG;

        private GraphicsBuffer m_InstanceData;
        private BatchID m_BatchID;
        private BatchMeshID m_MeshID;
        private BatchMaterialID m_MaterialID;

        // Some helper constants to make calculations more convenient.
        private const int kSizeOfMatrix = sizeof(float) * 4 * 4;
        private const int kSizeOfPackedMatrix = sizeof(float) * 3 * 4;
        private const int kSizeOfFloat4 = sizeof(float) * 4;
        private const int kBytesPerInstance = (kSizeOfPackedMatrix * 2) + kSizeOfFloat4;
        private const int kExtraBytes = kSizeOfMatrix * 2;
        [SerializeField] private int kNumInstances = 20000;
        [SerializeField] private int m_RowCount = 200;
        private NativeArray<float4x4> matrices;
        private NativeArray<float3x4> objectToWorldMatrices;
        private NativeArray<float3x4> worldToObjectMatrices;
        private float4[] colors;

        private void Start()
        {
            m_BRG = new BatchRendererGroup(this.OnPerformCulling, IntPtr.Zero);
            m_MeshID = m_BRG.RegisterMesh(mesh);
            m_MaterialID = m_BRG.RegisterMaterial(material);
            AllocateInstanceDateBuffer();
            PopulateInstanceDataBuffer();
        }

        private uint byteAddressObjectToWorld;
        private uint byteAddressWorldToObject;
        private uint byteAddressColor;

        private void Update()
        {
            var agents = orcaTest.GetAgents();
            NativeArray<float3> tempPosArr = new NativeArray<float3>(matrices.Length, Allocator.TempJob);
            NativeArray<quaternion> tempRotaArr = new NativeArray<quaternion>(matrices.Length, Allocator.TempJob);
            for (int i = 0; i < agents.Count; i++)
            {
                var agent = agents[i];
                tempPosArr[i] = agent.pos;
                tempRotaArr[i] = agent.rotation;
            }
            var matricesJob = new UpdateRendererMatricesJob()
            {
                positionArr = tempPosArr,
                rotationArr = tempRotaArr,
                matrices = matrices,
                obj2WorldArr = objectToWorldMatrices,
                world2ObjArr = worldToObjectMatrices
            };
            var jobHandle = matricesJob.Schedule(matrices.Length, 64);
            jobHandle.Complete();
            matrices = matricesJob.matrices;
            objectToWorldMatrices = matricesJob.obj2WorldArr;
            worldToObjectMatrices = matricesJob.world2ObjArr;
            RefreshData();
            tempPosArr.Dispose();
            tempRotaArr.Dispose();
        }

        private void AllocateInstanceDateBuffer()
        {
            m_InstanceData = new GraphicsBuffer(GraphicsBuffer.Target.Raw,
                BufferCountForInstances(kBytesPerInstance, kNumInstances, kExtraBytes),
                sizeof(int));
        }
        private void RefreshData()
        {
            m_InstanceData.SetData(objectToWorldMatrices, 0, (int)(byteAddressObjectToWorld / kSizeOfPackedMatrix), objectToWorldMatrices.Length);
            m_InstanceData.SetData(worldToObjectMatrices, 0, (int)(byteAddressWorldToObject / kSizeOfPackedMatrix), worldToObjectMatrices.Length);
        }
        public float3x4 ConvertFloat4x4To3x4(float4x4 matrix)
        {
            return new float3x4(matrix.c0.xyz, matrix.c1.xyz, matrix.c2.xyz, matrix.c3.xyz);
        }
        private void PopulateInstanceDataBuffer()
        {
            // Place a zero matrix at the start of the instance data buffer, so loads from address 0 return zero.
            var zero = new Matrix4x4[1] { Matrix4x4.zero };

            // Create transform matrices for three example instances.
            matrices = new NativeArray<float4x4>(kNumInstances, Allocator.Persistent);
            // Convert the transform matrices into the packed format that shaders expects.
            objectToWorldMatrices = new NativeArray<float3x4>(kNumInstances, Allocator.Persistent);
            // Also create packed inverse matrices.
            worldToObjectMatrices = new NativeArray<float3x4>(kNumInstances, Allocator.Persistent);
            colors = orcaTest.AgentColors;
            var offset = new Vector3(m_RowCount, 0, Mathf.CeilToInt(kNumInstances / (float)m_RowCount)) * 0.5f;
            for (int i = 0; i < kNumInstances; i++)
            {
                matrices[i] = Matrix4x4.Translate(new Vector3(i % m_RowCount, 0, i / m_RowCount) - offset);
                objectToWorldMatrices[i] = ConvertFloat4x4To3x4(matrices[i]);
                worldToObjectMatrices[i] = ConvertFloat4x4To3x4(Unity.Mathematics.math.inverse(matrices[0]));
                //colors[i] = orcaTest.AgentColors[i];
            }

            byteAddressObjectToWorld = kSizeOfPackedMatrix * 2;
            byteAddressWorldToObject = (uint)(byteAddressObjectToWorld + kSizeOfPackedMatrix * kNumInstances);
            byteAddressColor = (uint)(byteAddressWorldToObject + kSizeOfPackedMatrix * kNumInstances);

            // Upload the instance data to the GraphicsBuffer so the shader can load them.
            m_InstanceData.SetData(zero, 0, 0, 1);
            m_InstanceData.SetData(objectToWorldMatrices, 0, (int)(byteAddressObjectToWorld / kSizeOfPackedMatrix), objectToWorldMatrices.Length);
            m_InstanceData.SetData(worldToObjectMatrices, 0, (int)(byteAddressWorldToObject / kSizeOfPackedMatrix), worldToObjectMatrices.Length);
            m_InstanceData.SetData(colors, 0, (int)(byteAddressColor / kSizeOfFloat4), colors.Length);

            var metadata = new NativeArray<MetadataValue>(3, Allocator.Temp);
            metadata[0] = new MetadataValue { NameID = Shader.PropertyToID("unity_ObjectToWorld"), Value = 0x80000000 | byteAddressObjectToWorld, };
            metadata[1] = new MetadataValue { NameID = Shader.PropertyToID("unity_WorldToObject"), Value = 0x80000000 | byteAddressWorldToObject, };
            metadata[2] = new MetadataValue { NameID = Shader.PropertyToID("_BaseColor"), Value = 0x80000000 | byteAddressColor, };

            m_BatchID = m_BRG.AddBatch(metadata, m_InstanceData.bufferHandle);
        }

        int BufferCountForInstances(int bytesPerInstance, int numInstances, int extraBytes = 0)
        {
            // Round byte counts to int multiples
            bytesPerInstance = (bytesPerInstance + sizeof(int) - 1) / sizeof(int) * sizeof(int);
            extraBytes = (extraBytes + sizeof(int) - 1) / sizeof(int) * sizeof(int);
            int totalBytes = bytesPerInstance * numInstances + extraBytes;
            return totalBytes / sizeof(int);
        }

        private void OnDestroy()
        {
            m_BRG.Dispose();
            matrices.Dispose();
            objectToWorldMatrices.Dispose();
            worldToObjectMatrices.Dispose();
            //colors.Dispose();
        }

        public unsafe JobHandle OnPerformCulling(
            BatchRendererGroup rendererGroup,
            BatchCullingContext cullingContext,
            BatchCullingOutput cullingOutput,
            IntPtr userContext)
        {

            int alignment = UnsafeUtility.AlignOf<long>();

            var drawCommands = (BatchCullingOutputDrawCommands*)cullingOutput.drawCommands.GetUnsafePtr();
            drawCommands->drawCommands = (BatchDrawCommand*)UnsafeUtility.Malloc(UnsafeUtility.SizeOf<BatchDrawCommand>(), alignment, Allocator.TempJob);
            drawCommands->drawRanges = (BatchDrawRange*)UnsafeUtility.Malloc(UnsafeUtility.SizeOf<BatchDrawRange>(), alignment, Allocator.TempJob);
            drawCommands->visibleInstances = (int*)UnsafeUtility.Malloc(kNumInstances * sizeof(int), alignment, Allocator.TempJob);
            drawCommands->drawCommandPickingInstanceIDs = null;

            drawCommands->drawCommandCount = 1;
            drawCommands->drawRangeCount = 1;
            drawCommands->visibleInstanceCount = kNumInstances;

            drawCommands->instanceSortingPositions = null;
            drawCommands->instanceSortingPositionFloatCount = 0;

            drawCommands->drawCommands[0].visibleOffset = 0;
            drawCommands->drawCommands[0].visibleCount = (uint)kNumInstances;
            drawCommands->drawCommands[0].batchID = m_BatchID;
            drawCommands->drawCommands[0].materialID = m_MaterialID;
            drawCommands->drawCommands[0].meshID = m_MeshID;
            drawCommands->drawCommands[0].submeshIndex = 0;
            drawCommands->drawCommands[0].splitVisibilityMask = 0xff;
            drawCommands->drawCommands[0].flags = 0;
            drawCommands->drawCommands[0].sortingPosition = 0;

            drawCommands->drawRanges[0].drawCommandsBegin = 0;
            drawCommands->drawRanges[0].drawCommandsCount = 1;

            drawCommands->drawRanges[0].filterSettings = new BatchFilterSettings { renderingLayerMask = 0xffffffff, };

            for (int i = 0; i < kNumInstances; ++i)
                drawCommands->visibleInstances[i] = i;
            return new JobHandle();
        }
    }
    [BurstCompile]
    partial struct UpdateRendererMatricesJob : IJobParallelFor
    {
        [ReadOnly]
        public NativeArray<float3> positionArr;
        [ReadOnly]
        public NativeArray<quaternion> rotationArr;
        public NativeArray<float4x4> matrices;
        public NativeArray<float3x4> obj2WorldArr;
        public NativeArray<float3x4> world2ObjArr;
        [BurstCompile]
        public void Execute(int index)
        {
            var mat = matrices[index];
            var rotation = rotationArr[index];
            if (rotation.Equals(quaternion.identity))
            {
                rotation = mat.Rotation();
            }
            mat = float4x4.TRS(positionArr[index], rotation, mat.Scale());
            matrices[index] = mat;
            obj2WorldArr[index] = ConvertFloat4x4To3x4(mat);
            world2ObjArr[index] = ConvertFloat4x4To3x4(math.inverse(mat));
        }
        public float3x4 ConvertFloat4x4To3x4(float4x4 matrix)
        {
            return new float3x4(matrix.c0.xyz, matrix.c1.xyz, matrix.c2.xyz, matrix.c3.xyz);
        }
    }
}

创建海量RVO Agent测试代码:


using Nebukam.Common;
#if UNITY_EDITOR
using Nebukam.Common.Editor;
#endif
using System.Collections.Generic;
using TMPro;
using Unity.Jobs.LowLevel.Unsafe;
using Unity.Mathematics;
using UnityEngine;
using static Unity.Mathematics.math;
using Random = UnityEngine.Random;

namespace Nebukam.ORCA
{
    public class ORCASetupRing : MonoBehaviour
    {

        private AgentGroup<Agent> agents;
        private ObstacleGroup obstacles;
        private ObstacleGroup dynObstacles;
        private RaycastGroup raycasts;
        private ORCA simulation;

        [Header("Settings")]
        public int seed = 12345;
        public Texture2D shapeTex;
        public float shapeSize = 1f;
        public TextMeshProUGUI workerCountText;
        public TextMeshProUGUI agentCountText;
        public Transform target;
        public GameObject prefab;
        public AxisPair axis = AxisPair.XY;

        [Header("Agents")]
        public int agentCount = 50;
        public float maxAgentRadius = 2f;
        public bool uniqueRadius = false;
        public float maxSpeed = 1f;
        public float minSpeed = 1f;

        [Header("Obstacles")]
        public int obstacleCount = 100;
        public int dynObstacleCount = 20;
        public float maxObstacleRadius = 2f;
        public int minObstacleEdgeCount = 2;
        public int maxObstacleEdgeCount = 2;

        [Header("Debug")]
        Color staticObstacleColor = Color.red;
        Color dynObstacleColor = Color.yellow;

        [Header("Raycasts")]
        public int raycastCount = 50;
        public float raycastDistance = 10f;
        private float4[] m_Colors;
        public float4[] AgentColors => m_Colors;
#if UNITY_EDITOR
        private void OnDrawGizmos()
        {
            if (agents != null)
            {
                for (int i = 0; i < agents.Count; i++)
                {
                    var agent = agents[i];
                    var agentPos = agent.pos;
                    //Agent body
                    Color bodyColor = i % 3 == 0 ? Color.red : Color.green;

                    if (axis == AxisPair.XY)
                    {
                        Draw.Circle2D(agentPos, agent.radius, bodyColor, 12);
                        Draw.Circle2D(agentPos, agent.radiusObst, Color.cyan.A(0.15f), 12);
                    }
                    else
                    {
                        Draw.Circle(agentPos, agent.radius, bodyColor, 12);
                        Draw.Circle(agentPos, agent.radiusObst, Color.cyan.A(0.15f), 12);

                    }
                    //Agent simulated velocity (ORCA compliant)
                    Draw.Line(agentPos, agentPos + (normalize(agent.velocity) * agent.radius), Color.green);
                    //Agent goal vector
                    Draw.Line(agentPos, agentPos + (normalize(agent.prefVelocity) * agent.radius), Color.grey);
                }
            }

            if (obstacles != null)
            {
                //Draw static obstacles
                Obstacle o;
                int oCount = obstacles.Count, subCount;
                for (int i = 0; i < oCount; i++)
                {
                    o = obstacles[i];
                    subCount = o.Count;

                    //Draw each segment
                    for (int j = 1, count = o.Count; j < count; j++)
                    {
                        Draw.Line(o[j - 1].pos, o[j].pos, staticObstacleColor);
                    }
                    //Draw closing segment (simulation consider 2+ segments to be closed.)
                    if (!o.edge)
                        Draw.Line(o[subCount - 1].pos, o[0].pos, staticObstacleColor);
                }

                if (dynObstacles != null)
                {
                    float delta = Time.deltaTime * 50f;

                    //Draw dynamic obstacles
                    oCount = dynObstacles.Count;
                    for (int i = 0; i < oCount; i++)
                    {
                        o = dynObstacles[i];
                        subCount = o.Count;

                        //Draw each segment
                        for (int j = 1, count = o.Count; j < count; j++)
                        {
                            Draw.Line(o[j - 1].pos, o[j].pos, dynObstacleColor);
                        }
                        //Draw closing segment (simulation consider 2+ segments to be closed.)
                        if (!o.edge)
                            Draw.Line(o[subCount - 1].pos, o[0].pos, dynObstacleColor);

                    }
                }
            }
            if (raycasts != null)
            {
                Raycast r;
                float rad = 0.2f;
                for (int i = 0, count = raycasts.Count; i < count; i++)
                {
                    r = raycasts[i] as Raycast;
                    Draw.Circle2D(r.pos, rad, Color.white, 3);
                    if (r.anyHit)
                    {
                        Draw.Line(r.pos, r.pos + r.dir * r.distance, Color.white.A(0.5f));

                        if (axis == AxisPair.XY)
                        {
                            if (r.obstacleHit != null) { Draw.Circle2D(r.obstacleHitLocation, rad, Color.cyan, 3); }
                            if (r.agentHit != null) { Draw.Circle2D(r.agentHitLocation, rad, Color.magenta, 3); }
                        }
                        else
                        {
                            if (r.obstacleHit != null) { Draw.Circle(r.obstacleHitLocation, rad, Color.cyan, 3); }
                            if (r.agentHit != null) { Draw.Circle(r.agentHitLocation, rad, Color.magenta, 3); }
                        }

                    }
                    else
                    {
                        Draw.Line(r.pos, r.pos + r.dir * r.distance, Color.blue.A(0.5f));
                    }
                }
            }
        }
#endif
        private void Start()
        {
            Application.targetFrameRate = -1;
            agents = new AgentGroup<Agent>();
            obstacles = new ObstacleGroup();
            dynObstacles = new ObstacleGroup();
            raycasts = new RaycastGroup();
            m_Colors = new float4[agentCount];
            simulation = new ORCA();
            simulation.plane = axis;
            simulation.agents = agents;
            simulation.staticObstacles = obstacles;
            simulation.dynamicObstacles = dynObstacles;
            simulation.raycasts = raycasts;

            agentCountText.text = $"Agent Count:{agentCount}";
            workerCountText.text = $"JobWorkerCount:{JobsUtility.JobWorkerCount}";

            float radius = ((agentCount * (maxAgentRadius * 2f)) / PI) * 0.02f;
            Random.InitState(seed);

            #region create obstacles

            float dirRange = 2f;
            List<float3> vList = new List<float3>();
            Obstacle o;
            for (int i = 0; i < obstacleCount; i++)
            {
                int vCount = Random.Range(minObstacleEdgeCount, maxObstacleEdgeCount);
                vList.Clear();
                vList.Capacity = vCount;

                //build branch-like obstacle

                float3 start = float3(Random.Range(-radius, radius), Random.Range(-radius, radius), 0f),
                    pt = start,
                    dir = float3(Random.Range(-dirRange, dirRange), Random.Range(-dirRange, dirRange), 0f);

                if (axis == AxisPair.XZ)
                {
                    pt = start = float3(start.x, 0f, start.y);
                    dir = float3(dir.x, 0f, dir.y);
                }

                vList.Add(start);
                vCount--;

                for (int j = 0; j < vCount; j++)
                {
                    dir = normalize(Maths.RotateAroundPivot(dir, float3(0f),
                        axis == AxisPair.XY ? float3(0f, 0f, (math.PI) / vCount) : float3(0f, (math.PI) / vCount, 0f)));

                    pt = pt + dir * Random.Range(1f, maxObstacleRadius);
                    vList.Add(pt);
                }

                //if (vCount != 2) { vList.Add(start); }

                o = obstacles.Add(vList, axis == AxisPair.XZ);
            }

            #endregion

            Random.InitState(seed + 10);

            #region create dyanmic obstacles

            for (int i = 0; i < dynObstacleCount; i++)
            {
                int vCount = Random.Range(minObstacleEdgeCount, maxObstacleEdgeCount);
                vList.Clear();
                vList.Capacity = vCount;

                //build branch-like obstacle

                float3 start = float3(Random.Range(-radius, radius), Random.Range(-radius, radius), 0f),
                    pt = start,
                    dir = float3(Random.Range(-dirRange, dirRange), Random.Range(-dirRange, dirRange), 0f);

                if (axis == AxisPair.XZ)
                {
                    pt = start = float3(start.x, 0f, start.y);
                    dir = float3(dir.x, 0f, dir.y);
                }

                vList.Add(start);
                vCount--;

                for (int j = 0; j < vCount; j++)
                {
                    dir = normalize(Maths.RotateAroundPivot(dir, float3(0f),
                        axis == AxisPair.XY ? float3(0f, 0f, (math.PI) / vCount) : float3(0f, (math.PI) / vCount, 0f)));
                    pt = pt + dir * Random.Range(1f, maxObstacleRadius);
                    vList.Add(pt);
                }

                //if (vCount != 2) { vList.Add(start); }

                dynObstacles.Add(vList, axis == AxisPair.XZ);
            }

            #endregion

            #region create agents

            var colors = shapeTex.GetPixels32();//.Where(col => col.a > 100).ToArray();
            float radius2 = (((agentCount - colors.Length) * (maxAgentRadius * 2f)) / PI) * 0.05f;
            IAgent a;

            float angleInc = (PI * 2) / agentCount;
            float angleInc2 = (PI * 2) / (agentCount - colors.Length);
            float halfWidth = shapeTex.width * 0.5f;
            float halfHeight = shapeTex.height * 0.5f;
            int freeIdx = 0;
            for (int i = 0; i < agentCount; i++)
            {
                float2 pos = float2(sin(angleInc * i), cos(angleInc * i)) * (Random.Range(radius * 0.5f, radius));

                if (axis == AxisPair.XY)
                {
                    a = agents.Add((float3)transform.position + float3(pos.x, pos.y, 0f)) as IAgent;
                }
                else
                {
                    a = agents.Add((float3)transform.position + float3(pos.x, 0f, pos.y)) as IAgent;
                }

                a.radius = uniqueRadius ? maxAgentRadius : 0.5f + Random.value * maxAgentRadius;
                a.radiusObst = a.radius;// + Random.value * maxAgentRadius;
                a.prefVelocity = float3(0f);
                a.maxSpeed = maxSpeed;
                a.timeHorizon = 5;
                a.maxNeighbors = 10;
                a.neighborDist = 10;
                a.timeHorizonObst = 5;
                if (i < colors.Length)
                {
                    a.targetPosition = new float3(i % shapeTex.width - halfWidth, 0, i / shapeTex.width - halfHeight);
                    var col = colors[i];
                    m_Colors[i] = new float4(col.r / 255f, col.g / 255f, col.b / 255f, 1f);
                    a.radius = 0.2f;
                    a.radiusObst = a.radius;// + Random.value * maxAgentRadius;
                }
                else
                {
                    m_Colors[i] = new float4(0.7735849f, 0.427747f, 0f, 1f);
                    pos = float2(sin(angleInc2 * freeIdx), cos(angleInc2 * freeIdx)) * (Random.Range(radius2 * 0.5f, radius2));
                    a.targetPosition = (float3)transform.position + float3(pos.x, 0f, pos.y);
                    freeIdx++;
                }
            }

            #endregion

            #region create raycasts

            Raycast r;

            for (int i = 0; i < raycastCount; i++)
            {
                if (axis == AxisPair.XY)
                {
                    r = raycasts.Add(float3(Random.Range(-radius, radius), Random.Range(-radius, radius), 0f)) as Raycast;
                    r.dir = normalize(float3(Random.Range(-1f, 1f), Random.Range(-1f, 1f), 0f));
                }
                else
                {
                    r = raycasts.Add(float3(Random.Range(-radius, radius), 0f, Random.Range(-radius, radius))) as Raycast;
                    r.dir = normalize(float3(Random.Range(-1f, 1f), 0f, Random.Range(-1f, 1f)));
                }

                r.distance = raycastDistance;
            }

            #endregion
        }

        private void Update()
        {
            //Schedule the simulation job. 
            simulation.Schedule(Time.deltaTime);
        }

        private void LateUpdate()
        {
            //Attempt to complete and apply the simulation results, only if the job is done.
            //TryComplete will not force job completion.
            if (simulation.TryComplete())
            {

                //Move dynamic obstacles randomly
                int oCount = dynObstacles.Count;
                float delta = Time.deltaTime * 50f;

                for (int i = 0; i < oCount; i++)
                    dynObstacles[i].Offset(float3(Random.Range(-delta, delta), Random.Range(-delta, delta), 0f));

            }
        }

        private void OnApplicationQuit()
        {
            //Make sure to clean-up the jobs
            simulation.DisposeAll();
        }

        public AgentGroup<Agent> GetAgents()
        {
            return agents;
        }
    }
}

 相比之前5千人100多帧,优化后32768人200多帧:

猜你喜欢

转载自blog.csdn.net/final5788/article/details/133079504