Найти тему

Unity 3D. John Lemon's Jount. Part 15

Part 14 https://dzen.ru/a/Y80_-k8Jz0Gza_4J

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

Добавьте префабу призрака новый скрипт. Призраки в вашей игре будут двигаться, перемещаясь в цикле по своим путевым точкам, поэтому скрипт назовем WaypointPatrol. Откройте скрипт для редактирования.

Сначала давайте добавим библиотеку для работы с агентом NavMesh в блок библиотек в самом начале скрипта:

using UnityEngine.AI;

И заведем глобальную переменную для привязки к компоненту Nav Mesh Agent:

NavMeshAgent navMeshAgent;

Далее вам нужно установить путевые точки, по которым перемещается агент-призрак. Эти точки являются обычными позициями в игровом мире, поэтому имеют тип Vector3. Но на игровой сцене у нас нет объектов типа Vector3, поэтому нам придется самостоятельно в коде прописывать координаты каждой точке, но это долго и неудобно. Поэтому, чтобы связать эти точки в редакторе Unity с конкретными игровыми объектами, мы вместо типа Vector3 будем использовать тип Transform.

Поскольку этих точек может быть много, то мы можем, конечно, завести большое количество отдельных переменных типа Vector3 и работать с ними. Но это не очень удобно, так как увеличивает объем кода и в этих переменных можно в итоге запутаться. Вместо этого мы будем использовать массив.

Массив – это переменная, которая является совокупностью компонентов одного типа. Массивы есть в большинстве языков программирования и они используются в ситуациях, когда необходимо работать с однотипным набором данных, например последовательностью чисел, объектов или строк, в нашем случае последовательностью путевых точек.

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

Расположены элементы в соседних ячейках памяти, поэтому перебор элементов или другие действия с ними выполняются довольно быстро.

Обратиться к конкретному элементу массива можно по индексу, то есть по его порядковому номеру в массиве. Следует только помнить, что нумерация в программировании всегда идет с нуля, поэтому первый элемент массива будет иметь индекс равный нулю.

У массива фиксированная длина, которую программист указывает при создании переменной массива. Если выйти за пределы длины массива и, например, попытаться присвоить значение пятому элементу массива, длина которого равна четырем, то возникнет ошибка.

Объявление массива похоже на объявление переменной за тем исключением, что после указания типа ставятся квадратные скобки:

тип_переменной[] название_массива;

Давайте заведем переменную waypoints - массив для хранения точек:

public Transform[] waypoints;

Скрипт после определения всех переменных выглядит так:

Скрипт призрака
Скрипт призрака

Сначала привяжем переменную navMeshAgent к компоненту NavMeshAgent, который мы добавили префабу призрака:

navMeshAgent = GetComponent<NavMeshAgent>();

Теперь разберемся с массивом точек. Обратиться к первой точке массива мы можем так - waypoints[0].

Эта точка имеет тип Transform, который содержит три параметра - position, rotation и scale. Нам необходимо обратиться к позиции этой точки:

waypoints[0].position

И теперь эту позицию точки мы должны поместить в список точек пути нашего агента. В метод Start добавим задание самой первой, начальной точки пути агента Nav Mesh:

navMeshAgent.SetDestination(waypoints[0].position);

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

int CurrentWaypointIndex;

По умолчанию значение этой переменной равно нулю, что нам и нужно, потому что первая активная точка пути имеет индекс ноль.

Сначала в методе Update() нам нужно выполнить проверку — дошел ли агент до точки с номером CurrentWaypointIndex. Простой способ это проверить - сравнить расстояние от текущего положения агента до позиции точки с параметром Stopping Distance, который мы задавали в инспекторе в предыдущей части:

if(navMeshAgent.remainingDistance < navMeshAgent.stoppingDistance) {

}

navMeshAgent.remainingDistance - позволяет получить значение расстояния от текущего положения агента до позиции точки;

navMeshAgent.stoppingDistance - позволяет получить значение параметра Stopping Distance.

Как только условие выполнится, то есть агент дойдет до активной точки, нам нужно увеличить текущий индекс на единицу:

CurrentWaypointIndex = CurrentWaypointIndex + 1;

А затем используем его для задания следующей точки пути агента Nav Mesh.

navMeshAgent.SetDestination (waypoints[CurrentWaypointIndex].position);

Добавим еще одно небольшое условие, которое обезопасит нас от неверного перемещения агента-призрака. При первом срабатывании метода Update() вычисление дистанции navMeshAgent.remainingDistance вернет нам значение равное 0, так как определение пути призрака еще не завершилось и он не начал движение. Параметр pathPending определяет вычислен ли уже путь агента или нет. Если путь еще не готов, то нам не нужно ничего делать, а просто выходим из метода Upadte() при помощи ключевого слова return:

if (navMeshAgent.pathPending)
return;

Скрипт будет выглядеть так:

Скрипт призрака
Скрипт призрака

Но в этом коде есть проблема - когда призрак дойдет до последней точки своего пути, у нас возникнет ошибка обращения к несуществующему элементу. Например, если точек в пути всего пять, то когда мы дойдем до этой пятой точки, переменная CurrentWaypointIndex снова увеличится на единицу, но в массиве больше нет элементов!

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

Для этого нам понадобится новый оператор, называемый оператором остатка, который обозначается символом процента %. Оператор остатка делит значение, находящееся слева от него на значение, находящееся справа, и дает нам остаток от деления. Например:

