上一个项目的寻路方案是客户端和服务器都采用了 NavMesh 作为解决方案,当时的那几篇文章(一,二,三)是很多网友留言和后台发消息询问最多的,看来这个方案有着广泛的需求。但因为是商业项目,我无法贴出代码,只能说明下我的大致思路,况且也有些悬而未决的不完美的地方,比如客户端和服务器数据准确度和精度的问题,但是考虑到项目类型和性价比,我们忽略了这个点。

  从今年5月份开始为期一个月,我的主要工作是为新项目寻找一个新的寻路方案。新项目是一个 RTS 实时竞技游戏,寻路要求是:每个寻路单位之间的碰撞精确,不能出现不正确的拥挤和穿插,并且单位大小和所在的任何位置,都会影响到其他活动单位的通路性选择,看起来就是一个典型的 RTS 游戏寻路,和红警,星际等这些游戏非常像。

  项目最初的阶段为了先快速迭代功能,使用了 Unity 内建的 NavMesh 系统,当单位停下使用 NavMeshObstacle 在地上挖个坑来影响通路性,但是经常会出现两个建筑型的单位中间会有个小缝,虽然缝很细,但是也是可以走的,而很可能一个半径巨大的单位直接就试图传过去,结果是被卡在这里。如果给单位的半径很小就可以穿过去,但是感觉很怪,而且单位之间的穿插也会很明显,十分影响游戏的观感,更重要的是会影响游戏性。终于有一天,策划的同学们再也不能忍了,必须改掉!

  在我看来我首先要解决的是能够将现有的或者寻找到一个能支持单位半径大小的寻路方案。但是这从一开始就排除掉了 Unity 的 NavMesh 的寻路方案,因为没有任何 api 提供给用户可供做类似的修改,通过修改 RecastNavigation 项目然后编译 Native 插件的方式我也否掉了,不划算,其实本质上是因为 NavMesh 系统无法提供我们需要的高精度要求,所以我把精力集中到寻找传统的 Grid 网格寻路上了。

  过程中找到了一篇专门讲解不同单位通路性的文章:《Clearance-based Pathfinding and Hierarchical Annotated A* Search》,讲解深入详细,并且还配有源码。

  

  这看起来似乎正好是满足我要求的东西,但是它有个致命的缺点:所有一切都是预先烘焙和计算的,如果有任何物体或者情况影响了原有的通路性,那么整个烘焙过程必须重新进行,按照作者的算法和流程,对于我们这种时刻都在改变整个地图通路性的情况来讲,完全不可能进行不断地实时计算,所以该方案最终还是放弃了。

  后来朋友介绍了个很有意思的项目:Stratagus - GitHub - Wargus/stratagus: The Stratagus strategy game engine。这个项目很有意思,安装到手机后,直接把星际争霸1的资源导入,然后就可以在手机上玩星际争霸了,狂拽酷炫吊炸天。不过我安装并且导入星际资源到安卓手机后,一进入战斗场景就崩溃,试了很多次都不行,其实就是想看看它寻路的表现。好了不浪费时间,直接下载代码开始阅读,各种查找翻阅,最后看到了它是如何处理单位的大小和通路性的关键判断:位于 src/pathfinder/astar.cpp:CostMoveToCallBack_Default @line:506

 1 /* build-in costmoveto code */
 2 static int CostMoveToCallBack_Default(unsigned int index, const CUnit &unit)
 3 {
 4 #ifdef DEBUG
 5     {
 6         Vec2i pos;
 7         pos.y = index / Map.Info.MapWidth;
 8         pos.x = index - pos.y * Map.Info.MapWidth;
 9         Assert(Map.Info.IsPointOnMap(pos));
10     }
11 #endif
12     int cost = 0;
13     const int mask = unit.Type->MovementMask;
14     const CUnitTypeFinder unit_finder((UnitTypeType)unit.Type->UnitType);
15
16     // verify each tile of the unit.
17     int h = unit.Type->TileHeight;
18     const int w = unit.Type->TileWidth;
19     do {
20         const CMapField *mf = Map.Field(index);
21         int i = w;
22         do {
23             const int flag = mf->Flags & mask;
24             if (flag && (AStarKnowUnseenTerrain || mf->playerInfo.IsExplored(*unit.Player))) {
25                 if (flag & ~(MapFieldLandUnit | MapFieldAirUnit | MapFieldSeaUnit)) {
26                     // we can't cross fixed units and other unpassable things
27                     return -1;
28                 }
29                 CUnit *goal = mf->UnitCache.find(unit_finder);
30                 if (!goal) {
31                     // Shouldn't happen, mask says there is something on this tile
32                     Assert(0);
33                     return -1;
34                 }
35                 if (goal->Moving)  {
36                     // moving unit are crossable
37                     cost += AStarMovingUnitCrossingCost;
38                 } else {
39                     // for non moving unit Always Fail unless goal is unit, or unit can attack the target
40                     if (&unit != goal) {
41                         if (goal->Player->IsEnemy(unit) && unit.IsAgressive() && CanTarget(*unit.Type, *goal->Type)
42                             && goal->Variable[UNHOLYARMOR_INDEX].Value == 0 && goal->IsVisibleAsGoal(*unit.Player)) {
43                                 cost += 2 * AStarMovingUnitCrossingCost;
44                         } else {
45                         // FIXME: Need support for moving a fixed unit to add cost
46                             return -1;
47                         }
48                         //cost += AStarFixedUnitCrossingCost;
49                     }
50                 }
51             }
52             // Add cost of crossing unknown tiles if required
53             if (!AStarKnowUnseenTerrain && !mf->playerInfo.IsExplored(*unit.Player)) {
54                 // Tend against unknown tiles.
55                 cost += AStarUnknownTerrainCost;
56             }
57             // Add tile movement cost
58             cost += mf->getCost();
59             ++mf;
60         } while (--i);
61         index += AStarMapWidth;
62     } while (--h);
63     return cost;
64 }

  每次进行 AStar 寻路,计算寻路 Cost 的时候,都会走到这个回调函数,请注意以上代码中每个 Unit(建筑和可活动单位)都有一个 TileWidth,这个TileWidth 就是寻路单位在地图上所占的一个正方形的宽度(如果使用长方形会极大的增加计算复杂度,因为要考虑旋转后占格的问题。)一次遍历这个正方形,看看每一个格子是否被任何单位设置了使用 Mask,如果没有就说明可通过,最终如果一个正方形内所有格子都没有被设置任何 Mask 说明该区域可走,对于移动中的物体,认为它所占的区域属于可走区域,但是会给予比普通可走区域更高的 Cost。所以看来原理很简单,就是在寻找 AStar 节点的时候要遍历该节点所占的每个格子看是否可以通过,条件变得更加严格。

  这个方式非常适合我的需求,我决定采取这个方式来进行后一步工作。考虑到时间紧迫且对稳定性要求高,我不打算自己重新编写整个寻路算法和框架了,寻找一个成熟的合适的插件来进行后一步工作,经过试验考察,我选择了使用 Unity 上一个非常强大和成熟的插件 A* Pathfinding Project,来进行扩展和升级以便达到我的要求。

  我们在 Unity Asset Store 中购买了此插件(很贵:100刀),然后我针对 GridGraph 类型进行了深度的修改,增加单位的 TraverseSize 作为寻路的参数传入,以便影响寻路结果,这样不同单位的大小,在穿过缝隙时,就可能会有不同的路径,如下图,每一个寻路的 Node 设置为了0.5,大的单位半径0.5,小的单位半径0.25,(寻路计算最终是直径),前往同一个地点,有如下结果:

  小的单位可以通过宽度为0.5的缝隙而大的不可以,只能通过最小为1.0大小的缝隙,于是我的最基本的需求得到满足。

  接下来,我需要处理碰撞的问题,精细碰撞需要单位无论大小,速度,位置,都要准确的处理,他们只能在边缘接触,不能过于穿插。期初我试用了这个插件自带的 RVO 系统,但是精度真的不够,后来我决定采用一个比较诡异但十分凑效的方案:使用 Unity 的 NavMesh 系统的 NavMeshAgent 的 Detour 系统来实现碰撞,当初据 RecastNavigation 的作者说,Unity 深度改写了该系统的 Detour 系统,可能这就是直接的体现吧。也就是说在地图上生成一个没有任何阻挡的完整的 NavMesh 可走区域,但是不用来走寻路目的,只是为了使用 NavMeshAgent 的碰撞计算而已,真实的寻路结果是 A* Pathfinding Project 提供的。

  以上所有需要的基础系统都已经完成,但这只是另一个开始,和项目的结合和调试过程也是一个不断地改进的过程,需要和策划的同学们不断地沟通和交流进行调校,经过一段时间的磨合,终于开始稳定的按照预期目标进行工作了。结项!

