首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

复盘:Unity RTS (4) 网格系统+路线规划

  • 24-03-18 00:03
  • 2683
  • 12130
blog.csdn.net

网络的部分搞定后,主要就是RTS的几大难点.

这里先复盘一下遇到的最大的坑:寻路系统

        方案的选择:

        1.基于路点寻路+A*算法的Unity Navigation

        优点:优化非常好,上手很简单,使用了地图烘焙

        缺点:

        1.多角色之间的碰撞和挤压很难看且难以避免

        如果是人型角色这倒没什么,谁没在CS里被挤过,但是一堆坦克挤来挤去就很酸爽了.这点亲身体会,另外可以参考这个帖子.

unity3d多人寻路问题方案_unity 多人寻路-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/u011926026/article/details/63682788#:~:text=%E7%9B%B8%E4%BF%A1%E5%A4%A7%E5%AE%B6%E7%94%A8unity3d%E8%87%AA%E5%B8%A6navmeshagent%E5%AF%BB%E8%B7%AF%E7%9A%84%E6%97%B6%E5%80%99%EF%BC%8C%E4%B8%80%E5%AE%9A%E4%BC%9A%E7%A2%B0%E5%88%B0%E5%A4%9A%E4%BA%BA%E5%AF%BB%E8%B7%AF%E7%9B%B8%E4%BA%92%E6%8C%A4%E5%8E%8B%E7%9A%84%E9%97%AE%E9%A2%98%E3%80%82,%E8%BF%99%E4%B8%AA%E6%88%91%E5%B7%B2%E7%BB%8F%E8%A7%A3%E5%86%B3%E4%BA%86%EF%BC%8C%E8%80%8C%E4%B8%94%E5%B7%B2%E7%BB%8F%E5%BA%94%E7%94%A8%E5%88%B0%E6%88%91%E4%BB%AC%E7%9A%84%E9%A1%B9%E7%9B%AE%E4%B8%AD%EF%BC%8C%E6%84%9F%E8%A7%89%E8%BF%98%E4%B8%8D%E9%94%99%E3%80%82%20%E9%A6%96%E5%85%88%E6%8F%90%E4%B8%8B%E9%97%AE%E9%A2%98%E7%9A%84%E5%8E%9F%E5%9B%A0%EF%BC%9Anavmeshagent%E5%9C%A8%E5%AF%BB%E8%B7%AF%E7%9A%84%E6%97%B6%E5%80%99%EF%BC%8C%E5%B9%B6%E4%B8%8D%E4%BC%9A%E6%8A%8A%E5%88%AB%E4%BA%BA%E5%BD%93%E6%88%90%E9%9A%9C%E7%A2%8D%E7%89%A9%EF%BC%8C%E8%80%8C%E6%98%AF%E5%9C%A8%E5%8D%B3%E5%B0%86%E5%92%8C%E5%85%B6%E4%BB%96agent%E7%A2%B0%E6%92%9E%E7%9A%84%E6%97%B6%E5%80%99%EF%BC%8C%E9%80%9A%E8%BF%87%E5%87%8F%E9%80%9F%EF%BC%8C%E7%A2%B0%E6%92%9E%E8%BD%AC%E5%BC%AF%EF%BC%8C%E5%AE%9E%E7%8E%B0%E9%81%BF%E5%BC%80%E5%85%B6%E4%BB%96agent.

        总之一点,这个东西对于单人或者对挤压没那么敏感的游戏可以,否则得深入底层改一些东西.

        2.动态添加障碍物比如RTS这种能够随时随地建建筑,会产生一定bug

        3.不适用于帧同步(浮点\碰撞)

        2.现成的A*框架

        性能开销过大,适用于单人或者少量角色.

       *3.流场寻路算法(采用)

        思路参考:

        优点:大量单位公用同一份寻路结果节省了大量时间

        PS.寻路远非线路规划这么简单,因为没有单位能够完全按照规划路线行进,每个单位之间会相互影响,这个影响逻辑怎么写是最麻烦的,这不在本文复盘内容之内.

        

一.建立网格系统

        首先无论是A*还是流场寻路,都必须建立一个网格系统,网格系统听着复杂,其实就是写一些方法,将当前的Unity 转化成抽象的网格上的坐标.

比如我有一个位置Vector3(5,10.6,8),我想知道他在我抽象的网格世界的网格坐标是什么

就有了了Vector3Int currentGrid = Grid.FromWorldToCell(Vector3(5,10.6,8));

