Найти тему
ElandGames

Учим волка не отвлекаться от кусания игрока! Делаем игру на Unity и изучаем C# (Часть 14)

Оглавление

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

Настройка движения врага через NavMesh

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

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

Видимо волк при беге успевает набрать большую скорость, а компонент при торможении сбрасывает скорость постепенно. Из-за этого волк успевает пробежать лишние пол метра и забежать внутрь игрока.

Попробуем поиграться с параметрами компонента NavMesh, так как движения врага у нас реализовано через него. Например, можно попробовать выставить параметр Stopping Distance. Со значением 1 волк всё еще забегает внутрь игрока. А вот при значении 2 волк даже со скоростью бега останавливается вполне адекватно.

Разработчики компонента НавМеш уже многое предусмотрели.
Разработчики компонента НавМеш уже многое предусмотрели.

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

Бег на месте конечно полезен для здоровья, но волки итак здоровее некуда!
Бег на месте конечно полезен для здоровья, но волки итак здоровее некуда!

Анимация не переключается и скорость не сбрасывается из-за того, что в скрипте Enemy мы выставили дистанцию сброса скорости равно 1,5.

Если волк окажется на дистанции между 1.5 и 2, то его НавМеш остановит, но скрипт скорость еще не сбросит.
Если волк окажется на дистанции между 1.5 и 2, то его НавМеш остановит, но скрипт скорость еще не сбросит.

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

Теперь этот параметр у нас явно указан и будет автоматически настраивать дистанцию движения в компоненте движения.
Теперь этот параметр у нас явно указан и будет автоматически настраивать дистанцию движения в компоненте движения.

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

Преследовал, преследовал и чего-то приуныл.
Преследовал, преследовал и чего-то приуныл.

Но вот сама эта остановка выглядит весьма нелогичной. Но вполне объяснимой. Триггер атаки AttackTrigger находится перед мордой волка, а игрок в него не попадает. При этом волк находится на дистанции меньше stoppingDistance, когда ему положено уже остановится. А еще игрок остаётся в триггере обнаружения DetectTrigger и поэтому волк остаётся в режиме преследования. В таком пассивном преследовании - вроде помнит о том, что идёт за целью, чтобы её атаковать, но не особо что-то для этого делает. Жизненно, не права ли? :)

Волк понимает, что он достаточно близко к игроку, чтобы атаковать, но игрок вне зоны его атаки.
Волк понимает, что он достаточно близко к игроку, чтобы атаковать, но игрок вне зоны его атаки.

А ведь волку всего-то надо повернуться к игроку и можно будет спокойно кусать. Надо нашему серому санитару леса в этом как-то помочь. Компонент движения NavMeshAgent автоматически поворачивает объект во время движения, но если объект стоит на месте, то поворота от него не дождешься.

Придётся крутить-вертеть волка в ручном режиме! :) Тут нам понадобится научиться работать с векторами и вращением.

Векторная математика для расчёта направления

Нам нужно вычислить вектор между игроком и волком. Для этого позицию игрока нужно вычесть из позиции волка.

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

Согласно векторной математике, результатом вычитания двух векторов будет вектор равный стороне треугольника, если два вычитаемых вектора построить из одной точки. Например, из начала координат. Для наглядности покажу с двумя осями координат (2D), но и в 3D суть та же.

-8

Таким образом, новое направление newDestination, в котором должен смотреть волк, чтобы смотреть прямо на игрока будет равно разнице player.transform.position и transform.position самого волка.

Вычисляем вектор направления волка к игроку.
Вычисляем вектор направления волка к игроку.

Плавное вращение волка к игроку

С помощью специально обученной функции Quaternion.LookRotation() мы сможем получить нужный угол поворота объекта, чтобы он смотрел по направлению этого вектора. Т.е. от волка к игроку.

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

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

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

Функция плавного поворота врага к игроку.
Функция плавного поворота врага к игроку.

Теперь надо придумать, где эту функцию вызывать. Мы могли бы вызывывать её в цикле метода FollowPlayer().

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

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

Давайте сделаем метод RotateToPlayer() асинхронным и будем его запускать вместе с методом преследования и также до тех пор пока isPlayerDetected будет true. Но чтобы волк все время не поворачивался к игроку сделаем переменную isAttack.

Частоту выполнения этого метода сделаем меньше, чтобы обеспечить плавность вращения волка.
Частоту выполнения этого метода сделаем меньше, чтобы обеспечить плавность вращения волка.

Переменную isAttack будем менять в зависимости от расстояния до игрока, как и скорость.

Метод преследования запускает метод поворота к игроку. Работать они будут параллельно, а выключаться одновременно, когда isPlayerDetected будет false.
Метод преследования запускает метод поворота к игроку. Работать они будут параллельно, а выключаться одновременно, когда isPlayerDetected будет false.

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

Заметил еще вот такую проблему: волк иногда останавливается чуть дальше, разворачивается и всё равно не достаёт. Решим эту проблему в следующий раз, а пока хочется узнать о ваших успехах - всё ли получается? С чем возникают проблемы? А может вы уже доделали игру и выложили её куда-то? С удовольствием бы посмотрел) Как обычно с вас лайки и подписки, если нравится материал, а с меня продолжение! :) Продолжаем воплощать мечту о создании своей игры в реальность! :)

Слышь, человек, подойди поближе - кое-чего скажу...
Слышь, человек, подойди поближе - кое-чего скажу...