5 % 4 вернет 1 (так как число 4 входит в 5 один раз и остается еще единица).

11 % 4 вернет 3 (так как 11 = 4 * 2 + 3)

3 % 4 вернет 3 (так как 3 = 4 * 0 + 3)

С помощью этого оператора легко организовать зацикливание следующим образом:

CurrentWaypointIndex = (CurrentWaypointIndex + 1) % waypoints.Length;

waypoints.Length вернет нам значение длины массива, то есть количество элементов в этом массиве. Взяв остаток от деления номера текущей точки на количество элементов в массиве, мы получим номер точки в диапазоне длины массива.

Например, если длина массива равна 5, значит элементы массива имеют номера: 0, 1, 2, 3, 4. Подставьте эти значения в строку

CurrentWaypointIndex = (CurrentWaypointIndex + 1) % waypoints.Length;

(0 + 1) % 5 = 1

(1 + 1) % 5 = 2 и т.д.

Значение CurrentWaypointIndex вычисляется верно в пределах длины массива.

Как только мы дойдем до пятой точки (ее индекс равен 4), то вычисление следующей точки будет таким:

(4 + 1) % 5 = 0

То есть мы опять вернулись в начальную точку пути агента. Следующая точка вычисляется так:

(5 + 1) % 5 = 1

Таким образом оператор остатка от деления обеспечивает нам зацикливание по пути агента.

Окончательный скрипт:

Скрипт призрака
Скрипт призрака

Путевые точки будут разными для каждого призрака, поэтому мы не можем задать их в префабе. К тому же путевые точки будут игровыми объектами на сцене, поэтому префаб призрака не может ссылаться на них. Поэтому переходим обратно в редактор, нам нужно задать публичным переменным конкретные значения.

Выберите призрака, который уже есть на сцене, и из которого был создан префаб. Задайте ему имя Ghost (1).

Для начала в области ViewArea нужно привязать персонажа кота и изображения конца игры:

Привязка переменных
Привязка переменных

Теперь наша задача создать точки для пути призрака. Создайте пустой игровой объект и назовите его WayPoint (1). Задайте ему координаты (-13.5, 0, 0.5).

Создание точки
Создание точки

Теперь создайте дубликат этой точки, имя автоматически изменится на WayPoint (2):

Создание дубликата
Создание дубликата

Координаты второй точки задайте (-5, 0, 0.5):

Создание точки
Создание точки

Теперь эти точки нужно поместить в переменную waypoints призрака, который размещен на сцене. Для этого выберите объект призрака в иерархии и найдите в инспекторе компонент скрипта. В нем есть поле Waypoints напротив которого стоит 0. Это означает количество элементов массива точек пути. Сейчас точек не задано. Установите значение 2 и нажмите Enter. У вас появится два новых элемента, в которые необходимо перетащить мышкой объекты двух только что созданных точек WayPoint (1) и WayPoint (2):

Привязка переменных
Привязка переменных

И разместите призрака рядом с первой точкой его пути. Призрак должен двигаться как на видео:

Аналогичным образом создайте еще призраков:

Второй призрак должен двигаться рядом с комнатой, в которой начинается игра. Задайте ему имя Ghost (2).

  • В Инспекторе установите положение этого призрака ( -5.3 , 0 , -3.1 ).
  • Создайте объекты WayPoint (3) и WayPoint (4), продублировав объект WayPoint (2). В Инспекторе установите положение WayPoint (3) на ( -5.3 , 0 , 6.7 ). Установите положение WayPoint (4) на ( -5.5 , 0 , -4.5 ).

Третий призрак должен двигаться по длинному коридору, так что Джону Лимону придется забегать в боковые комнаты, чтобы пройти.Задайте ему имя Ghost (3).

  • В Инспекторе установите положение этого призрака ( 1.5 , 0 , 4 ).
  • Создайте объекты WayPoint (5) и WayPoint (6) также при помощи дублирования. В Инспекторе установите положение WayPoint (5) на ( 1.2 , 0 , 7.7 ). Установите положение WayPoint (6) на ( 0.9 , 0 , -3.5 ).

Четвертый призрак будет кружить вокруг стола в одной из столовых. Задайте ему имя Ghost (4).

  • В Инспекторе установите положение этого призрака ( 3.2 , 0 , 6.5 ).
  • Создайте объекты WayPoint (7), WayPoint (8), WayPoint (9), WayPoint (10) также при помощи дублирования. В Инспекторе установите положение WayPoint (7) на ( 3.2, 0, 5.6 ), WayPoint (8) на ( 3.2, 0, 12.3 ), WayPoint (9) на ( 6.5, 0, 12.3 ), WayPoint (10) на ( 6.5, 0, 5.6 ).

И последний призрак будет перемещаться в спальне рядом с выходом, так что Джон Лимона попадет в ловушку, если пойдет не в ту сторону. Задайте ему имя Ghost (5).

  • В Инспекторе установите положение этого призрака ( 7.4 , 0 , -3 ).
  • Создайте объекты WayPoint (11) и WayPoint (12) также при помощи дублирования. В Инспекторе установите положение WayPoint (11) на ( 3.2, 0, -5 ). Установите положение WayPoint (12) на ( 7.4, 0, -2 ).

Вы также можете создать дополнительных призраков по своему желанию.

Part 16 https://dzen.ru/a/Y84jhrk9K0N-nmEo