转自:http://gamasutra.com/blogs/AndrewErridge/20180522/318413/Group_Pathfinding__Movement_in_RTS_Style_Games.php

On and off for the last 5 years I’ve worked to improve grouped unit movement in an RTS-style game called The Maestros. As Dave Pottinger pointed out almost 20 years ago after his work on Age of Empires, the “pathfinding” part of movement gets all the attention, but making dozens of units follow a path intelligently is at least as important and quite difficult. I’d love to tell you about my... journey in this space.

What follows are by no means state of the art solutions. The industry’s had excellent minds on this problem for over two decades, and you and I have little hope of catching up over an afternoon coffee. So let’s focus on the nitty-gritty details of making basic pathfinding look and feel good for players under practical game constraints. A practical knowledge of 3D math is assumed, but a phD in AI is not recommended. I’d probably just upset you, honestly ;)

Goals

In RTS movement, some players want realistic, slow-rotating tanks and squads of infantry hustling together like Company of Heroes. Our game is about executing big plays in quick brawls so our priorities were “responsive over realistic,” and “direct-control over coordinated formations.” Think more Starcraft than Age of Empires.

Where We Started

UDK (Unreal Engine 3’s SDK) supports A* pathfinding in a navmesh-based space, and has pretty effective (if finicky) navmesh generation.  Unfortunately, pathfinding was implemented almost entirely in unreachable engine code which we could not modify in UDK. All in all, if I selected a single unit and right clicked a pathable location, I could expect it to get there eventually.   Both Unity and UE4 have about the same level of support today though tuned a bit better (though UE4 offers better low-level access).  I thought we were pretty well covered with that. Boy, was I wrong.

Problem #1 - Stopping the Group

The next step is moving a group. If I select a few of our Doughboy units and ask them to move to the exact same location, only one of them is going to actually make it there. At best, the others will be adjacent to that one who made it. So how do they know when they’re done moving? Two clicks and we’ve already hit our first issue!

What we came up with was a sort of message-passing system. The first guy who got there was set to have reached the destination, and anybody who touched him and was also trying to get to the same place would consider himself at his destination. Then those guys could pass that message on to anybody who bumped them. We called this “transitive bumping.” This felt pretty clever, and works well for clustered groups, but still has some silly degenerate cases (e.g. if units are in a line).

Problem #2 - Moving Through the Crowd

Another issue we ran into early on was one unit being blocked by another. While UDK’s pathfinding supported creating new obstacles in the navmesh, doing it for a couple hundred units who were constantly changing their location resulted in unplayable performance. Because of this, units were always trying to move through one another instead of around.

Our solve was to allow units to apply a force to one another under certain conditions. This also needed to propagate throughout the group like our stopping messages.

A more natural looking solution might be to tell the unit to move themself out of the way (a la Starcraft 2), and then to move themselves back. In either case, determining the exact states/conditions to “push” another unit was incredibly complex and error-prone. “You can push Allies but not enemies, idle units but not attacking units.” In our case, it took ~10 unique clauses with various levels of nesting to achieve. Yikes! I’d love to find a more generic solve here.

Problem #3 - Staying in Your Lane

After our first public demo of The Maestros at GDC in 2014, I received some feedback from a mentor of mine that the game felt “messy.” Plenty of things contributed to this at the time, but the problem that was most at fault was that even simple, straight-line movements had units jockeying for position along the same path. Nobody would expect a real-life crowd to do that, and certainly not a group of military-trained robots. All of our units were still acting completely independently. When they received a single, common destination from a player’s click and tried to get there on their own fastest route, they’d often choose the same route as the guy next to them. The result was about as graceful as all 8 lanes of the 405 freeway collapsing into one lane instantaneously.

The general solution to this isn’t terribly hard. Calculate a center point for the current group, take the difference of each unit’s position from that center point, and issue a bespoke move command for each unit with their offset from the destination.

For units A, B, & C, and a clicked location (red reticle), offset each destination

That worked great for the basic case of moving a unit cluster from one open area to another, but as you’ll begin to learn in this article - most of the “general” feeling solutions have conditions where they break down. The most obvious is if you try to move next to an obstacle. As you can see below, the center point is fine, but unit C would be inside a boulder (gray box).

Another issue was that if your units were spread out and you clicked near the center, you’d expect them to collapse inwards. Using a naive offset, however, they’d generally stay put. Offsetting the destination also fails to meet expectations if your units are too spread out. For example, you’ve all your units in one cluster, but your commander (unit A) was off solo farming 2 screens away. When you issue a move to a point near the center of the cluster, you’d expect all your units, including your commander, to end up generally underneath your cursor (red reticle). In fact, none of them end up under your cursor if you apply offsets naively.