我这里的代码是用LStepLock(一个开源定点库)的LVector3,原理一样.

  1. using Lockstep.Math;
  2. using UnityEngine;
  3. public class LGrid : MonoBehaviour
  4. {
  5. [SerializeField] private LFloat cellSize;
  6. ///
  7. /// 获得当前真实三维坐标对应的网格坐标
  8. ///
  9. ///
  10. public LVector3 LWorldToCell(LVector3 world)
  11. {
  12. return new LVector3(
  13. LFloat.Divide(world.x, cellSize),
  14. LFloat.Divide(world.y, cellSize),
  15. LFloat.Divide(world.z, cellSize)
  16. );
  17. }
  18. ///
  19. /// 获得当前网格(左下角)的真实三位坐标中
  20. ///
  21. ///
  22. //默认网格初始位置0,0,0
  23. public LVector3 LCellToWorld(LVector3Int cell)
  24. {
  25. return new LVector3(
  26. (cell.x -1) * cellSize,
  27. (cell.y) * cellSize,
  28. (cell.z -1) * cellSize);
  29. }
  30. ///
  31. /// 获得当前网格(中心点)的真实三位坐标
  32. ///
  33. ///
  34. public LVector3 LGetCellCenterWorld(LVector3 cell)
  35. {
  36. return new LVector3(
  37. (cell.x-1)*cellSize+cellSize/2,
  38. (cell.y)*cellSize,
  39. (cell.z-1)*cellSize+cellSize/2
  40. );
  41. }
  42. ///
  43. /// 获得当前网格(中心点)的真实三位坐标*1000000L的坐标的值
  44. ///
  45. ///
  46. ///
  47. public LVector3 LGetCellCenterWorld_s(LVector3 cell)
  48. {
  49. return new LVector3(
  50. ((cell._x-1)*cellSize+cellSize/2),
  51. (cell._y)*cellSize,
  52. (cell._z-1)*cellSize+cellSize/2
  53. );
  54. }
  55. }

二.初始化导航地图

       这个步骤相当复杂,但是简单来说就是建立一个bool[,]的二维地图,尺寸比如500x500,值为false表示障碍物,true表示可通行.

        对于这次的复盘的任务而言就是传入一张这样的地图,起始点,终点,怎样找到一条从起点到终点的二维地图中的坐标数组

