Дата публикации статьи: 11.07.2003 00:00

Алгоритмы обхода препятствий Навигация движения путника(unit) от пункта A в пункт B всегда была интересной проблемой, которой интересовались программисты игр. Есть различные способы реализации и я расскажу о нескольких разных алгоритмах.

Самый короткий и истиный маршрут( волновой алгоритм )

Есть различные мнения, что считать самым коротким маршрутом между двумя точками на карте. Я считаю, что алгоритм Djikstra's наиболее общее известный и хорошо документированый. Этот алгоритм основывается на взвешенных графах, где расстояние между двумя точками берется в графе. Но в дальнейшем мы не будем рассматривать этот алгоритм. Хотя его можно упростить и оптимизировать. Так как в играх в большинстве случаев мы имеем дело с картами, которые разбиты на равные секции ( ячейки, квадраты, tiles ).

Найдите page Palmer's Chris для объяснения алгоритма Djikstra's.

Алгоритм, который я предлагаю Вашему вниманию в большой степени основывается на алгоритме Djikstra's, и учитывает допущение, что секции на карте равны. Это алгоритм "A-звезды" или "A*".

Самый большой вклад в разработку алгоритма звезды "A*" внес {k_burge@alcor.concordia.ca}.

Пример алгоритма вычисления самого короткого маршрута

Пусть надо пройти от A до B. Нам необходим массив направлений такого же размера что и карта с ландшафтом. Так же необходимо иметь два
списка с координатами (X,Y) свободных секций карты. Количество элементов в них зависит от размера карты. Представьте себе, что Вы стоите в точке A и льете воду. Вода постепенно заполняет все свободные секции и Вы запоминаете координаты этих секций, и то откуда на эти секции пролилась вода. Когда вода достигнет точки B, можно узнать откуда она пролилась на точку B, и так возвращаясь Вы достигаете точки A.

Алгоритм:

1. Добавить позицию A в список 1.
2. Установить текущим список 1.
3. Вокруг текущей точки в текущем списке ищите секции по специальному звездообразному шаблону: Вверх, Вправо, Вниз, Влево, Вверх-Влево, Вверх-Вправо, Вниз-Вправо, Вниз-Влево.
4. Если Вы нашли свободную секцию на карте, укажите в массиве направлений направление на секцию с которой была найдена эта свободная секция. И добавьте эту секцию втекущий список.
5. Повторяйте пункты 3-4 до тех пор пока все свободные ячейки относительно текущей позиции не будут добавлены в текущий список.
6. Повторяйте пункты 3-5 до тех пор, пока все координаты в текущем списке не будут оценены.
7. Замените текущий список на другой ( инвертируя выбор текущего списка ).
8. Повторяйте пункты 3-7 до тех пор, пока точка B не будет достигнута.
9. Вернитесь из точки B в точку A с использованием массива направлений по найденому маршруту.

Примечания к алгоритму "A*"

1. Скорость: это - довольно медленный алгоритм.

2. Память: надо иметь два списка и это отнимет много памяти в зависимости от размера карты. Но можно обратный маршрут просчитать занося все точки и в конец первого списка, но все равно нужно иметь массив направлений. Так же можно выкинуть секцию после просмотра относительно нее всех свободных секции.

3. Кривизна пути: Возможны различные маршруты от A до B. При этом алгоритме Путник сначала делает шаг поперк, а потом по прямой линии. Это не очень красиво. Программа может быть оптимизирована, чтобы маршрут был более прямой, и при этом не удлинялся.

4. Изменения на карте: если на карте произошли какие-то изменения уже после того был вычислен маршрут и путник начал передвигаться, например открылась дверь в стене или сместился какой-то монстр. Надо снова вычислить маршрут. При этом если изменения будут носить периодический характер, например дверь открывается/закрывается, то путник может зациклится.

Некоторые возможные модификации алгоритма "A*"

Amit Patel {(amitp@CS.Stanford.EDU}) высказал несколько замечений по по поводу оптимизации алгоритма:

Главное различие между моим алгоритмом и алгоритмом "A*", то что мой алгоритм вносит понятие приоритета при внисение свободной секции в список.

