153 lines
5.4 KiB
C#
153 lines
5.4 KiB
C#
using DotRecast.Core;
|
|
using DotRecast.Core.Numerics;
|
|
using DotRecast.Detour;
|
|
using DotRecast.Detour.Crowd;
|
|
using System.Collections.Concurrent;
|
|
using System.Numerics;
|
|
|
|
namespace XNet.Business
|
|
{
|
|
public class SceneAgent
|
|
{
|
|
private readonly NavMeshManager _navMeshManager;
|
|
|
|
private class CrowdInstance
|
|
{
|
|
public DtCrowd Crowd { get; set; } = null!;
|
|
public object SyncRoot { get; } = new object();
|
|
}
|
|
|
|
private readonly ConcurrentDictionary<string, CrowdInstance> _crowdInstances = new();
|
|
|
|
private const float AGENT_RADIUS = 0.5f;
|
|
private const float AGENT_HEIGHT = 2.0f;
|
|
|
|
public SceneAgent(NavMeshManager navMeshManager)
|
|
{
|
|
_navMeshManager = navMeshManager;
|
|
}
|
|
|
|
public bool CreateCrowdForInstance(string instanceId)
|
|
{
|
|
// 使用 NavMeshManager 新增的 Helper 方法
|
|
if (!_navMeshManager.GetNavMeshAndQuery(instanceId, out var navMesh, out _))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var config = new DtCrowdConfig(0.6f);
|
|
var crowd = new DtCrowd(config, navMesh);
|
|
|
|
var paramsData = crowd.GetObstacleAvoidanceParams(0);
|
|
paramsData.velBias = 0.4f;
|
|
paramsData.adaptiveDivs = 5;
|
|
paramsData.adaptiveRings = 2;
|
|
paramsData.adaptiveDepth = 2;
|
|
crowd.SetObstacleAvoidanceParams(0, paramsData);
|
|
|
|
var instance = new CrowdInstance { Crowd = crowd };
|
|
return _crowdInstances.TryAdd(instanceId, instance);
|
|
}
|
|
|
|
public void RemoveCrowdInstance(string instanceId)
|
|
{
|
|
_crowdInstances.TryRemove(instanceId, out _);
|
|
}
|
|
|
|
public int AddAgent(string instanceId, Vector3 position, float radius, float height, float maxAcceleration = 8.0f, float maxSpeed = 3.5f)
|
|
{
|
|
if (!_crowdInstances.TryGetValue(instanceId, out var ci)) return -1;
|
|
|
|
lock (ci.SyncRoot)
|
|
{
|
|
var pos = new RcVec3f(position.X, position.Y, -position.Z);
|
|
var ap = new DtCrowdAgentParams
|
|
{
|
|
radius = radius,
|
|
height = height,
|
|
maxAcceleration = maxAcceleration,
|
|
maxSpeed = maxSpeed,
|
|
collisionQueryRange = radius * 12.0f,
|
|
pathOptimizationRange = radius * 30.0f,
|
|
updateFlags = DtCrowdAgentUpdateFlags.DT_CROWD_ANTICIPATE_TURNS
|
|
| DtCrowdAgentUpdateFlags.DT_CROWD_OBSTACLE_AVOIDANCE
|
|
| DtCrowdAgentUpdateFlags.DT_CROWD_SEPARATION
|
|
| DtCrowdAgentUpdateFlags.DT_CROWD_OPTIMIZE_VIS
|
|
| DtCrowdAgentUpdateFlags.DT_CROWD_OPTIMIZE_TOPO
|
|
};
|
|
|
|
return ci.Crowd.AddAgent(pos, ap).idx;
|
|
}
|
|
}
|
|
|
|
public void RemoveAgent(string instanceId, int agentIdx)
|
|
{
|
|
if (!_crowdInstances.TryGetValue(instanceId, out var ci)) return;
|
|
|
|
lock (ci.SyncRoot)
|
|
{
|
|
var agent = ci.Crowd.GetAgent(agentIdx);
|
|
// 【修复】判断 Agent 是否有效,需要检查 state
|
|
if (agent != null && agent.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
|
|
{
|
|
ci.Crowd.RemoveAgent(agent);
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool AgentGoto(string instanceId, int agentIdx, Vector3 destination)
|
|
{
|
|
if (!_crowdInstances.TryGetValue(instanceId, out var ci)) return false;
|
|
|
|
// 使用 NavMeshManager 获取 Query
|
|
if (!_navMeshManager.GetNavMeshAndQuery(instanceId, out _, out var query)) return false;
|
|
|
|
var targetPos = new RcVec3f(destination.X, destination.Y, -destination.Z);
|
|
|
|
// 使用 public 的 Extents 和 Filter
|
|
query.FindNearestPoly(targetPos, _navMeshManager.Extents, _navMeshManager.Filter,
|
|
out long targetRef, out var realTargetPos, out var _);
|
|
|
|
if (targetRef == 0) return false;
|
|
|
|
lock (ci.SyncRoot)
|
|
{
|
|
var agent = ci.Crowd.GetAgent(agentIdx);
|
|
// 【修复】检查 state
|
|
if (agent != null && agent.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
|
|
{
|
|
return ci.Crowd.RequestMoveTarget(agent, targetRef, realTargetPos);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public Vector3? GetAgentPosition(string instanceId, int agentIdx)
|
|
{
|
|
if (!_crowdInstances.TryGetValue(instanceId, out var ci)) return null;
|
|
|
|
lock (ci.SyncRoot)
|
|
{
|
|
var agent = ci.Crowd.GetAgent(agentIdx);
|
|
// 【修复】检查 state
|
|
if (agent != null && agent.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
|
|
{
|
|
var p = agent.npos;
|
|
return new Vector3(p.X, p.Y, -p.Z);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public void UpdateAll(float deltaTime)
|
|
{
|
|
Parallel.ForEach(_crowdInstances.Values, ci =>
|
|
{
|
|
lock (ci.SyncRoot)
|
|
{
|
|
ci.Crowd.Update(deltaTime, null);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
} |