XNet/XNet.Business/SceneAgent.cs
2025-12-25 13:18:48 +08:00

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);
}
});
}
}
}