1. Когда Вы включаете новую секцию в список, удостоверьтесь, что она находится между началом и концом движения.

2. Когда Вы выбираете секцию из списка, найдите лучшую.

Для оценки приоритета позиции P на карте, надо добавить путь от A до P и предполагаемый маршрут от P до B. Оценить величину приоритета можно относительно прямой линии от A до B.

Вот примерно то, что надо сделать для изменения алгоритма "A*":

1. Кроме координат секции в списке Вы должны хранить пройденную дистанцию от точки A.

2. Вместо перебора всех элементов в списке ищите те которые имеют наименьший приоритет вычисленный по формуле: пройденный путь + расстояние от секции до точки B. Расстояние вычисляется как sqrt( (x1-x2)^2 + (y1-y2)^2 ).

3. После того, как Вы нашли этот наилучший элемент списка, поменяйте его местами с последним элементом списка и уменьшите счет.

4. Исследуйте соседей этого наилучшего элемента списка. Каждый из новых элементов будет иметь приоритет на единицу выше чем наилучший элемент. ( Это будет по другому если разная местность есть на вашей карте ).

Примечания к модификации алгоритма "A*"

1. Скорость: Этот алгоритм проверяет только те маршруты, которые ближе всего к цели, но другие маршруты остаются не проверны.

2. Память: мне кажется что число рассмотренных маршртов будет меньше поскольку просматриваются только наиболее важные.

3. Кривизна пути: мне кажется что кривизна пути при этом алгоритме будет меньше, чем у алгоритма "A*".

4. Изменения на карте : так как новый алгоритм работает быстрее. Вы можете оценивать маршрут чаще и если что произошло на карте, то изменить маршрут. Если конечно Ваш маршрут не будет блокироваться.

Еще одна модификация алгоритма "A*"

1. Можно начинать оценку маршрута из начальной и конечной точек. Причем можно использовать один и тот же список для сохранения свободных секций.

2. Если есть на карте препятствия, которые можно пройти, но они требуют для своего прохождения больше времени. Можно учитывать такие препятствия ( болота, лес ), если после внесения такой секции в список задержать поиск относительно нее других свободных секций.

3. В алгоритме "A*" есть списки свободных секций и карта направлений. Их можно заменить на списки координат свободных секций с номером этой секции, который выставляется соответственно той секции, с которой была обнаружена эта новая свободная секция. И картой где указыватся, что данная секция была пройдена. Так же проходим все свободные секции до точки B. Тогда при выборе маршрута от точки B надо будет с выбрать секцию, с которой мы пришли на нее и так до достижения точки A.

4. Найдем не первый попавшийся маршрут, а все маршруты, которые ведут от точки A до точки B. Тогда, если надо вычислить оптимальный путь с учетом разного времени прохождения препятствия. Можно проанализировать все маршруты и найти оптимальный.

Алгоритм разделяй и влавствуй ?

Это алгоритм прислан мне {sillywiz@wardrobe.demon.co.uk}.

Возьмите начальную позицию и конечную. Нарисуйте между ними прямую линию. Возьмите точку посередине линии. Если она попала на препятствие перемещайте ее каким-нибудь образом ( по спирали, например ) до тех пор пока она будет не на препятствии. Установите эту точку как конечную и продолжайте до тех пор пока не достигните начальной позиции, или одной двух секции карты от начальной.

Этот алгоритм не будет работать на пересеченной местности, но от ухода от деревьев/болот ( мелких препятствий ) возможно он подойдет. У этого алгоритма есть еще преймущество в том, что вы игнорируете дальние препятствия, которые могут изменится до того как путник до них дойдет.

Можно помнить последние несколько секций в которых был до этого путник и не возвращаться в них. В таком случае у путника будет чаще до ходить до места назначения.

Алгоритм поворота Креша ( Crash )

Наиболее простой алгоритм. Данный алгоритм оценивает путь не весь сразу, а только на один шаг. Для этого надо провести прямую линию между началом движения и концом, определив правила как будет совершаться движение. Как Вы уже наверное знаете, это не очень хороший способ. Путник ведет себя странно, выбирает не верный маршрут и иногда его движения зацикливаются. Вот этот алгоритм :

