二维平面简单A*算法的实现(不理解也能直接用!)


算法的具体思路在网上一搜一大把,我这里就不献丑了,写了也是CV的,下面直接放算法实现,方便急用又不想了解具体算法的同学~

适用范围

  1. 二维平面
  2. 以int整数为坐标表示

实现算法的环境

Unity、C#

实现思路

在实现方式上对算法本身以及数据来源进行了拆分,数据来源以接口的形式去实现,可以灵活用于不同的情况,例如四连通还是八连通、不同格子移动代价不同等等。

完整算法代码

using System.Collections.Generic;
using UnityEngine;

public class Simple2DAStarPathFinding
{
	private struct PathNode
	{
		public Vector2Int Position;
		public Vector2Int ParentPosition;
		public float CostFromStart;
		public float EstimatedCostToEnd;
		public float CostSum => CostFromStart + EstimatedCostToEnd;

		public PathNode(Vector2Int position, Vector2Int parentPosition, float costFromStart, float estimatedCostToEnd)
		{
			Position = position;
			ParentPosition = parentPosition;
			CostFromStart = costFromStart;
			EstimatedCostToEnd = estimatedCostToEnd;
		}
	}

	private Dictionary<Vector2Int, PathNode> _openList = new Dictionary<Vector2Int, PathNode>();
	private Dictionary<Vector2Int, PathNode> _closeList = new Dictionary<Vector2Int, PathNode>();

	private Vector2Int _end;

	private ISimple2DAStarPathFindingDataSource _pathFindingAStarDataSource;

	public const float UnreachableCost = -1;

	public Simple2DAStarPathFinding(ISimple2DAStarPathFindingDataSource pathFindingAStarDataSource)
	{
		_pathFindingAStarDataSource = pathFindingAStarDataSource;
	}

	public bool FindPath(Vector2Int start, Vector2Int end, out List<Vector2Int> path)
	{
		_end = end;
		_openList.Clear();
		_closeList.Clear();
		path = null;

		if (!_pathFindingAStarDataSource.IsValidNode(start) || !_pathFindingAStarDataSource.IsValidNode(end))
			return false;

		var currentRowAndColumn = start;
		int costFromStart = 0;
		_openList.Add(currentRowAndColumn, new PathNode(currentRowAndColumn, currentRowAndColumn, costFromStart, EstimateCostToEnd(currentRowAndColumn)));
		while (_openList.Count > 0)
		{
			var nextNode = ChooseMinimumCostNodeFromOpenList();
			MoveToCloseList(nextNode);
			AppendNearConnectableIntoOpenList(nextNode);
			if (_openList.ContainsKey(_end))
			{
				path = GeneratePath(start);
				Debug.Log("Find path successfully.");
				return true;
			}
		}
		Debug.Log("Find path failure.");
		return false;
	}

	private List<Vector2Int> GeneratePath(Vector2Int start)
	{
		List<Vector2Int> path = new List<Vector2Int>();
		var node = _openList[_end];
		path.Insert(0, node.Position);
		while (node.Position != start)
		{
			node = _closeList[node.ParentPosition];
			path.Insert(0, node.Position);
		}

		return path;
	}

	private void AppendNearConnectableIntoOpenList(Vector2Int currentRowAndColumn)
	{
		var nearNodes = _pathFindingAStarDataSource.GetNearNodes(currentRowAndColumn);
		foreach (var nearNode in nearNodes)
		{
			if (nearNode.Value < 0)
				continue;

			var nodePosition = nearNode.Key;
			if (_closeList.ContainsKey(nodePosition))
				continue;

			if (!_pathFindingAStarDataSource.IsValidNode(nodePosition))
				continue;

			AddToOpenListOrRefreshCost(currentRowAndColumn, nodePosition, nearNode.Value);
		}
	}

	private void AddToOpenListOrRefreshCost(Vector2Int currentPosition, Vector2Int anotherPosition, float cost)
	{
		var parentNode = _closeList[currentPosition];
		if (_openList.TryGetValue(anotherPosition, out var leftNode))
		{
			if (parentNode.CostFromStart + cost < leftNode.CostFromStart)
			{
				leftNode.ParentPosition = currentPosition;
				leftNode.CostFromStart = parentNode.CostFromStart + cost;
			}
		}
		else
		{
			PathNode pathNode = new PathNode(anotherPosition, currentPosition, parentNode.CostFromStart + cost, EstimateCostToEnd(anotherPosition));
			_openList.Add(anotherPosition, pathNode);
			Debug.LogFormat("Add position {0} into openlist", anotherPosition);
		}
	}

	private void MoveToCloseList(Vector2Int nodePosition)
	{
		Debug.Assert(_openList.ContainsKey(nodePosition));

		var node = _openList[nodePosition];
		_openList.Remove(nodePosition);
		_closeList.Add(nodePosition, node);
		Debug.LogFormat("move position {0} into closelist", nodePosition);
	}

	private int EstimateCostToEnd(Vector2Int position)
	{
		return Mathf.Abs(_end.x - position.x) + Mathf.Abs(_end.y - position.y);
	}

	private Vector2Int ChooseMinimumCostNodeFromOpenList()
	{
		Vector2Int? result = null;
		foreach (var node in _openList)
		{
			if (result == null)
			{
				result = node.Key;
				continue;
			}
			if (_openList[result.Value].CostSum > node.Value.CostSum)
				result = node.Key;
		}
		return result.Value;
	}
}

在使用时实现以下接口即可,建议拥有完整地图坐标数据的类去实现,至于是四连通还是八连通,以及相邻格间的代价如何计算(例如沼泽、水域、沙地)取决于GetNearNodes如何实现。

public interface ISimple2DAStarPathFindingDataSource
{
	public bool IsValidNode(Vector2Int nodePos);

	/// <returns>PathNodes and cost from <paramref name="nodePos"/> to which. If unreachable, use <see cref="Simple2DAStarPathFinding.UnreachableCost"/>.</returns>
	public Dictionary<Vector2Int, float> GetNearNodes(Vector2Int nodePos);
}

使用示例

ISimple2DAStarPathFindingDataSource impl; // 建议拥有完整地图坐标数据的类去实现
Simple2DAStarPathFinding pathFinding = new Simple2DAStarPathFinding(impl);
Vector2Int startPos = xxx; // 以任意方式获取的开始点
Vector2Int endPos = xxx; // 以任意方式获取的结束点
if (pathFinding.FindPath(startPos, endPos, out var path)) // 得出完整路径则为true,如果找不到路径则为false
{
    // TODO 拿着path去做你想做的事情吧
}

拓展思考

  1. 如果是连续坐标而非离散坐标(整数坐标),该如何实现。
  2. 可否实现为二维三维通用的算法。

一点小唠叨

提取接口的初衷,其实就是项目里面我接手过的不同模块都用到了寻路算法。然后一想,我要是复制代码大佬肯定忍不了呀,而且CV大法虽然好,但也显得有些蠢,所以还是通过提取接口的方式,让我能脱胎于算法本身,专注于功能实现,嘿嘿嘿~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值