Summarizing many issues in one sentence, “There are situations where some or all units should collapse together, not maintain their offset from the group’s center.” The idea of determining who is in a group or not can sound a bit daunting, and certainly there are some complex clustering algorithms that could be applied here. My solution to this problem ended up being much simpler and has been unexpectedly effective across a huge number of scenarios. Here’s the rundown:

Borrowing language from our code, I calculate a “SmartCenter” for the group

  1. Calculate the average position of all units in the group
  2. Remove any units that aren’t within 1 standard deviation of that average
  3. Recalculate the average position from that subset of the group

If the point we are trying to reach is within a standard deviation of the center point, I use naive independent movement. This guarantees that units will gather shoulder-to-shoulder in a tight cluster, and gives players the kind of direct control of the group shape we’re looking for in The Maestros.

If I don’t have a meaningful “Primary Cluster,” then my units are probably spread out all over the map. In this situation, I just want them to regroup as best they can. Another win for naive independent pathfinding. I detect this situation when the standard deviation for the group is larger than a particular maximum. Ideally, that maximum is relative to the area occupied by the group so I used the sum of all unit’s radii. That’s been reasonably effective.

If I have a “Primary Cluster,” but 1 or more units are more than 1 standard deviation from the group’s center, I collapse them in by giving them a destination in the direction (i.e. normal) of their offset, but only a standard deviation’s length (i.e. magnitude) away from the group’s central destination. This has the effect of “collapsing back in” and feels much more natural.

Problem #4 - Sticking Together

Overall applying relative offsets to each unit’s destination was a huge win for the “cleanliness” of movement within our game when moving in a straight line. Pathing around obstacles was still abysmal though. First, units will take their own shortest path around an obstacle, and don’t always stick together with their group. Second, our 8-lane to 1-lane traffic jam happens all over again at each intermediate point before we reach our destination (see second image).

Not pathing together

Traffic jam on intermediate points

I sat on this problem for an embarrassingly long time without a good answer. On day one, I thought to pick 1 unit’s path, and apply the offsets to each intermediate point. This breaks down quickly when you consider that often the reason you’re pathfinding in the first place is that your going tightly around an obstacle. Applying the offsets will leave 50% of your units trying to path into a rock, and naive independent pathfinding will cause a permanent gridlock before you even get near your final destination.

My conceptual answer to this setback wasn’t terribly clever either (depicted below). I’d move the path away from the corner, about one radius width. Determining this mathematically on the other hand proved incredibly elusive to me. How do I determine whether my path is cornering close to an obstacle or far away? If I am close, is the obstacle on my left or my right? On what axis is my left or my right for a given point in my path?

At some point I was going to have to do a raycast to locate obstacle volumes. Perhaps I could try raycasting radially around each point (pictured below)? Unfortunately it was prone to missing the obstacle entirely. The accuracy of this solution scaled directly with the number of raycasts I did per point on the path, and that felt terribly inefficient.

What I really needed was the left-right axis for a given turn. The hypothesis is that the angle of the turn is telling you about where you obstacle likely is. Most of my obstacles where going to be directly inside the “elbow” of my vectors, and occasionally outside it. I hit a breakthrough when I found the axis through the following operations:

  1. Generate the vectors for relative movement between points - For each pathfinding point B, subtract its predecessor, A, to get the vector from A -> B

  2. For each pair of subsequent vectors, A & B, add them to get a vector C that goes from the beginning of A to the end of B
  3. Cross C with an up/down vector to get a vector P, that bisects the area between A & B.

The vector P is the right/left axis for my turn! I check for obstacles on either side and shift my pathfinding point away from the obstacle by a little more than 1 standard deviation. The result goes from a path (green) directly on top of my obstacle, to one comfortably offset from it.

Before

After

Now, I can apply my offsets at the updated points along my path so my group can stick together as they path, and they won’t traffic jam. It doesn’t cover every situation, but in ~90% of cases we can get by without traffic-jamming. The improvement is enormous. Here’s a before & after of going around just one corner.

Before

After

Learnings

My biggest learning from doing this is that “generalized” pathfinding algorithms like A* are unlikely to be the whole movement story for your game, especially if you’re trying to coordinate a group’s movement. The second thing I learned is that complexity is truly the enemy here. Pathfinding isn’t hard because the pathfinding algorithms are complex. A tight A* implementation is easily less than a hundred lines of pretty readable code, and is perfectly serviceable for most games. Pathfinding is hard because moving multiple units in real-time and space with one another produces an incredibly large volume of scenarios, and humans have pretty specific expectations of what should happen in many of those scenarios.

扩展参考:

1、http://www.red3d.com/cwr/steer/gdc99/

2、http://onemanmmo.com/index.php?cmd=newsitem&comment=news.1.179.0