流场算法

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Threading;
  7. using Lockstep.Math;
  8. using UnityEngine;
  9. ///
  10. /// 流场算法
  11. ///
  12. public class FlowFieldPath
  13. {
  14. private bool[,] grid;
  15. public int[,] heatMap;
  16. private int targetX, targetY;
  17. private int[,] flowField;
  18. public bool ifGenerateFineshed = false;
  19. ///
  20. /// 初始化流场寻路:传入导航地图
  21. ///
  22. ///
  23. public FlowFieldPath(bool[,] grid)
  24. {
  25. this.grid = grid;
  26. heatMap = new int[grid.GetLength(0), grid.GetLength(1)];
  27. for (int i = 0; i < heatMap.GetLength(0); i++)
  28. {
  29. for (int j = 0; j < heatMap.GetLength(1); j++)
  30. {
  31. heatMap[i, j] = int.MaxValue;
  32. }
  33. }
  34. }
  35. ///
  36. /// 传入终点,根据重点生成导航热力图
  37. /// 1.创建新的线程
  38. ///
  39. /// 终点
  40. public void GenerateHeatMap(Vector2Int target)
  41. {
  42. new Thread(() => { GenerateHeatMap_T(target); }).Start();
  43. }
  44. ///
  45. /// 传入终点,根据重点生成导航热力图
  46. /// 2.正式开始生成热力图,生成的热力图
  47. ///
  48. ///
  49. ///
  50. public int[,] GenerateHeatMap_T(Vector2Int target)
  51. {
  52. heatMap = new int[grid.GetLength(0), grid.GetLength(1)];
  53. for (int i = 0; i < heatMap.GetLength(0); i++)
  54. {
  55. for (int j = 0; j < heatMap.GetLength(1); j++)
  56. {
  57. heatMap[i, j] = int.MaxValue;
  58. }
  59. }
  60. this.targetX = target.x;
  61. this.targetY = target.y;
  62. Queue queue = new Queue();
  63. queue.Enqueue(new Vector2Int(targetX, targetY));
  64. heatMap[targetX, targetY] = 0;
  65. while (queue.Count > 0)
  66. {
  67. Vector2Int current = queue.Dequeue();
  68. List neighbors = GetNeighbors(current);
  69. foreach (Vector2Int neighbor in neighbors)
  70. {
  71. if (grid[neighbor.x, neighbor.y])
  72. {
  73. int newHeat = heatMap[current.x, current.y] +
  74. ((neighbor.x == current.x || neighbor.y == current.y) ? 1 : 2);
  75. if (newHeat < heatMap[neighbor.x, neighbor.y])
  76. {
  77. heatMap[neighbor.x, neighbor.y] = newHeat;
  78. queue.Enqueue(neighbor);
  79. }
  80. }
  81. }
  82. }
  83. ifGenerateFineshed = true;
  84. return heatMap;
  85. }
  86. ///
  87. /// 获取热力图某点的热力值
  88. ///
  89. ///
  90. ///
  91. public int GetHeatMapPoint(LVector2Int loc)
  92. {
  93. return heatMap[loc.x, loc.y];
  94. }
  95. ///
  96. /// 根据热力图生成流场图
  97. ///
  98. ///
  99. ///
  100. public int[,] GenerateFlowField(Vector2Int target)
  101. {
  102. Debug.Log("确定流场目标:" + target.x + "," + target.y);
  103. GenerateHeatMap(target);
  104. flowField = new int[grid.GetLength(0), grid.GetLength(1)];
  105. for (int i = 0; i < flowField.GetLength(0); i++)
  106. {
  107. for (int j = 0; j < flowField.GetLength(1); j++)
  108. {
  109. List neighbors = GetNeighbors(new Vector2Int(i, j));
  110. int minHeat = int.MaxValue;
  111. foreach (Vector2Int neighbor in neighbors)
  112. {
  113. if (grid[neighbor.x, neighbor.y] && heatMap[neighbor.x, neighbor.y] < minHeat)
  114. {
  115. minHeat = heatMap[neighbor.x, neighbor.y];
  116. flowField[i, j] = DirectionToIndex(new Vector2Int(i, j), neighbor);
  117. }
  118. }
  119. }
  120. }
  121. return flowField;
  122. }
  123. public int CheckDirection(LVector2Int loc)
  124. {
  125. return flowField[loc.x, loc.y];
  126. }
  127. private List GetNeighbors(Vector2Int point)
  128. {
  129. List neighbors = new List();
  130. for (int dx = -1; dx <= 1; dx++)
  131. {
  132. for (int dy = -1; dy <= 1; dy++)
  133. {
  134. if (dx == 0 && dy == 0) continue; // 跳过自身
  135. int nx = point.x + dx;
  136. int ny = point.y + dy;
  137. if (nx >= 0 && ny >= 0 && nx < grid.GetLength(0) && ny < grid.GetLength(1))
  138. {
  139. neighbors.Add(new Vector2Int(nx, ny));
  140. }
  141. }
  142. }
  143. return neighbors;
  144. }
  145. private int DirectionToIndex(Vector2Int current, Vector2Int target)
  146. {
  147. Vector2Int direction = target - current;
  148. if (direction.x == 0 && direction.y == -1) return 0; // 上
  149. if (direction.x == 1 && direction.y == -1) return 1; // 右上
  150. if (direction.x == 1 && direction.y == 0) return 2; // 右
  151. if (direction.x == 1 && direction.y == 1) return 3; // 右下
  152. if (direction.x == 0 && direction.y == 1) return 4; // 下
  153. if (direction.x == -1 && direction.y == 1) return 5; // 左下
  154. if (direction.x == -1 && direction.y == 0) return 6; // 左
  155. if (direction.x == -1 && direction.y == -1) return 7; // 左上
  156. return -1;
  157. }
  158. public LVector2Int GetFlowFieldDirection(LVector2Int point)
  159. {
  160. int directionIndex = flowField[point.x, point.y];
  161. LVector2Int direction = IndexToDirection(directionIndex);
  162. return direction;
  163. }
  164. private LVector2Int IndexToDirection(int index)
  165. {
  166. switch (index)
  167. {
  168. case 0: return new LVector2Int(0, 1); // 上
  169. case 1: return new LVector2Int(1, 1); // 右上
  170. case 2: return new LVector2Int(1, 0); // 右
  171. case 3: return new LVector2Int(1, -1); // 右下
  172. case 4: return new LVector2Int(0, -1); // 下
  173. case 5: return new LVector2Int(-1, -1); // 左下
  174. case 6: return new LVector2Int(-1, 0); // 左
  175. case 7: return new LVector2Int(-1, 1); // 左上
  176. default: return new LVector2Int(0, 0); // 默认情况,没有方向
  177. }
  178. }
  179. }

注:本文转载自blog.csdn.net的asd55571的文章"https://blog.csdn.net/u011457179/article/details/135682657"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

134
游戏
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top