在游戏世界的寻路中,通常会有这样一种情况:在小地图上点击目标点时,点击到了障碍物或者建筑上,然后游戏会提示我们目标地点无法到达。玩家必须非常小心的在小地图上点击目标区域的空白部分,才能移动到目标地点。那么,有没有办法来改进一下这种不友好的体验呢?
下面给出两种方法:
最近可达点替代方法是在寻路之前的一个预处理,首先需要检测目标点是否可达,如果不可达则需检测最近的一个可达的点作为目标点。其情形如下:
从A点到S的寻路中,发现S点不可达时,在S点周围一圈的点中搜寻到R,使用R点作为替代目标点。如果A点需要移动到S2点呢?我们在检测附近可达点的时候,引入一个变量depth,表示不可达点的深度,意为从S到最近的可达点需要搜寻的圈数。
代码延续前一篇文章A*寻路算法之解决路径多拐点问题中的A*寻路算法,搜索替换点方法实现如下:
public Node getNearestReachableNode(Node node, int maxDepth) {
if (maxDepth <= 0 || map.canReach(node.x, node.y)) {
return node;
}
for (int depth = 1; depth < maxDepth ; depth++) {
for (int i = node.x - depth; i <= node.x + depth; i++) {
// 左
if (map.canReach(i, node.y - depth)) {
return new Node(i, node.y - depth);
}
// 右
if (map.canReach(i, node.y + depth)) {
return new Node(i, node.y + depth);
}
}
for (int i = node.y - depth + 1; i < node.y + depth; i++) {
// 上
if (map.canReach(node.x - depth, i)) {
return new Node(node.x - depth, i);
}
// 下
if (map.canReach(node.x + depth, i)) {
return new Node(node.x + depth, i);
}
}
}
return node;
}
最近点检测,基本的想法就是在寻路过程中不断检测加入到openList中的点与目标点的距离。当无法寻路到目标点时,可以移动到这个最近点上。下面直接上代码:
public List findPath(Node startNode, Node endNode) {
int minDistance = Integer.MAX_VALUE;
Node nearestNode = startNode;
newOpenList.add(startNode);
Node currNode = null;
while ((currNode = newOpenList.poll()) != null) {
removeKey(openSet, currNode.x, currNode.y);
addKey(closeSet, currNode.x, currNode.y);
ArrayList neighborNodes = findNeighborNodes(currNode);
for (Node nextNode : neighborNodes) {
// G + H + E
int gCost = 10 * calcNodeCost(currNode, nextNode) + currNode.gCost
+ calcNodeExtraCost(currNode, nextNode, endNode);
if (contains(openSet, nextNode.x, nextNode.y)) {
if (gCost < nextNode.gCost) {
nextNode.parent = currNode;
nextNode.gCost = gCost;
nextNode.fCost = nextNode.gCost + nextNode.hCost;
}
} else {
nextNode.parent = currNode;
nextNode.gCost = gCost;
nextNode.hCost = 10 * calcNodeCost(nextNode, endNode);
nextNode.fCost = nextNode.gCost + nextNode.hCost;
newOpenList.add(nextNode);
addKey(openSet, nextNode.x, nextNode.y);
// 检测是否是当前最近点
int distance = Math.abs(nextNode.x - endNode.x) + Math.abs(nextNode.y - endNode.y);
if (distance < minDistance) {
minDistance = distance;
nearestNode = nextNode;
}
}
}
if (contains(openSet, endNode.x, endNode.y)) {
Node node = findOpenList(newOpenList, endNode);
return getPathList(node != null ? node : nearestNode);
}
}
Node node = findOpenList(newOpenList, endNode);
return getPathList(node != null ? node : nearestNode);
}
大概修改10行左右的代码,就可以在寻路的过程中解决目标点不可达的问题。
1.目标点替代法无法解决寻路到封闭地形的问题,而最近点检测方法可以移动到附近的点R( 如左图)。因此,最近点检测方法一定会生成一条路径,而目标点替代法不一定会有。
2.一般情况下,当目标点无法移动过去时,最近点检测方法往往会搜索地图上的所有点,而目标点替代法通过找最近可达点可以解决这个问题。不过在如左图所示回字形地图上,标点替代法同样会搜索地图上的所有点。
3.目标点替代法搜索到的可达点,可能不是在起点和中间之间,可能使最终生成路径变得稍微有点怪异。而最近点检测方法生成的路径则很自然。如右图所示,由于目标点替代法查找周围的可达点时,是按照一定的顺序去搜索的。那么就可能出现目标点是S,却走到R1上去了,而很自然的路径应该是走到R点上。这一点在格子大小很小的地图中表现不是非常明显,但是在格子很大的地图上表现的差异感就很强烈了,甚至不能接受了。不过,我们可以通过额外的方法消除这个问题-- 在搜索每一圈的可达点的时候,计算该圈上每一点到起始点的距离大小,选择距离最小的可达点作为替代点。