A*寻路算法之解决目标点不可达问题

在游戏世界的寻路中,通常会有这样一种情况:在小地图上点击目标点时,点击到了障碍物或者建筑上,然后游戏会提示我们目标地点无法到达。玩家必须非常小心的在小地图上点击目标区域的空白部分,才能移动到目标地点。那么,有没有办法来改进一下这种不友好的体验呢?

下面给出两种方法:

  1. 最近可达点替代:当目标点S不可达时,在S点周围寻找一个最近的可达点R,让R替代S作为目标点寻路。
  2. 最近点检测法:设置一个最小距离,寻路过程中,不断检测每个加入openList的点,如果该点到目标点S的距离小于最小距离,保存当前点为N,并修改最小距离。在寻路结束时,如果目标点S无法到达,那么将最接近点N作为寻路的终点。

1.最近可达点替代

最近可达点替代方法是在寻路之前的一个预处理,首先需要检测目标点是否可达,如果不可达则需检测最近的一个可达的点作为目标点。其情形如下:

  

从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;
	}

2.最近点检测法

最近点检测,基本的想法就是在寻路过程中不断检测加入到openList中的点与目标点的距离。当无法寻路到目标点时,可以移动到这个最近点上。下面直接上代码:

	public List<Node> 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<Node> 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行左右的代码,就可以在寻路的过程中解决目标点不可达的问题。

3.两种方法对比

1.目标点替代法无法解决寻路到封闭地形的问题,而最近点检测方法可以移动到附近的点R( 如左图)。因此,最近点检测方法一定会生成一条路径,而目标点替代法不一定会有。

2.一般情况下,当目标点无法移动过去时,最近点检测方法往往会搜索地图上的所有点,而目标点替代法通过找最近可达点可以解决这个问题。不过在如左图所示回字形地图上,标点替代法同样会搜索地图上的所有点。

3.目标点替代法搜索到的可达点,可能不是在起点和中间之间,可能使最终生成路径变得稍微有点怪异。而最近点检测方法生成的路径则很自然。如右图所示,由于目标点替代法查找周围的可达点时,是按照一定的顺序去搜索的。那么就可能出现目标点是S,却走到R1上去了,而很自然的路径应该是走到R点上。这一点在格子大小很小的地图中表现不是非常明显,但是在格子很大的地图上表现的差异感就很强烈了,甚至不能接受了。不过,我们可以通过额外的方法消除这个问题-- 在搜索每一圈的可达点的时候,计算该圈上每一点到起始点的距离大小,选择距离最小的可达点作为替代点。

        

 

  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值