MMORPG战斗系统随笔(二)、浅谈场寻路Flow Field PathFinding算法
转载请标明出处http://www.cnblogs.com/zblade/
今天给大家带来一篇游戏中寻路算法的博客。去年,我加入一款RTS的游戏项目,负责开发其中的战斗系统,战斗系统的相关知识,属于游戏中比较繁杂的部分。今天就说说其中的寻路的实现思想,当然,由于牵涉工作保密,我不会贴出核心代码,那么就用简单的意向代码表达核心思想即可:D
博客写的慢,马上又要国庆了,下一篇等我国庆放完假再回来更新吧~
一、游戏中的常用寻路算法
说到寻路算法,很多人的第一反应就是A*算法,是的,这是正常的反应,而且A*已经在一些游戏中论证了其实用性。无论是基本的A*算法还是后期基于A*的优化算法,以及延伸出来的变异算法,比如B*、JPS算法等,都对游戏的寻路算法做到了极大的推动作用。下面我会给出一个基本优化版本的A*算法原理及初步代码。
在一般的单机游戏中,或者ARPG,亦或是优化比较好的MMORPG游戏中,基于A*优化的寻路都可以满足基本的寻路要求。但是在多人RTS游戏中,如果同屏几十上百个玩家,每个玩家进行一次寻路,就会消耗10-20ms左右,那么累积下来的时间损耗就会很大。而我做的RTS游戏又是一款帧同步的游戏,如果我们锁定帧数为30/s,那么游戏中的对象每帧的更新频率在33ms左右,如果分出10-20ms用于寻路,那么,只能看游戏对象寻路了,其他的战斗系统都运行不起来了:D
基于这样的考虑,只有Pass通用的A*算法,我们采用的是Unity3D的游戏引擎,那么,自然就会考虑下一个对象:Unity中自带的Navigation寻路。这确实是一个比较优秀的寻路组件,unity内部封装的组件,而且做了一系列的优化,执行的效率比较高。具体的Navigation介绍,可以参考这篇文章:unity navigation社区index/,, 这儿,我就不过多的深入讨论这个寻路组件了。由于我们采用的是帧同步的同步机制,对于每个游戏对象的计算都是在各个客户端计算,为了确保计算结果的一致,需要确保各自的寻路计算是一致的。如果采用navigation,那么不一定能确保在不同的机型下计算得到的寻路节点一致,如果出现寻路节点不一致,就会在不同的机型上出现同一个游戏对象不同的寻路行为,从而引发一系列的不同步,最终这局游戏被判定为作弊Orz
文章说到这儿,也许有的同仁会给出其他的寻路解决办法,可以留言在下面,一起学习。
接着说,这时候否定了Unity自带的navigation组件,感觉思维似乎走到一个节点了。如果深入的优化A*,最终效果也似乎在10ms左右,不能稳定的符合游戏的设计。这时候,偶然发现一个论坛,其中讨论了《军团要塞》这款游戏的寻路。军团要塞游戏中,也是类似的RTS游戏,会出现同屏几十上百个战斗单位,进行寻路和AI操作。其中的寻路算法就是V社自己提出的场寻路算法Flow Field PathFinding。不得不说RTS游戏对于寻路算法的贡献是不可忽略的,针对多人同屏操作的性能优化,其中的重头戏就是寻路的优化。这儿给出查阅到的场寻路算法的链接:flow-field-pathfinding/
在这篇文章中,大概讲述了场寻路算法的思想,当然我没有逐字细看,不过其基本思想大概了解,基于此,我进一步查找了相关的算法,找到国外的一个RTS论坛,得到一些启发:如何开启一款RTS游戏, 在其中提到了Flow Field算法,并给出一定的实现代码。虽然并不是完全的代码,而且由于和AI控制中的steeringBehaviour混合在一起,初期还有一定的迷惑性,后来我结合项目的基本节点刷新,利用其中提到的Dijkstra算法的进一步优化,总算推导出文章中所提到的寻路思想。
二、两种寻路算法的基本原理和实现代码
1、A*算法及其优化
这儿说的A*算法,是针对格子类型的地图的寻路算法,现在有很多讲解A*算法的文章,可以参考很多的文章,其核心的思想是对两张列表的操作,开放表openset和关闭表closeset,用一个列表pathSet作为路径点列表。其基本的函数: F = G + H, 这儿就不再赘述,可以查看相关的算法讲解。
这儿,我就用大概的思维代码来列举A*的基本过程吧:
1) 设置初始点 startPoint, 目标点endPoint,地图相关的数据:宽度width,高度height,地图对应的各个格子的阻挡与否数据mapData;
2) 首先将初始点塞入开放表openset,计算其F,如果对计算不做优化,H可以用哈曼计算,计算其直线距离即可;
3) 做一个循环判断,判断条件是openset表中元素个数为0,此时跳出判断;
4) 进入循环判断,取出openset表的第一个元素current,判断其是否为重点,如果是,则寻路完成,对路径表pathset进行操作,得到最后的路径链表;
5) 将该点从openset表移除,塞入到closeset中,更改相关标记;
6) 以current为中心,寻找周边8个方向(具体方向上下左右及四个对角方向),在取点的时候做一个可行走判断,只塞入可行走的区域点,得到neighborPoints;
7)对neighborPoints点进行逐个计算和判断,首先,不在closeset表中,然后计算当前标准点current到当前节点neighbor代价F1,如果小于当前节点neighbor的代价,则设置为新的路径点,进行下一个操作8)
8)在该操作中,对于当前节点neighbor,首先将当前标准点current,然后更新当前节点neighbor的消耗为F1,更新其消耗F为F1+H(neighbor,endPoint), 接着进行下一步操作9)
9)在这步操作中,主要是当前节点neighbor塞入到openSet中,然后即可进行一次堆排序,当然是最小堆排序,这样保证openSet的第一个元素永远是消耗最小的节点。返回到步骤7)中
10)步骤7的neighborPoints都遍历完后,返回到步骤4)
最后,要么我们找到了到目标点的路径,要么没有路径点,这就是整个A*的算法过程,其中的优化一个是采用数据结构的方式,动态设置每个节点在openSet还是closeSet,一个是采用堆排序的方法保持openSet的第一个元素一定为消耗最小的临近点。
下面我就再次用我的灵魂写法,写一个基本的代码思路吧, 哈哈:
A* F = G + H 1. startPoint/endPoint/width/height/mapData 初始化
2. openset/closedset/pathSet 初始化
3. startPoint->openset
4. while openset.length > 0 基本循环
5. current = openset[1] 取首节点
6. if current == endPoint then 判断
7. find the path and get the key points from pathSet and return the key points 获取节点
8. insert the current into closedset and change it's state
9. get the neighborNodes based on the current
10. foreach neighbor in neighborNodes 判断
11. if neighbor not in closedset and G_cur->neighbor < G_neighbor
12. pathSet <- current
13. G_neighbor <- G_cur->neighbor
14. F_neighbor <- G_neighbor + H
15. insert the neight into openset
16. sort the openset immediately with the MinHeap algorithm
17. if the openset.length <0 and get none the key points to endPoint, return fail
基本的思路就是这样,网上也有很多成熟的代码,可以搜索后参考实现即可。
2、Flow Field PathFinding算法原理及其实现
说到Flow Field PathFinding算法,就需要补充说一下Dijkstra算法。基于A*算法,在搜寻节点的时候,是不需要遍历所有的节点的,只需要能够在openset中找到目标点,那么这次寻路就算结束。Dijkstra算法,实现的思路是对所有的节点都执行一次寻路消耗计算,最终得到所有地图上可达点到目标点的消耗。基于这样的消耗值,我们可以进一步的绘制一张各个点到目标点的路径的矢量图,这样进一步就可以得到我们需要的各个点到目标点的矢量路线。整体的算法思想,就是这样,接下来可以用简易代码来表现Dijkstra算法的过程,基于这样的过程,不难推导出后续的矢量图绘制和寻路操作。
1、设置两张表openset和closeset,将目标点goal塞入;
2、从openset中取出第一个元素,寻找8个方向的相邻点;
3、对寻找出来的8个临近点,如果其不在closeset中,则计算更新其消耗,然后塞入到openset中
4、重复2、3步,最后更新得到的路径点,其中的距离就是从当前点到目标点需要多少步的值。(这儿的步,是八个方向的,分为上下左右和四个斜方向)
5、基于第四步得到的新的路径点数据,可以进一步的生成一张矢量图。矢量,顾名思义,就是一个点指向另一个点的操作。
还是用代码写过程吧,感觉还是习惯了写我的灵魂代码:D
、地图生成
openset/closeset初始化
while openset.length >
get the first node of openset and remove from openset and insert into closeset
get the eight neighbor node of the current node
search all the neighborNodes
if not in closeset
get the cur distance of the node
if the distance is original or bigger than the current distance +
update the distance to the current node distance +
insert the node into the openset over the search and get the map that all the valid node has newly distance to the goal based on the map, create the vector map of all the node 、路径生成
get the node position of the start point from the vector map
get the minimum point of the node
repeat until get the goal point
revert the finding process, get the path!
经过数据的分析对比,整个场寻路的时间消耗,主要在最初的Dijkstra算法生成地图的过程,这个过程会随着地图的变大而变长,当然,这个过程可以在前期的游戏加载中就进行,这样的时间嵌入到加载时间中,显得影响不大。那么基于生成的矢量图寻路的过程,是极其迅速的,测量了100*100的地图,寻路的过程不到1ms,基本可以忽略不计,这样快速高效的寻路算法,简直对于特定场合的寻路应用是极其诱惑的。
有利也有弊,场寻路算法带来极其快捷的寻路,其对应的限制就是矢量图一次生成后,不能再次修改,如果需要重新设置目标点,那么需要重新生成一张新的矢量图。在大量单位模拟同类操作的过程中,可以用这样的寻路算法来解决大量单位带来的寻路消耗。在对玩家自由操作寻路的游戏类型中,显然不如A*或者JPS等寻路算法。所以,具体的寻路算法,是需要结合实际的游戏应用场景来实现的,只有最优最合适的算法:D
好了,今天关于场寻路算法就说到这儿,后面会接着写一些AI相关的文章,下篇文章见
MMORPG战斗系统随笔(二)、浅谈场寻路Flow Field PathFinding算法的更多相关文章
- 浅谈DFS,BFS,IDFS,A*等算法
搜索是编程的基础,是必须掌握的技能.--王主任 搜索分为盲目搜索和启发搜索 下面列举OI常用的盲目搜索: 1.dijkstra 2.SPFA 3.bfs 4.dfs 5.双向bfs 6.迭代加深搜索( ...
- MMORPG战斗系统随笔(一)
前言 很久没有更新博客,中间迁移过一次博客,后来一直忙于项目的开发,忙的晚上回去没时间写博客,周日又要自我调整一下,所以空闲了很久没有继续写博客.最近终于慢慢放慢节奏,项目也快上线了,可以有空写一些个 ...
- MMORPG战斗系统随笔(一)、战斗系统流程简介
前言 转载请标明出处http://www.cnblogs.com/zblade/ 很久没有更新博客,中间迁移过一次博客,后来一直忙于项目的开发,忙的晚上回去没时间写博客,周日又要自我调整一下,所以空闲 ...
- MMORPG战斗系统随笔(三)、AI系统简介
在设计一款游戏的时候,如果我们是玩家,是希望自己能够操作角色畅玩游戏的.在一款MMORPG游戏中,大部分的实际游戏角色,是需要玩家来操作的,通过在游戏大世界相互完成游戏中的任务等等来体验游戏.在大世界 ...
- MMORPG战斗系统随笔(四)、优化客户端游戏性能
转载请标明出处http://www.cnblogs.com/zblade/ 说到游戏性能,这是一个永恒的话题.在游戏开发的过程中,性能问题一直是我们研发需要关注的一个节点.当然,说句客观话,很多程序员 ...
- BizTalk开发系列(三十二)浅谈BizTalk主机性能优化
很多BizTalk的项目都要考虑到性能优化的问题,虽然BizTalk采用多线程处理消息的,大大提高了程序效率.但默认情况下 BizTalk的主机有很多阻止参数会控制BizTalk对服务器的资源使用率, ...
- 浅谈双流水线调度问题以及Jhonson算法
引入:何为流水线问题 有\(n\)个任务,对于每个任务有\(m\)道工序,每个任务的\(m\)道工序必须在不同的m台机器上依次完成才算把这个任务完成,在前\(i-1\)道工序完成后才能去完成第\(i\ ...
- 【转】浅谈对主成分分析(PCA)算法的理解
以前对PCA算法有过一段时间的研究,但没整理成文章,最近项目又打算用到PCA算法,故趁热打铁整理下PCA算法的知识.本文观点旨在抛砖引玉,不是权威,更不能尽信,只是本人的一点体会. 主成分分析(PCA ...
- 手撸ORM浅谈ORM框架之基础篇
好奇害死猫 一直觉得ORM框架好用.功能强大集众多优点于一身,当然ORM并非完美无缺,任何事物优缺点并存!我曾一度认为以为使用了ORM框架根本不需要关注Sql语句如何执行的,更不用关心优化的问题!!! ...
随机推荐
- 挖个坑,写一个Spring+SpringMVC+Mybatis的项目
想挖个坑督促自己练技术,有时候想到一个项目,大概想了一些要实现的功能,怎么实现.现在觉得自己差不多能完成QQ空间的主要功能了.准备立个牌坊,写一个类似功能的网站.并且把进度放到这里来. 初步计划实现以 ...
- 如何使用 Weave 网络?- 每天5分钟玩转 Docker 容器技术(63)
weave 是 Weaveworks 开发的容器网络解决方案.weave 创建的虚拟网络可以将部署在多个主机上的容器连接起来.对容器来说,weave 就像一个巨大的以太网交换机,所有容器都被接入这个交 ...
- python 之 计数器(counter)
Counter是对字典类型的补充,用于追踪值的出现次数. ps:具备字典的所有功能 + 自己的功能 c = Counter('abcdeabcdabcaba') print c 输出:Counter( ...
- .NET CORE 学习笔记之安装EF【Microsoft.EntityFrameworkCore】扩展报错
最近在学习.NET CORE ,刚开始就遇到问题了. 安装EF框架的试试就报错, 报错如下: 错误 程序包还原失败.正在回滚“XXX”的程序包更改. 找了好久的方案,网上也没搜到对应的问题和方案,然而 ...
- 从源码分析java.lang.String.isEmpty()
今天在写代码的时候用到了java.lang.String.isEmpty()的这个方法,之前也用过,今天突发奇想,就看了看源码,了解了解它的实现方法,总结出来,大家可以交流交流. 通常情况下,我们使用 ...
- 对Java的数据类型和运算符的理解
我知道千里之行始于足下,包含着对编程的兴趣,希望能够在这个平台上记录下我学习过程中的点点滴滴! Java的基本构造 标识符和关键字 标识符规则 标识符就是用于给程序中变量,类.方法命名的符号 1.标识 ...
- Linux-chown命令(1)
chown [chang owner]:更改文件的属主,也就是指定文件的拥有者改为另一个指定的用户或组. 命令格式: chown [选项]... [用户][:[组]] 文件... 例子: sudo ...
- CCNA基础知识摘录
cisco设备的启动要点: 1.检测硬件(保存在rom) 2.载入软件(IOS)(保存在Flash) 3.调入配置文件(密码,IP地址,路由协议都保存在此)(此文件保存在NVRAM) 0x2102:正 ...
- 201521123083 《Java程序设计》第7周学习总结
1. 本周学习总结 以你喜欢的方式(思维导图或其他)归纳总结集合相关内容. 参考资料: 2. 书面作业 1. ArrayList代码分析 1.1 解释ArrayList的contains public ...
- 个人作业1——四则运算题目生成程序(基于C++)
题目描述: 从<构建之法>第一章的 "程序" 例子出发,像阿超那样,花二十分钟写一个能自动生成小学四则运算题目的命令行 "软件",满足以下需求: 1 ...