3、https://www.researchgate.net/publication/224705309_The_Corridor_Map_Method_Real-Time_High-Quality_Path_Planning

4、https://pdfs.semanticscholar.org/a921/86a365a2e5c407f98efdbd95cf412f91a6e5.pdf

Group Pathfinding & Movement in RTS Style Games的更多相关文章

  1. CG&Game资源(转)

    cg教程下载: http://cgpeers.com http://cgpersia.com http://bbs.ideasr.com/forum-328-1.html http://bbs.ide ...

  2. Lua的各种资源1

    Libraries And Bindings     LuaDirectory > LuaAddons > LibrariesAndBindings This is a list of l ...

  3. DOM Scripting -- Web Design with JavaScript and the Document Object Model 第2版 读书笔记

    1. childNodes  nodeValue <p id="p1">hello p!</p> alert(document.getElementById ...

  4. iOS 3D Touch实践

    本文主要讲解3DTouch各种场景下的开发方法,开发主屏幕应用icon上的快捷选项标签(Home Screen Quick Actions),静态设置 UIApplicationShortcutIte ...

  5. 《JavaScript DOM编程艺术(第二版)》读书总结

    这本书是一本很基础的书,但对于刚入前端不久的我来说是一本不错的书,收获还是很大的,对一些基础的东西理解得更加透彻了. 1.DOM即document object model的缩写,文档对象模型,Jav ...

  6. JavaScript DOM 编程艺术·setInterval与setTimeout的动画实现解析

    先贴上moveElement()函数的大纲,为了方便观看,删了部分代码,完整版粘到文章后面. function moveElement(elementID,final_x,final_y,interv ...

  7. JavaScript DOM编程艺术读书笔记(四)

    第十章 实现动画效果 var repeat = "moveElement('"+elementID+"',"+final_x+","+fin ...

  8. 通过百度echarts实现数据图表展示功能

    现在我们在工作中,在开发中都会或多或少的用到图表统计数据显示给用户.通过图表可以很直观的,直接的将数据呈现出来.这里我就介绍说一下利用百度开源的echarts图表技术实现的具体功能. 1.对于不太理解 ...

  9. ZK框架的分析与应用

    前言:本文是在下的在学习ZK官方文档时整理出来的初稿.本来里面有很多的效果图片和图片代码的.奈何博客园中图片不能粘贴上去,所以感兴趣的筒子们就将就吧.内容中,如有不好的地方,欢迎斧正! ZK框架的分析 ...

随机推荐

  1. mac上配置java开发环境

    项目在mac上跑起来的步骤: 1. 访问,https://brew.sh/  装上这个然后  brew install git  brew install maven, settings.xml需要放 ...

  2. vim 插件 -- NERDTree

    介绍 NERDTree 插件就是使vim编辑器有目录效果. 所谓无图无真相,所以直接看这个插件的效果图吧. 下载 https://www.vim.org/scripts/script.php?scri ...

  3. python-模块2

    from collections import namedtuple # # 类 # p = namedtuple("Point", ["x", "y ...

  4. js 异常判断

    /** * 预处理response * @param rep */ function validateRep(rep) { try{ if(rep.status==false){ layer.open ...

  5. 用IDEA时,类/方法提示"class/method **** is never used"

    https://segmentfault.com/q/1010000005996275?_ea=978306 清理下缓存试下.在 File -> Invalidate Caches 下,会重启 ...

  6. 【oracle入门】数据模型

    数据模式也是一这种模型,它是数据库中用于提供信息表示的操作手段的形式架构,是数据库中用来对现实世界惊喜抽象的工具.数据模型按不同的应用层次分为3种类型,分别为概念数据模型.逻辑数据模型.物理数据模型. ...

  7. springboot打war包后部署到tomcat后访问返回404错误

    springboot打war包后部署到tomcat后访问返回404错误 1.正常情况下,修改打包方式为war <packaging>war</packaging> 2.启动类继 ...

  8. C/C++ 宏技巧

    1. C 也可以模板化 #define DEFINE_ARRAY_TYPE(array_type_, element_type_) \ static inline int array_type_ ## ...

  9. Python全栈之路----常用数据类型--集合

    集合(set):无序的,不重复的数据组合,它的主要作用如下:  · 去重,把一个列表变成集合,就自动去重了  · 关系测试,测试两组数据之间的交集.差集.并集等关系 1.基本操作:修改,删除,合并 & ...

  10. 《从Lucene到Elasticsearch:全文检索实战》学习笔记三

    今天我给大家讲讲倒排索引. 索引是构成搜索引擎的核心技术之一,它在日常生活中是非常常见的,比如我看一本书的时候,我首先会看书的目录,通过目录可以快速定位到具体章节的页码,加快对内容的查询速度. 文档通 ...