-
스파르타) The Last Rollback (D-10, Node.js 게임 서버 최종 프로젝트) - DotRecast (4)TIL-sparta 2024. 8. 18. 01:41
학습 키워드: C#, .NET, Recast & Detour, DotRecast
1. Progress
1) Agent 추가 및 이동 테스트:
> 24-08-17 일자 commit history 참고
주요 작업 내용
- AddMonster() 구현하여 DtCrowd에 DtCrowdAgent 추가
- Start()로 GameLoop() 비동기 실행, 매 루프마다 DtCrowd 업데이트
- GameLoop의 Update 주기를 _tickRate를 통해 조절
// Program.cs public static void Main() { Init(); CrowdManager cm = new (1); cm.Start(); cm.AddMonster(1); Console.WriteLine("pos: " + cm.GetMonsterPos(1)); SchedulerUtils.SetIntervalAction(1000, () => { Console.WriteLine("new pos: " + cm.GetMonsterPos(1)); }); // Start the TCP server StartTcpServer(); } ...
// CrowdManager.cs ... private async Task GameLoop() { _startTime = DateTime.Now; DateTime prevTime = _startTime; int count = 0; while (_state == CrowdManagerState.RUNNING) { DateTime curTime = DateTime.Now; float deltaTime = (float) (curTime - prevTime).TotalSeconds; Update(deltaTime); Console.WriteLine($"[{count++}], deltaTime: {deltaTime}s"); double elapsed = (DateTime.Now - curTime).TotalMilliseconds; if (elapsed < _tickRate) { await Task.Delay(_tickRate - (int) elapsed); } prevTime = curTime; } Console.WriteLine("Game loop ended."); } ...
2) AddMonster 구현하며 학습한 DtCrowd 관련 내용:
// CrowdManager.cs (작성한 코드) ... /// <summary> /// Add a new monster to the given position, with the given option. /// </summary> /// <param name="monsterIdx">monster's index to be added to _monsters list</param> /// <param name="pos">vector position on navMesh the mosnter is to be spwawned at</param> /// <param name="option">crowd agent's option parameters</param> public void AddMonster(int monsterIdx, RcVec3f pos, DtCrowdAgentParams option) { // Add new agent DtCrowdAgent agent = _crowd.AddAgent(pos, option); _monsters.Add(monsterIdx, agent.idx); Console.WriteLine($"monster[{monsterIdx}] spawned at Vector3({pos.X},{pos.Y},{pos.Z})"); // Query NavMesh DtNavMeshQuery navQuery = _crowd.GetNavMeshQuery(); RcVec3f center = new(-5.04f, 0.55f, 135.68f); RcVec3f halfExtents = new(3f, 1.5f, 3f); navQuery.FindNearestPoly( center, halfExtents, new DtQueryDefaultFilter(), out long nearestRef, out RcVec3f nearestPt, out bool isOverPoly ); // Set the agent's initial target _crowd.RequestMoveTarget(agent, nearestRef, nearestPt); Console.WriteLine($"monsterIdx[{monsterIdx}] state: " + agent.state); } public void AddMonster(int monsterIdx, DtCrowdAgentParams option) { // TODO: create a external function to get a random spawn position on a map RcVec3f spawnPos = SpawnerManager.GetRandomSpawnerPosition(_dungeonCode, 1234); AddMonster(monsterIdx, spawnPos, option); } public void AddMonster(int monsterIdx) { // TODO: fetch monster's agent param stats from JSON DtCrowdAgentParams option = new() // temp stats { radius = 0.6f, height = 1.5f, maxAcceleration = 5f, maxSpeed = 3.5f, collisionQueryRange = 0.6f, pathOptimizationRange = 10f, // temp separationWeight = 0, updateFlags = DtCrowdAgentUpdateFlags.DT_CROWD_ANTICIPATE_TURNS, obstacleAvoidanceType = 2, queryFilterType = 0, userData = new(), }; AddMonster(monsterIdx, option); } ...
// DtCrowdAgentParams.cs (DotRecast) ... public class DtCrowdAgentParams { public float radius; // < Agent radius. [Limit: >= 0] public float height; // < Agent height. [Limit: > 0] public float maxAcceleration; // < Maximum allowed acceleration. [Limit: >= 0] public float maxSpeed; // < Maximum allowed speed. [Limit: >= 0] /// Defines how close a collision element must be before it is considered for steering behaviors. [Limits: > 0] public float collisionQueryRange; public float pathOptimizationRange; // < The path visibility optimization range. [Limit: > 0] /// How aggresive the agent manager should be at avoiding collisions with this agent. [Limit: >= 0] public float separationWeight; /// the agent path. /// Flags that impact steering behavior. (See: #UpdateFlags) public int updateFlags; /// The index of the avoidance configuration to use for the agent. /// [Limits: 0 <= value < #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS] public int obstacleAvoidanceType; /// The index of the query filter used by this agent. public int queryFilterType; /// User defined data attached to the agent. public object userData; }
AddMonster를 여러 형태의 overload로 구현했다. 기본적으로 사용할 overload는 인자가 하나인 AddMonster(int monsterIdx) 인데, 나중에 몬스터 별 agent 설정을 바꾸게 되면 그 외 두 개의 overload를 사용하게 될 것이다. DtCrowdAgentParams는 agent가 NavMesh 위를 활보할 때 적용되는 각종 설정 값이다. 각각의 값이 무엇을 의미하는지는 라이브러리 코드에 대략적으로 적혀있다.
유의할 부분은 가장 아래 4개 항목이다. 먼저 updateFlags는 DtCrowdAgentUpdateFlags에 상수 값으로 존재하며, 어떤 값을 주느냐에 따라 agent의 steering behavior가 바뀐다. 각각이 정확히 어떻게 동작하는지는 Docs에 따로 언급되어있지 않아서 추후에 따로 알아보기로 했다.
// DtCrowd.cs (DotRecast) ... public DtCrowd(DtCrowdConfig config, DtNavMesh nav, Func<int, IDtQueryFilter> queryFilterFactory) { _config = config; _agentPlacementHalfExtents = new RcVec3f(config.maxAgentRadius * 2.0f, config.maxAgentRadius * 1.5f, config.maxAgentRadius * 2.0f); _obstacleQuery = new DtObstacleAvoidanceQuery(config.maxObstacleAvoidanceCircles, config.maxObstacleAvoidanceSegments); _filters = new IDtQueryFilter[DtCrowdConst.DT_CROWD_MAX_QUERY_FILTER_TYPE]; for (int i = 0; i < DtCrowdConst.DT_CROWD_MAX_QUERY_FILTER_TYPE; i++) { _filters[i] = queryFilterFactory.Invoke(i); } // Init obstacle query option. _obstacleQueryParams = new DtObstacleAvoidanceParams[DtCrowdConst.DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]; for (int i = 0; i < DtCrowdConst.DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS; ++i) { _obstacleQueryParams[i] = new DtObstacleAvoidanceParams(); } // Allocate temp buffer for merging paths. _maxPathResult = DtCrowdConst.MAX_PATH_RESULT; _pathQ = new DtPathQueue(config); _agentIdx = new RcAtomicInteger(0); _agents = new Dictionary<int, DtCrowdAgent>(); _activeAgents = new List<DtCrowdAgent>(); // The navQuery is mostly used for local searches, no need for large node pool. SetNavMesh(nav); } ... public void SetObstacleAvoidanceParams(int idx, DtObstacleAvoidanceParams option) ...
public class DtObstacleAvoidanceParams { public float velBias; public float weightDesVel; public float weightCurVel; public float weightSide; public float weightToi; public float horizTime; public int gridSize; // < grid public int adaptiveDivs; // < adaptive public int adaptiveRings; // < adaptive public int adaptiveDepth; // < adaptive ...
다음은 obstacleAvoidanceType인데, 이 값은 0 부터 MAX_OBSTAVOIDANCE_PARAMS 값 사이를 지정해줘야 하고, MAX 값은 DtCrowdConst에 상수로 지정되어있다. DtCrowd를 생성하면 MAX 값 만큼의 길이를 가진 DtObstacleAvoidanceParams 배열이 생기는데, DtCrowd의 SetObstacleAvoidanceParams method를 사용하여 이 배열의 각 index마다 다른 option을 지정할 수 있다. 따라서 agent 생성시에 주는 obstacleAvoidanceType 값은 DtCrowd에서 어느 index에 저장된 avoidance option을 사용할 지를 정하는 것이다.
queryFilterType 또한 DtCrowdConst에 MAX 값이 저장되어있는데, obstacleAvoidanceType과 마찬가지로 DtCrowd가 생성될 때 이 MAX 값 만큼의 배열이 생성된다. IDtQueryFilter를 implement한 custom queryFilter를 여러 개 만들어서 적용할 수 있다. 따로 수정할 계획이 없다면 기본 제공되는 DtQueryDefaultFilter를 사용하면 된다.
마지막 userData는 Object로 선언된, 유저가 정의한 커스텀 데이터를 저장하는 공간이다. 당장에 추가할 정보가 없어서 그냥 new()로 초기화만 해주고 넘어갔다.
(계속)
--
REFERENCES:https://github.com/eliotjang/the-last-rollback-server
> The Last Rollback 서버 repo
https://github.com/donkim1212/PathfindingDediServer/tree/feat/crowd-manager
> Pathfinding Dedi 서버 repo
https://rwindegger.github.io/recastnavigation/index.html
> Recast & Detour Docs
728x90'TIL-sparta' 카테고리의 다른 글
스파르타) Node.js 5기 게임 서버 최종 프로젝트 (D-8) (0) 2024.08.20 스파르타) Node.js 5기 게임 서버 최종 프로젝트 (D-9) (0) 2024.08.19 스파르타) The Last Rollback (D-11, Node.js 게임 서버 최종 프로젝트) - DotRecast (3) (0) 2024.08.16 스파르타) The Last Rollback (D-12, Node.js 게임 서버 최종 프로젝트) - DotRecast (2) (0) 2024.08.16 스파르타) The Last Rollback (D-13, Node.js 게임 서버 최종 프로젝트) - DotRecast (0) 2024.08.14