Найти тему

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

Part 4 https://dzen.ru/a/Y7z76yh-uFcGsopj

Давайте доработаем движение Джона Лимона, чтобы он поворачивался в сторону своего движения.

Вращение в трехмерном пространстве можно задать одним из двух способов:

  • углы Эйлера (Euler angles)
  • кватернион (Quaternions)

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

Иллюстрацию "складывания рамок" можно посмотреть на видео, начиная с 0:37 до 1:12.

В нашем проекте это не совсем очевидно, но если мы развернем кота по оси X на -90 градусов, то последующий поворот по оси Y и Z будет одинаковым. Можете повторить эту ситуацию самостоятельно.

Шарнирный замок
Шарнирный замок

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

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

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

Ошибка CS0029 Не удается неявно преобразовать тип "UnityEngine.Vector3" в "UnityEngine.Quaternion".

Таким образом, несмотря на отображение в инспекторе, Unity внутри для представления ориентации или вращения игровых объектов использует именно кватернионы. Что же такое кватернион?..

Представьте, что Вы держите в руках спицу, на конце которой шар. Спица — это ось вращения, её направление мы можем задать через вектор (x, y, z). А угол вращения, на который мы можем повернуть шар на спице по часовой стрелке или против — это угол поворота. Кстати именно потому, что вращение измеряется таким образом кватернион не может представлять вращение больше 180 градусов по какой-либо из осей.

Кватернион задается четырьмя параметрами (X, Y, Z и W). XYZ — та самая ось поворота, которая предварительно нормализуется и каждый компонент умножается на синус половины угла, а параметр W — угол поворота, который задается через косинус половины угла.

Если сейчас Вам захотелось закрыть статью и пойти попить чаю, то это нормально), но не спешите, не все так страшно... В Unity реализовано много методов, которые облегчают работы с кватернионами и поворотами так , что Вам не обязательно задумываться об их внутреннем строении, а можно поворачивать всё что угодно и как угодно без скачков, торможений и сложных расчетов.

Например, поворот объекта на 45 градусов по оси X и на 60 градусов по оси Y можно записать вот таким образом:

Quaternion.Euler(45, 60, 0);

То есть Unity за нас создает кватернион из привычных нам углов Эйлера.

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

Вернемся к нашему коту...

Нам необходимо повернуть кота в сторону движения. У класса Vector3 есть метод RotateTowards(). Этот метод принимает 4 параметра:

Vector3 RotateTowards ( Vector3 current , Vector3 target , float maxRadiansDelta , float maxMagnitudeDelta );

Метод итерационный, срабатывает несколько раз, пока исходный вектор current не станет равным конечному вектору target. Угол maxRadiansDelta задает максимальное значение угла поворота за один шаг, а maxMagnitudeDelta - максимальную величину вектора за один шаг.

Чтобы получить текущую ориентацию кота (это и есть наш исходный вектор, который надо повернуть), мы используем вектор transform.forward, который возвращает нормализованный вектор, представляющий ось Z преобразования в мировом пространстве. То есть это вектор, который имеет направление взгляда кота. Этот вектор нам надо плавно повернуть в строну движения, которое у нас задано вектором movement. Почему плавно? Потому что это добавит реалистичности в нашу игру, очень странно бы смотрелся кот, который просто меняет свой разворот на 90 градусов. Гораздо красивее смотрится, когда мы замечаем, что кот поворачивается постепенно.

Соответственно следующая строка сделает нам плавный поворот кота от текущего положения, которое мы получаем при помощи transform.forward к конечному положению, заданному вектором movement:

Vector3 desiredForward = Vector3.RotateTowards(transform.forward, movement, 1 * Time.deltaTime, 0);

Параметр максимального значения угла мы зададим пока равным 1 * Time.deltaTime.

Почему просто не взять значение равное 1, зачем умножать его на Time.deltaTime?

Обновление вызывается каждый кадр, и если ваша игра работает со скоростью 60 кадров в секунду, это означает, что этот метод будет вызываться 60 раз в секунду. В каждом вызове будет очень небольшое изменение, так что за 60 кадров вы получите желаемое изменение на секунду. Но как насчет игры, работающей со скоростью 30 кадров в секунду? Только половина вызовов метода будет выполнена за одно и то же время, поэтому будет выполнена только половина хода. Вы же не хотите, чтобы количество кадров в секунду влияло на скорость вращения персонажа — это нехорошо! Поэтому, если мы умножим любое изменение в секунду на количество времени, которое занял кадр, а это и есть параметр Time.deltaTime (время, прошедшее с момента предыдущего кадра), тогда изменения не будут зависеть от скорости обновления кадров.

Теперь у нас есть вектор, который постепенно меняет поворот кота в ту сторону, куда он идет.

Осталось применить этот разворот к физическому телу кота. Для этого сначала создадим кватернион с помощью метода LookRotation, который создает кватернион поворота к вектору desiredForward:

Quaternion rotation = Quaternion.LookRotation(desiredForward);

И осталось использовать этот кватернион для поворота физического тела:

rBody.MoveRotation(rotation);

Код итогового скрипта, код дописан только в методе FixedUpdate():

Код скрипта
Код скрипта

Как теперь выглядит движение кота можно посмотреть на видео:

Осталось только вынести настройки движения и поворота в настраиваемые параметры Unity.

Для этого объявим две публичные переменные:

public float turnSpeed = 20f, moveSpeed = 10f;

Скорость движения используем в строке с перемещением физического тела:

rBody.velocity = moveSpeed * movement;

А скорость разворота используем при вычислении плавного поворота кота в сторону движения:

Vector3 desiredForward = Vector3.RotateTowards(transform.forward, movement, turnSpeed * Time.deltaTime, 0);

Добавим еще обнуление вращения кота вокруг своей оси перед применением перемещения и поворота, что избавит нас от закручивания кота при соприкосновении с препятствием:

rBody.angularVelocity = Vector3.zero;

Скрипт кота будет выглядеть следующим образом (на скрипте свернуты методы Start() и Upadte(), так как в них нет изменений):

Код скрипта
Код скрипта

Теперь Вы сможете настроить скорость перемещения и поворота как хотите во время тестирования.

Публичные параметры скорости
Публичные параметры скорости

На этом скрипт пугливого кота Джона Лимона готов.

Part 6 https://dzen.ru/a/Y7xa1_xg6BKFpvbR