Введение
Сложно представить игры без искусственного интеллекта для NPC. Неигровые персонажи создают наполняемость мира и являются двигателями игрового процесса. Для проектов в жанрах RTS и Shooter особенно важно адекватное поведение юнитов.
Одним из важных элементов поведения персонажа является способ построения маршрута из точки А в точку Б. В данной статье рассмотрим один из самых популярных алгоритмов поиска пути - А*.
Алгоритм А*.
Алгоритм А* пошагово просматривает все пути, ведущие от начальной вершины в конечную, пока не найдет минимальный. Сам поиск выглядит следующим образом:
Создаем 2 списка вершин - ожидающие рассмотрения и уже рассмотренные. В список ожидающих добавляем точку старта, а список рассмотренных оставляем пустым.
Для каждой точки рассчитываем цена F = G + H, где G - расстояние от старта до точки, H - примерное расстояние от точки до цели. Каждая узел должен хранить ссылку на точку, из которой в него пришли.
Из списка узлов на рассмотрение выбираем точку с наименьшим F. Обозначим ее за X.
Если Х - целевая точка, то мы нашли маршрут.
Переносим Х из списка ожидающих в список рассмотренных.
Для каждой соседней с Х точки делаем следующее:
Обозначим соседнюю точку за Y. Если Y уже находится в рассмотренных - пропускаем ее.
Если Y еще нет в списке на ожидание - добавляем ее туда, запомнив ссылку на Х, рассчитав Y.G (это X.G + distance(X, Y)) и Y.H.
Если же У в списке на рассмотрение - проверяем, если X.G + distance(X, Y) < Y.G, значит мы пришли в точку Y более коротким путем, заменяем Y.G на X.G + distance(X, Y), а точку, из которой пришли в Y, на X.
Если список точек на рассмотрение пуст, а до цели мы так и не дошли - значит маршрут не существует.
В качестве функции примерной оценки расстояния до цели возьмем вычисление длины между двумя точками без препятствий.
Зададим ноды следующим образом:
public class Node { public Vector3 Position { get; set; } public Vector3 TargetPosition { get; set; } public Node PrevNode { get; set; } public int F { get; set; } public int G { get; set; } public int H { get; set; } public bool IsWalkable { get; set; } public Node(Vector3 nodePosition, Vector3 targetPosition, Node prevNode, int g) { Position = nodePosition; TargetPosition = targetPosition; PrevNode = prevNode; G = g; H = (int)Mathf.Abs(targetPosition.x - Position.x) + (int)Mathf.Abs(targetPosition.y - Position.y); F = G + H; } }
Сам метод для поиска пути выглядит следующим образом:
{
PathToTarget = new List<Vector3>();
CheckedNodes = new List<Node>();
WaitingNodes = new List<Node>();
Vector3 StartPosition = new Vector3(Mathf.Round(transform.position.x), Mathf.Round(transform.position.y), Mathf.Round(transform.position.z));
Vector3 TargetPosition = new Vector3(Mathf.Round(Target.transform.position.x), Mathf.Round(Target.transform.position.y), Mathf.Round(Target.transform.position.z));
if (IsEqualVector(StartPosition, TargetPosition))
return PathToTarget;
Node startNode = new Node(StartPosition, TargetPosition, null, 0);
CheckedNodes.Add(startNode);
WaitingNodes.AddRange(GetNeighboursNodes(startNode));
while (WaitingNodes.Count > 0)
{
Node nodeToCheck = WaitingNodes.Where(x => x.F == WaitingNodes.Min(y => y.F)).FirstOrDefault();
if (IsEqualVector(nodeToCheck.Position, TargetPosition))
{
return CalculatePathFromNode(nodeToCheck);
}
var walkable = (Physics.OverlapSphere(nodeToCheck.Position, 0.1f, SolidLayer).Length == 0);
nodeToCheck.IsWalkable = walkable;
if (!walkable)
{
WaitingNodes.Remove(nodeToCheck);
CheckedNodes.Add(nodeToCheck);
}
else
{
WaitingNodes.Remove(nodeToCheck);
if (!CheckedNodes.Where(x => IsEqualVector(x.Position, nodeToCheck.Position)).Any())
{
CheckedNodes.Add(nodeToCheck);
WaitingNodes.AddRange(GetNeighboursNodes(nodeToCheck));
}
}
}
FreeNodes = CheckedNodes;
return null;
}