Система навигации NavMesh позволяет объяснить игровым персонажам, как добраться до определённой точки уровня, избегая всевозможные препятствия и используя созданные игроком механики.
Для работы с ней Unity предлагает следующие четыре компонента:
- NavMesh. Основной компонент, который является структурой данных, описывающей все поверхности игрового уровня для которого была создана. Обращаясь к ней персонажи могут просчитать свой путь до определённой точки.
- NavMeshAgent. Это компонент, который закрепляется за игровыми объектами, которые должны в последствии передвигаться по уровню. Такие игровые объекты называются агентами.
- Off-mesh Link. Данный компонент необходим для создания особенных путей на уровне. Подъём по лестнице, вход в портал, прыжок с платформы — примеры того, что может быть сделано с помощью него.
- NavMesh Obstacle. Движущиеся препятствия, которые подвержены физике или могут изменять своё положение каким-либо другим образом должны иметь данный компонент для того, чтоб агенты знали о их существовании и могли их обойти.
В Unity агенты описываются как цилиндры, а поверхности, по которым они могут двигаться, в виде выпуклых многоугольников. Для поиска пути между двумя точками в сцене, сначала сопоставляется начальная и конечная точки с ближайшими к ним полигонами. Далее, посещая все соседние полигоны, ищется путь позволяющий достигнуть пункта назначения. В Unity для осуществления данных действий используется распространённый алгоритм поиска пути A*.
Последовательность полигонов, которая описывает путь к конечной точке, называется коридором. Агент следует до точки назначения по видимым углам коридора и при необходимости способен исправить его, при появлении необходимости совершить крюк, встречая какое-то препятствие.
Для предотвращения столкновений агента с препятствиями или другими агентами избегание препятствий способно скорректировать желаемую скорость, принятую логикой рулевого управления. Для прогнозирования и предотвращения столкновений в Unity используются препятствия с обратной скоростью (RVO).
Наконец, после рулевого управления и обхода препятствий рассчитывается конечная скорость. На этом этапе так же учитывается ускорение динамической модели, которое обеспечивает более естественное и плавное движение.
Тогда же скорость смоделированного агента можно передать в систему анимации Mecanim или позволить навигационной системе самой позаботиться об этом.
Теперь разберёмся в том, как создать навигационную сетку NavMesh.
Создание навигационной сетки
Сам процесс создания NavMesh из геометрии уровня в Unity называется запеканием. В течение него все Render Meshes и Terrains помеченные на сцене как Navigation Static собираются вместе и обрабатываются для создания навигационной сетки. Для того, чтоб запечь ваш уровень, необходимо открыть окно навигации.
Окно навигации имеет четыре вкладки, каждую из которых мы разберём в ходе изучения вопроса. Для запекания навигационной сетки нам потребуется вкладка «Object».
Перед тем как нажать кнопку «Bake» необходимо пометить все нужные объекты флажком Navigation Static. Тогда поверхности предназначенные для ходьбы будут образованы на основе их Mesh Renderers или Terrains.
После того, как мы отметили нужные объекты, необходимо настроить запекание во вкладке «Bake» в соответствии с настройками нашего агента. Agent Radius определяет насколько близко агент может подойти к стене или уступу, Agent Height определяет низко могут находится объекты под которыми должен пройти агент, Max Slope определяет максимальный угол пандусов по которым агент способен взобраться, Step Height определяет насколько высоки могут быть препятствия на которые агент может подняться.
После запекания создастся файл NavMesh в папке, название которой будет соответствовать названию вашей сцены, для которой было произведено запекание. Он будет автоматически использоваться агентами, находящимися на данной сцене.
Ещё одним не мало важным моментом является возможность создания собственных типов навигационных зон помимо встроенных. Можно создать 29 типов пользовательских зон, дать им собственные названия и стоимости перехода.
Стоимость перехода определяет то насколько предпочтительным будет переход вдоль такой зоны. Так, например, можно создать тип зоны «болото», стоимость которой была бы выше. То есть, если есть зона «земля» со стоимостью 1 и есть зона «болота» стоимость которой 3, то при расчёте пути переход по зоне «болото» будет считаться, как путь в три раза длиннее.
Помимо этого, можно в дальнейшем задать маску области для каждого агента. Маска области определяет по каким типам зон агент может перемещаться, а по каким нет. Так, например, агенту, реализующему какую-нибудь рыбу, можно дать возможность перемещаться лишь в зоне «вода».
Перед тем, как приступить к изучению того, как создавать непосредственно агентов разберём так же расширенные настройки запекания NavMesh.
Min Region Area – позволяет отсечь он навигационной сетки ненужные слишком маленькие зоны. Все области, размер которых меньше, чем тот, что указан в этом поле, будут просто удалены.
Manual Voxel Size – позволяет в речную задать размер вокселя. Чем меньше размер вокселей, тем точнее результирующая сетка. Однако большая точность требует больших вычислительных способностей. По умолчанию точность установлена так, что размер вокселя определяется третью радиуса персонажа, что является хорошим компромиссом между точностью и скоростью выпечки. При желании можно задать собственный размер в поле Voxel Size.
Флажок Height Mesh позволяет создать сетку высоты. Такая сетка позволяет более точно размещать персонажей на поверхностях. Поскольку NavMesh представляет собой лишь приблизительное пространство для ходьбы, то некоторые объекты могут утратить свою форму на обычной сетке. Так, к примеру, лестница может превратиться в уклон.
Для того, чтоб избегать проблем с размещением персонажа на подобных поверхностях можно включить флажок Height Mesh.
Далее перейдём к тому, как создавать своих собственных агентов, препятствия, а также навигационные ярлыки.
Использование NavMesh Agent, Obstacle и Off-mesh Link
После запекания NavMesh можно начать создавать остальные компоненты.
Для того, чтоб создать персонажа, способного передвигаться по созданной навигационной сетке, необходимо на его объект добавить компонент NavMesh Agent. Внутри него мы можем настроить максимальную скорость движения и поворота, а также другие параметры, описанные в таблице ниже.
Свойство | Назначение |
Размер агента | |
Radius | Радиус агента, используемый для расчета столкновений между препятствиями и другими агентами. |
Height | Высота необходимая для того, чтоб агент мог пройти под препятствием. |
Base offset | Смещение цилиндра столкновения относительно центра самого объекта. |
Рулевое управление | |
Speed | Максимальная скорость движения. |
Angular Speed | Максимальная скорость вращения. |
Acceleration | Максимальное ускорение. |
Stopping distance | Расстояние, на которое агент должен приблизиться к целевой точке перед остановкой. |
Auto Braking | Если этот параметр включен, агент будет замедляться при достижении пункта назначения. Необходимо отключить это для поведения, такого как патрулирование, когда агент должен плавно перемещаться между несколькими точками. |
Уклонение от препятствий | |
Quality | Качество уклонения от препятствий. Если у вас большое количество агентов, вы можете сэкономить процессорное время, уменьшив качество обхода препятствий. Установка уклонения на none будет разрешать столкновение и не будет пытаться активно избегать других агентов и препятствий. |
Priority | Агенты с более низким приоритетом будут игнорироваться этим агентом при выполнении уклонения. Значение должно находиться в диапазоне от 0 до 99, где более низкие числа указывают на более высокий приоритет. |
Нахождение пути | |
Auto Traverse OffMesh Link | Установите значение true, чтобы автоматически перемещаться по ссылкам вне сетки. Следует отключать при необходимости проиграть какую-либо анимацию перед тем, как агент переместится в конечную точку ссылки. |
Auto Repath | Если этот параметр включен, агент попытается снова найти путь, когда достигнет конца частичного пути. Если пути к месту назначения нет, создается частичный путь к ближайшему достижимому местоположению к месту назначения. |
Area Mask | Маска области описывает, какие типы областей агент будет учитывать при поиске пути. |
Простейшим способом заставить персонажа идти в указанную позицию будет использование сценария описанного в листинге ниже.
using UnityEngine;
using System.Collections;
public class MoveTo : MonoBehaviour
{
// Положение точки назначения
public Transform goal;
void Start()
{
// Получение компонента агента
UnityEngine.AI.NavMeshAgent agent
= GetComponent<UnityEngine.AI.NavMeshAgent>();
// Указаие точки назначения
agent.destination = goal.position;
}
}
Для его использования достаточно создать другой объект в пределах досягаемости агента и привязать его положение к полю goal нашего сценария. Тогда при запуске приложения наш агент уже будет способен добраться до назначенной цели.
Пример использования этого сценария изображён на рисунке выше. При запуске данной сцены (после предварительного создания навигационной сетки), мы обнаружим, как наш агент передвигается к объекту цели, пока не достигнет его.
Препятствия NavMesh Obstacle также довольно просты в создании. Его параметры так же разбёрем составив таблицу.
Свойство | Назначение |
Shape | Форма геометрии препятствия. |
Для box | |
Center | Определяет центр препятствия относительно центра объекта. |
Size | Определяет размер препятствия. |
Для capsule | |
Center | Определяет центр препятствия относительно центра объекта. |
Radius | Определяет радиус капсулы. |
Height | Определяет высоту капсулы. |
Carve | При включении препятствие прорежет дыру в NavMesh. Стоит включить для препятствий, таких как ящики и бочки, которые обычно блокируют навигацию, но могут быть перемещены игроком или другими игровыми событиями, такими как взрывы. |
Свойства доступные только при включенном флажке Carve | |
Move Threshold | Пороговое расстояние для обновления движущегося вырезанного отверстия. |
Time To Stationary | Время ожидания перед тем, как препятствие станет стационарным. |
Carve Only Stationary | Когда эта функция включена, препятствие будет вырезано только тогда,когда оно неподвижно. |
Модифицируем нашу сцену, используя преграду в виде оранжевых кубов.
Для того, чтоб агенты знали о существовании такого препятствия достаточно просто обозначить центр и размер препятствия, а также включить флажок Carve, после чего агент будет знать о том, как найти путь вокруг препятствия. Модифицированная сцена изображена на рисунке ниже.
После добавления компонента NavMeshObstacle на данные кубы, настройки эквивалетной описанной выше, и запуска сцены мы увидим, что теперь агент обходит поставленные нами препятствия.
Как и говорилось ранее ссылки Off-mesh Link используются для того, чтоб создавать альтернативные пути перемещения для агентов. Для работы таких ссылок необходимо в компоненте Off-mesh Link указать положения начала и конца ссылки, а также стоимость такого перехода.
Параметры, доступные этому компоненту:
Свойство | Назначение |
Start | Объект, описывающий начальное местоположение Off-Mesh Link. |
End | Объект, описывающий конечное местоположение Off-Mesh Link. |
Cost Override | Если значение положительное, то используется при расчете стоимости пути при обработке запроса пути. В противном случае используется стоимость по умолчанию (стоимость территории, которой принадлежит данный игровой объект). |
Bi-Directional | В активном состоянии связь будет двусторонней. В неактивном односторонней. |
Activated | Определяет, будет ли данная связь использоваться системой поиска пути. |
Auto Update Positions | Когда эта функция включена, ссылка Off-Mesh будет повторно подключена к NavMesh при перемещении конечных точек. Если отключено, ссылка останется в своем начальном положении, даже если конечные точки будут перемещены. |
Navigation Area | Описывает тип области навигации ссылки. |
Добавим такую ссылку на нашу сцену. Ниже изображены изменённая сцена (рисунок 7) и компонент Off-Mesh Link привязанный к одному из объектов (рисунок 8).
Так же существует возможность автоматически сгенерировать такие ссылки при построении навигационной сетки. Двумя наиболее распространёнными типами автоматических ссылок являются Drop-Down (создаются для спусков с платформ) и Jump-Across (создаются для прыжка через щели). Для объектов, которым необходимо сгенерировать такие ссылки используется флажок Generate OffMeshLinks в окне навигации. Далее во вкладке запекания следует указать максимальную высоту для спуска, а также максимальную дистанцию для прыжка.
Примеры использования NavMesh
При создании логики персонажей в игре, разработчик может нуждаться в реализации следующих типичных задач:
- Движение персонажа в определённую точку.
- Следование за другим персонажем.
- Патрулирование персонажем области.
- Движение персонажа в указанную кликом мыши область.
С использованием NavMeshAgent такие задачи решаются без больших трудностей. Для простого движения персонажа в отведённую точку достаточно использовать описанный ранее сценарий MoveTo.
Для того, чтоб заставить одного персонажа следовать за другим следует выделить код метода Start в отдельный метод, который можно вызывать, когда необходимо пересчитать путь до конечной точки. Желательно производить это действие как можно реже. Например, союзник игрока может начать следовать за ним только при достаточном удалении от него.
Ещё одним полезным действием, в случае, когда необходимо лишь подойти к другому персонажу, изменить поле Stopping Distance. Тогда агент остановится заранее, не пытаясь «войти» в другого персонажа.
Для решения следующей проблемы достаточно так же модифицировать изначальный MoveTo.cs. Листинг, приведённый ниже, отображает содержимое сценария MoveBetweenGoals.cs. В нём наш Tranform goal стал массивом для указания всех точек, которые нужно проходить при патрулировании. В функции Update проверяется приблизился ли персонаж на достаточное расстояние к текущему месту назначения и, если да, перенаправляет его к следующему.
using UnityEngine;
using System.Collections;
public class MoveBetweenGoals : MonoBehaviour
{
// Компонент агента
UnityEngine.AI.NavMeshAgent agent;
// Массив положений точек назначения
public Transform[] goals;
// Расстояние на которое необходимо приблизиться к точке
public float distanceToChangeGoal;
// Номер текущей целевой точки
int currentGoal = 0;
void Start()
{
// Сохранение компонента агента и направление к первой точке
agent = GetComponent<UnityEngine.AI.NavMeshAgent>();
agent.destination = goals[0].position;
}
void Update()
{
// Проверка на то, достаточно ли близок агент к цели
if (agent.remainingDistance < distanceToChangeGoal)
{
// Смена точки на следующую
currentGoal++;
if (currentGoal == goals.Length) currentGoal = 0;
agent.destination = goals[currentGoal].position;
}
}
}
Этого кода достаточно, чтоб заставить персонажа ходить от точки к точке, патрулируя территорию. Так же можно обработать случай, когда до следующей точки нет пути для перенаправления персонажа к следующей точке или добавить другую модель поведения при встрече с игроком.
И так же разберём, как заставить двигаться агента к месту нажатия кнопки мыши. Даннаяя задача мыши больше о том, чтоб зарегистрировать место нажатия кнопки. Тогда мы смогли бы просто, используя сценарий MoveTo.cs, направить персонажа в нужное место.
Листинг соответствующего сценария приведён ниже. Этот сценарий находит точку соприкосновения клика мышью с поверхностью и установку новой конечной точки для агента, на котором находится данный сценарий.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class SendClickPosition : MonoBehaviour
{
// Компонент агента
NavMeshAgent agent;
private void Start()
{
// Сохранение компонента агента
agent = GetComponent<NavMeshAgent>();
}
void Update()
{
// Проверка нажата ли левая клавиша мыши в данном кадре
if (Input.GetMouseButtonDown(0))
{
RaycastHit hit;
// Проверка коснулась ли мышь кликом объекта
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit, 100))
{
// Если да, то устанавливает новую точку назначения
agent.destination = hit.point;
}
}
}
}
Многие прочие возникающие задачи могут решиться модификацией описанных выше сценариев. Избегание одного персонажа другим, например, создаётся выбором не случайной точки при использовании сценария патрулирования, а наиболее удалённой от догоняющего персонажа.
И напоследок мы разберём, как NavMesh может взаимодействовать с другими компонентами Unity.
Использование других компонентов вместе с NavMesh Agent
Агенты NavMesh могут использоваться вместе с физическими компонентами Unity или, например, с Animator’ом. Для того, чтоб избегать ошибок, которые могут возникнуть в процессе этого, следует придерживаться некоторых правил.
Вначале разберём то, как стоит использовать NavMesh Agent вместе с физикой:
- Для того, чтоб агенты избегали друг друга не обязательно создавать им физические коллайдеры, ведь они ориентируются исключительно на собственную модель агента.
- Если необходима возможность перемещения физических объектов или, использования физических триггеров агентом, то необходимо добавить ему собственный коллайдер и компонент Rigidbody. При этом важно, чтобы был включён флажок Is Kinematic, означающий, что твёрдое тело управляется чем-то другим, кроме физической симуляции.
- Если NavMesh Agent и не кинематический Rigidbody включены одновременно, то это может привести к ошибкам, связанным с тем, что оба компонента могут пытаться переместить агента одновременно.
Далее правила определим для использования NavMesh Agent вместе с Animator’ом:
- NavMesh Agent и Animator с Root Motion могут так же вызвать состояние гонки, когда движения агента контролируются несколькими компонентами одновременно. Существует два решения данной проблемы: использование анимации после передвижения персонажа агентом или анимация перемещает персонажа исключительно на основе смоделированного агентом результата.
- Если анимация следует за агентом: следует проигрывать анимации, выбранные на основе значения NavMeshAgent.velocity, чтобы сопоставить движение агента с анимацией. Такой подход требует большой работы над анимацией для того, чтоб избежать скольжения ног, когда анимация не соответствует скорости, заданной агентом.
- Если агент следует за анимацией: необходимо отключить флажки updatePosition и updateRotation для компонента агента для того, чтоб он не пытался переместить агента собственноручно. Аниматор в таком случае должен перемещать персонажа сам, используя поле nextPosition для определения следующей точки направления персонажа в коридоре, а rootPosition для понимания текущего положения персонажа