转载于:https://www.cnblogs.com/yaukey/p/rts_unit_traverse_size_based_path_finding.html

从 NavMesh 网格寻路回归到 Grid 网格寻路。相关推荐

  1. CSS Grid 网格布局完整教程

    前言 一.概述 二.基本概念 2.1 容器和项目 2.2 行和列 2.3 单元格 2.4 网格线 三.容器属性 3.1 display 属性 3.2 行与列属性定义 明文定义 百分比定义 repeat ...

  2. CSS Grid网格布局详解

    Grid 布局又称网格布局,是W3C提出的一个二维布局系统,它与 Flex 布局有一定的相似性,都可以指定容器内部多个项目的位置.但是,它们也存在重大区别.Flex 布局是轴线布局,只能指定" ...

  3. CSS Grid 网格布局全解析

    一.介绍 CSS Grid(网格) 布局使我们能够比以往任何时候都可以更灵活构建和控制自定义网格. Grid(网格) 布局使我们能够将网页分成具有简单属性的行和列.它还能使我们在不改变任何HTML的情 ...

  4. CSS的Grid网格布局

    Grid网格布局 就是通过设置百分比,或者默认划分的单位个数,来达到宽度自适应的效果 比如,页面控件的宽度,设置自适应随着显示器的宽度的增加而增加,网页永远占满整个屏幕 .searchContentR ...

  5. CSS Grid网格布局全攻略

    CSS Grid网格布局全攻略 所有奇技淫巧都只在方寸之间. 几乎从我们踏入前端开发这个领域开始,就不停地接触不同的布局技术.从常见的浮动到表格布局,再到如今大行其道的flex布局,css布局技术一直 ...

  6. CSS Grid 网格布局教程

    作者: 阮一峰 日期: 2019年3月25日 一.概述 网格布局(Grid)是最强大的 CSS 布局方案. 它将网页划分成一个个网格,可以任意组合不同的网格,做出各种各样的布局.以前,只能通过复杂的 ...

  7. CSS—— grid 网格布局

    文章目录 1. grid 网格布局 1. grid 网格布局 display:grid grid 属性是以下属性的简写属性,默认: grid-gap , none,[200px]网格之间的距离 gri ...

  8. 《十二》CSS3 Grid 网格布局

    网格布局将网页划分成一个个网格,可以任意组合不同的网格,做出各种各样的布局. Flex 布局是轴线布局,只能指定项目针对轴线的位置,可以看作是一维布局.适用于一个方向上的布局. Grid布 局则是将容 ...

  9. grid布局浏览器兼容_CSS Grid 网格布局教程

    一.概述 网格布局(Grid)是最强大的 CSS 布局方案. 它将网页划分成一个个网格,可以任意组合不同的网格,做出各种各样的布局.以前,只能通过复杂的 CSS 框架达到的效果,现在浏览器内置了. 上 ...