1. Постройте прямую линию до конечной точки. Надо всегда запомнить координаты секции в которой Путник был ( на один шаг ).
2. Если Путник встретился с препятствием он пробует правило правой руки. Перемещайте его вправо, до тех пор пока не встретится свободный проход. И затем двигайте его на эту секцию.
3. Повторяйте пункты 1-2 до достижения конечного пункта движения. Или если Путник оказался в предыдущей секции.
4. Если Путник попал в предыдущую секции, надо сменить правило правой руки на правило левой руки и повторить всю процедуру снова.

Последняя точка может быть изменена для того чтобы не было возврата на предыдущие секции. Тогда можно пользоваться алгоритмом правой
руки во все время движения.

Алгоритм не очень хорош и не всегда отрабатывает. Особенно когда препятствие имеет U-форму.

Дополнение к алгоритму поворота Креша

Вышерасмотренный алгоритм может быть расширен для того чтобы сделать поведение Путника немножко более похоже на человека обходящего препятствие. Рассмотрим пример:

########
########### A
###########
###########
###########
#######

B

Наш Путник хочет пройти от A до B. Мы сразу видим что лучше всего использовать правило левой руки. Но вышеуказанный алгоритм будет сначало использовать правило правой руки. Это будет выглядеть глупо, так как он будет использовать не правильный способ обхода препятствия.

Немного модифицируя алгоритм мы сможем решить какое правило мы должны использовать в начале. Представьте себе что вы хотите обойти
препятствие. Вы возможно думаете так: пойду по краю препятствия до тех пор пока не найду угол, а затем пойду к точке B. Отлично, Вы выбрали правило левой руки.

Альтернативу между правилом правой руки и левой мы найдем при встрече с первым же препятствием. То правило, которое даст свободный маршрут можно будет и использовать в дальнейшем. Это конечно не будет работать на пересеченной местности.

Расчеты до начала движения

Можно оценить весь путь от A до B до начала движения. Так же можно выполнять задержку движения путника при просчете маршрута, чтобы просчитать различные алгоритмы и найти наиболее оптимальный маршрут, или не найти его вообще.

Тем не менее вот несколько соображений :

1. Память : этот способ отнимет больше памяти для каждого путника, так как вы не захотите останавливать игру при оценке маршрута. Вы должны иметь структуру которая сохраняет оценку маршрута, чтобы можно было распределить вычисления на несколько раз. Эта структура не должна сохранять каждую координату, а скорее направление движения. Тогда потребуется 4 бита на координату.

2. Время : я не думаю, что простой путника будет большим.

3. Изменения на карте : любой алгоритм, который оценивает маршрут не сразу, а по частям будет опускать все изменения на карте после начала движения. Переоценка маршрута после начала движения не является оптимальной.

Оценка маршрута с начальной точки движения и с конечной

Используя упрощеную версию алгоритма поворота Креша попробуйте четыре маршрута при этом. Первый использует алгоритм левой руки от точки A до B, другой правой, а два других такие же правила, но от B до A. Один из них который достигнет цели и будет использоваться. Это не займет много времени для оценки. Используйте возможность распределить вычисления по времени.

Примечания переводчика

Все рассмотреные здесь алгоритмы не решают данной проблемы целиком. Так как препятствия могут находятся в движении, иметь разную проходимость, их можно разрушать, или они уничтожают путника. Кроме этого возможно, что карту нельзя будет разбить на секции равной величины, или конечная точка движения смещается. Я, к сожалению, не знаю алгоритмов, которые учитывают эти требования. Если у Вас есть какая-то информация по этому поводу, и Вы хотите поделиться ей, напишите мне я постараюсь внести ее в этот документ.

Данный документ составлен Анисимовым С.Ю. 12/1996. г. К-Чепецк, Кировской обл. Россия. {root@anis.velcom.vyatka.su}

Данными для составления этого документа послужила информация из статьи John Christian Lonningdal - Smart unit navigation и из других доступных автору источников. Поэтому автор не несет ответственность за неверную информацию, и за повреждения техники и тел при использовании этого документа.

С наилучшими пожеланиями, для всех любителей программировать игры !
Vale !