В прошлой статье мы научили волка воскрешаться, но заметили, что во время нападения он перемещается некорректно и заходит внутрь персонажа - нужно с этой проблемой разобраться.
Настройка движения врага через NavMesh
В начале волк подбегает к игроку и останавливается на нормальной дистанции, начиная его кусать. Если мы начинаем уходить от него, то волк подбегает и снова кусает - вроде бы все нормально. Но вот если начать убегать от него со скоростью бега, то проблема проявляется во всей красе.
Видимо волк при беге успевает набрать большую скорость, а компонент при торможении сбрасывает скорость постепенно. Из-за этого волк успевает пробежать лишние пол метра и забежать внутрь игрока.
Попробуем поиграться с параметрами компонента NavMesh, так как движения врага у нас реализовано через него. Например, можно попробовать выставить параметр Stopping Distance. Со значением 1 волк всё еще забегает внутрь игрока. А вот при значении 2 волк даже со скоростью бега останавливается вполне адекватно.
Но тут проявляется новая проблема. Если убегая от волка уйти немного в сторону, то движение волка может остановиться, но скорость не станет равной нулю, а соответственно и анимация не переключится. Он просто будет бежать на месте.
Анимация не переключается и скорость не сбрасывается из-за того, что в скрипте Enemy мы выставили дистанцию сброса скорости равно 1,5.
Вот она проблемность магических чисел. Благо найти это число и понять его смысл было не сложно, но вынести его в отдельную переменную stoppingDistance не помешает. Давайте сразу это и сделаем. Предлагаю также передавать это значение в компонент NavMeshAgent, чтобы его переменная stoppingDistance автоматически подстраивалась под заданную нами дистанцию.
Проверим , что происходит с анимациями волка при резких поворотах. Видим, что волк теперь как положено меняет анимацию в момент этой несанкционированной остановки.
Но вот сама эта остановка выглядит весьма нелогичной. Но вполне объяснимой. Триггер атаки AttackTrigger находится перед мордой волка, а игрок в него не попадает. При этом волк находится на дистанции меньше stoppingDistance, когда ему положено уже остановится. А еще игрок остаётся в триггере обнаружения DetectTrigger и поэтому волк остаётся в режиме преследования. В таком пассивном преследовании - вроде помнит о том, что идёт за целью, чтобы её атаковать, но не особо что-то для этого делает. Жизненно, не права ли? :)
А ведь волку всего-то надо повернуться к игроку и можно будет спокойно кусать. Надо нашему серому санитару леса в этом как-то помочь. Компонент движения NavMeshAgent автоматически поворачивает объект во время движения, но если объект стоит на месте, то поворота от него не дождешься.
Придётся крутить-вертеть волка в ручном режиме! :) Тут нам понадобится научиться работать с векторами и вращением.
Векторная математика для расчёта направления
Нам нужно вычислить вектор между игроком и волком. Для этого позицию игрока нужно вычесть из позиции волка.
Почему так? Напомню, что позиция объекта в Unity описывается типом данных Vector3. Это вектор, который идёт из начала координат в точку, где расположен объект. У нас есть вектор указывающий на игрока и вектор указывающий на волка.
Согласно векторной математике, результатом вычитания двух векторов будет вектор равный стороне треугольника, если два вычитаемых вектора построить из одной точки. Например, из начала координат. Для наглядности покажу с двумя осями координат (2D), но и в 3D суть та же.
Таким образом, новое направление newDestination, в котором должен смотреть волк, чтобы смотреть прямо на игрока будет равно разнице player.transform.position и transform.position самого волка.
Плавное вращение волка к игроку
С помощью специально обученной функции Quaternion.LookRotation() мы сможем получить нужный угол поворота объекта, чтобы он смотрел по направлению этого вектора. Т.е. от волка к игроку.
Далее мы могли бы просто задать волку этот угол поворота, но это развернёт волка мгновенно, что будет нереалистично. Поэтому воспользуемся ещё одной полезной функцией Quaternion.Lerp(), которая будет будет пошагово приводить угол поворота волка к нужному.
Каждый шаг поворота будет выполняться при каждом вызове функции, поэтому её нужно вызвать много раз подряд. Например, её можно вызывать каждый кадр, а шаг привязать ко времени между кадрами. Время между кадрами Time.deltaTime имеет очень маленькое значение и вращение будет происходить очень медленно. Поэтому не помешает домножить на коэффициент, который по сути будет скоростью вращения.
Теперь надо придумать, где эту функцию вызывать. Мы могли бы вызывывать её в цикле метода FollowPlayer().
Но результат нам не понравится. Волк будет поворачиваться, но будет это делать заметными рывками, что смотрится не очень хорошо.
Давайте сделаем метод RotateToPlayer() асинхронным и будем его запускать вместе с методом преследования и также до тех пор пока isPlayerDetected будет true. Но чтобы волк все время не поворачивался к игроку сделаем переменную isAttack.
Переменную isAttack будем менять в зависимости от расстояния до игрока, как и скорость.
Проверяем и видим, что волк плавно, хоть и немного медленно поворачивается к игроку, когда находится близко к нему, после чего продолжает атаковать. Можем настроить коэффициент скорости вращения по своему желанию.
Заметил еще вот такую проблему: волк иногда останавливается чуть дальше, разворачивается и всё равно не достаёт. Решим эту проблему в следующий раз, а пока хочется узнать о ваших успехах - всё ли получается? С чем возникают проблемы? А может вы уже доделали игру и выложили её куда-то? С удовольствием бы посмотрел) Как обычно с вас лайки и подписки, если нравится материал, а с меня продолжение! :) Продолжаем воплощать мечту о создании своей игры в реальность! :)