集群寻路问题的一些思考和提炼

以下是不使用引擎内嵌的寻路,自己写n个单位寻路时可能会用上的寻路手段

a星在大量单位寻路时消耗比较大,

Navmesh和大规模的单位进行寻路时,单位和单位之间的位置表现总是有一些问题

(表现为太过规律或挤在一起,这篇博文介绍了一些可能有用的优化思路,让单位移动看起来更舒服以及符合常理的移动表现)

 

问题1

停止一组单位的寻路.n个单位有同一个目的地时,容易出现单位重叠的问题,若用AABB,OBB等碰撞判定去处理性能可能会比较耗

问题1解决方案

可以在单位之间通过消息传递系统进行通知,第一个到达目的点的单位,标记为到达,之后向这个位置移动的单位碰到了这个单位(用数据层的圆形触发器,不用引擎自带的的),则第一个单位会通知第二个单位,使第二个单位停止下来,之后依次的n个单位通知停止

 

问题2

单位1移动时被一群单位挡住的问题

问题2解决方案

可以给每个单位赋予质量和力,单位间相互碰撞时相互施加力,然后这些力的施加可以在一个组内被传播(消息通知)

效果为大单位能冲散一群小的单位,能提升效果表现,因为只有一些简单的力判定,猜想性能问题不大

 

问题3

一组单位聚在一个范围内一起移动,但又不能太聚拢,同时需要动态的避开障碍物,避开障碍后表现上这个组又能合并在一起移动

问题3解决方案

计算这个组的平均中心位置

排除掉离散度最高(距离中心最远)的一些单位(动态控制这个离散)

再次计算这个组的平均中心位置

控制离散度高的一些单位向这个组的中心靠拢(靠拢需要动态控制,否则可能会出现挤压内部离散度低的单位的情况)

烘培阻挡信息,点击地图寻路时,在单位移动前先记录下来这个移动方向可能会碰到的障碍物区域范围(只需要一个大致的范围,太过具体可能会耗费性能,需暴露参数,用于后续调整),然后根据移动策略绕开这个障碍区域

 

问题4

绕过障碍物

问题4解决方案

如果是每个单位向各个方向发n个射线,那m个单位就是n*m个射线,性能消耗会比较高,以下是2个可能的解决方法

1.预先烘培障碍信息,在障碍物边缘做标记,从标记点向每个单位发射线,如果是在障碍物左右两边进行标记,则每个单位根这个障碍物会有2条射线组成一个有一定夹角的区域,移动时单位可能判定移动方向在这个夹角内,则向偏离这个角度的方向移动,这样就能绕开障碍,同时在寻路时将要碰到的障碍物的大致范围框定出一个射线区域。

这个方案有点射线追踪的感觉

复杂度为n个标记*m个单位

2.类似下面原文中的一张图,根据一定的算法,将寻路分为2段,以障碍物前方定点为中心,单位当前位置到障碍物前方一定距离的某一点为第一段,障碍物前方某一点到目的地为第二段。群组单位在寻路时,每个单位都向行进方向发一条射线,判断这条射线是否指向这个夹角内,没有指向夹角或者偏移过大则纠正方向。

复杂度为n个单位*2段距离上检查夹角偏移和纠正方向的消耗

 

---------------------------------------------

以下英文部分

转自: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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章