104 lines
4.3 KiB
C#
104 lines
4.3 KiB
C#
using Microsoft.Extensions.Hosting;
|
||
using System.Diagnostics;
|
||
using System.Numerics;
|
||
|
||
namespace XNet.Business
|
||
{
|
||
public class GameLoopService : BackgroundService
|
||
{
|
||
private readonly NavMeshManager _navMeshManager;
|
||
private readonly SceneAgent _sceneAgent;
|
||
private readonly WsConnectionManager _wsManager; // 新增WebSocket管理器
|
||
|
||
private const int TARGET_FPS = 30;
|
||
private const int FRAME_TIME_MS = 1000 / TARGET_FPS;
|
||
|
||
public GameLoopService(NavMeshManager navMeshManager, SceneAgent sceneAgent, WsConnectionManager wsManager)
|
||
{
|
||
_navMeshManager = navMeshManager;
|
||
_sceneAgent = sceneAgent;
|
||
_wsManager = wsManager;
|
||
}
|
||
|
||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||
{
|
||
Console.WriteLine("=== Server Initializing Resources... ===");
|
||
|
||
// 1. 加载所有静态地图资源 (Templates)
|
||
// 这里假设路径固定,实际可从配置表读取
|
||
_navMeshManager.LoadTemplate("Map_Forest", @"D:\NavMeshExport.obj");
|
||
//_navMeshManager.LoadTemplate("Map_Dungeon", @"D:\NavMeshExport.obj");
|
||
|
||
// 2. 模拟创建副本逻辑 (实际应由 WebAPI 或 MatchService 触发)
|
||
// 假设现在有两个队伍分别开启了森林副本
|
||
string instanceGuid_A = "Instance_TeamA_" + Guid.NewGuid();
|
||
string instanceGuid_B = "Instance_TeamB_" + Guid.NewGuid();
|
||
|
||
// 注册 NavMesh 映射
|
||
_navMeshManager.CreateInstance(instanceGuid_A, "Map_Forest");
|
||
_navMeshManager.CreateInstance(instanceGuid_B, "Map_Forest"); // 复用同一份 NavMesh 内存
|
||
|
||
// 为这两个副本创建独立的物理/避障模拟器
|
||
_sceneAgent.CreateCrowdForInstance(instanceGuid_A);
|
||
_sceneAgent.CreateCrowdForInstance(instanceGuid_B);
|
||
|
||
// 添加一些测试怪物
|
||
int monsterA = _sceneAgent.AddAgent(instanceGuid_A, new Vector3(-4, 0, -4), 0.05f, 1);
|
||
_sceneAgent.AgentGoto(instanceGuid_A, monsterA, new Vector3(5, 0, 3));
|
||
|
||
Console.WriteLine("=== Server Game Loop Started ===");
|
||
|
||
var stopwatch = new Stopwatch();
|
||
stopwatch.Start();
|
||
long lastTime = stopwatch.ElapsedMilliseconds;
|
||
|
||
while (!stoppingToken.IsCancellationRequested)
|
||
{
|
||
long currentTime = stopwatch.ElapsedMilliseconds;
|
||
long elapsedMs = currentTime - lastTime;
|
||
|
||
// 防止调试断点导致 delta 过大,限制最大单帧时间
|
||
if (elapsedMs > 100) elapsedMs = 100;
|
||
|
||
float deltaTime = elapsedMs / 1000.0f;
|
||
lastTime = currentTime;
|
||
|
||
// 【核心循环】
|
||
// 现在不需要一把大锁锁住全服了
|
||
|
||
// 1. 如果有来自 WebAPI 的异步命令(如动态创建副本),可以在 NavMeshManager 内部处理
|
||
// 或者在这里手动处理队列 (如果使用了 CommandQueue)
|
||
// _navMeshManager.ProcessCommands();
|
||
|
||
// 2. 并行更新所有副本的 Crowd
|
||
// 这行代码利用 SceneAgent 内部的 Parallel.ForEach,效率极高
|
||
_sceneAgent.UpdateAll(deltaTime);
|
||
|
||
// 2. 收集所有需要同步的Agent状态
|
||
List<AgentPositionSyncMsg> allSyncMsgs =
|
||
[
|
||
// 遍历所有实例(可从_navMeshManager获取实例列表,或SceneAgent维护)
|
||
.. _sceneAgent.GetAgentsNeedSync(instanceGuid_A),
|
||
.. _sceneAgent.GetAgentsNeedSync(instanceGuid_B),
|
||
];
|
||
|
||
// 3. 异步发送WebSocket消息(不阻塞游戏循环)
|
||
if (allSyncMsgs.Count > 0)
|
||
{
|
||
_ = _wsManager.SendAgentPositionBatchAsync(allSyncMsgs);
|
||
}
|
||
|
||
// 帧率控制
|
||
long frameWorkTime = stopwatch.ElapsedMilliseconds - currentTime;
|
||
int delay = (int)(FRAME_TIME_MS - frameWorkTime);
|
||
|
||
if (delay > 0)
|
||
{
|
||
await Task.Delay(delay, stoppingToken);
|
||
}
|
||
}
|
||
|
||
Console.WriteLine("=== Server Game Loop Stopped ===");
|
||
}
|
||
}
|
||
} |