最新文章

  1. 换系统后mysql环境不见了_电脑重装系统后如何恢复Mysql数据库
  2. JavaScript 的 Promise  和  C# 的 waitone 一样吗?请大家讨论i两句。
  3. centos下新建超级用户及sudoers权限问题
  4. 《C语言程序设计与实践(第2版)》——第1章 C语言与程序设计概述 1.1初见C语言程序...
  5. 前端学习(3345):设计模式之工厂模式2
  6. php 路由 隐藏index,CI中路由与伪静态、隐藏index.php(十四)
  7. 从/etc/inetd.conf学习服务(4)
  8. 关于单反相机中的APS-C
  9. day22 随机输出ArrayList
  10. 未解决:运行EtherCalc出错:Error: Cannot find module 'zappajs'
  11. IT桌面运维常识系列 - MDT
  12. vbs整人代码蓝屏_vbs整人代码
  13. MPEG4视频压缩编码技术详解
  14. 普源示波器 电脑 连接 软件_乐高wedo2.0电脑软件安装及蓝牙连接方法
  15. Linux怎么有两个vmdk文件,「Linux」- 挂载 VMDK 文件
  16. 【Html】 Html写静态淘宝页面
  17. 理论+实操: MySQL索引与事务、视图、存储过程(软件开发用的多)、存储引擎MyISAM和InnoDB
  18. Nginx(4)之搭建图片服务器
  19. 音乐微信小程序源码php版,仿QQ音乐的微信小程序
  20. md文件 linux,MD 文件扩展名: 它是什么以及如何打开它?

热门文章

  1. VB6+Winsock编写的websocket服务端
  2. 运行sqlplus时遇到cannot restore segment prot after reloc: Permission denied
  3. 采购订单的审批状态异常的处理,审批状态为:预审批或是处理中的单据
  4. iBatis 配置文件详解
  5. c语言未命名exe,用dev-c++编译出现问题,求大神解答啊
  6. linux shell写的文件断网没保存
  7. 牛客华为机试第1题python
  8. 求1-2+3-4+5......99的所有数的和
  9. 当心花招,关注全闪存性能
  10. 用 Python 实现打飞机,让子弹飞吧!