视频中万人同屏方案(gpu动画、渲染、索敌、避障等功能),可某宝搜【店铺】:【游戏开发资源商店】获取整套方案源码。
划重点!!此方案是绕开Entities(ECS),不用写一行ECS代码,现有MonoBehavior开发工作流享受Entities渲染的性能。已有项目也能使用此方案开挂,无需代码重构!
万人同屏对抗demo测试包下载,以及万人同屏方案性能测试,有需要的老板可以某宝【搜店铺】:【游戏开发资源商店】:https://pan.baidu.com/s/1ML0DC8s0RkkTAraN9maltw?pwd=bluehttps://pan.baidu.com/s/1ML0DC8s0RkkTAraN9maltw?pwd=blue
性能压力测试包自取:
PC 10万动画人同屏对抗:https://pan.baidu.com/s/1CgpTV0TuFagobtAf7k38OA?pwd=xwsf
安卓 1万动画人同屏避障: https://pan.baidu.com/s/1RkXyVXt_he5uCgizTSA42A?pwd=k0ji
插件使用视频教程:
Unity低成本性能开挂 gpu动画+dots graphics万人同屏渲染 jobs rvo2 弹幕游戏海量单位索敌
效果见视频:
Unity Dots 10万人同屏RVO避障是一种什么体验? 3D人物带动画,不使用ECS
Unity弹幕游戏, RVO红蓝对抗 割草游戏 海量单位高性能索敌攻击方案压测
不使用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
插件依赖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;
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多帧: