Чтобы упростить и ускорить процесс создания анимаций для Ruby, его контроллер уже предоставлен в проекте.
Выберите персонажа Ruby и убедитесь, что у него есть компонент Animator, а в свойстве Controller уже указан контроллер Ruby, который находится в папке Animation:
Откройте Animator Ruby:
Обратите внимание, что у нас есть три состояния, которые представляют собой четыре дерева смешивания:
- Moving (перемещение): воспроизводится, когда Руби бежит.
- idle (покой): воспроизводится, когда Ruby стоит на месте.
- Hit (повреждение) : воспроизводится, когда Руби сталкивается с роботом или зоной урона.
- Launch (бросок) : воспроизводится, когда Руби сталкивается с роботом или зоной урона.
Белые стрелки между состояниями — это связи Transitions. Они определяют переход между состояниями. Например, нет связи (перехода) между повреждением и броском, потому что Руби не может бросить снаряд, когда поврежден!
Нажмите на стрелку вниз между Moving и Idle и посмотрите в инспекторе, чтобы увидеть настройки для этой связи:
Большинство этих настроек, например полосы на графике, полезны в 3D, где анимацию можно смешивать. В 2D нам нужны только несколько.
Обратите внимание, что параметр "Has Exit Time"(имеет время выхода) не отмечен. Это означает, что текущая анимация движения мгновенно изменится на анимацию покоя и не будет ждать завершения, то есть отображения последнего спрайта из своей цепочки.
Наиболее важной частью является раздел «Conditions»(условия) внизу, где указаны условия перехода от одной анимации к другой.
Обычно переход от одной анимации к другой происходит в двух случаях :
- Если условия в блоке «Conditions» отсутствуют, то переход произойдет в конце анимации. Если вы посмотрите на связь, идущую от Hit к idle, то увидите, что флажок Has Exit Time установлен и не задано ни одно условие. Это означает, что анимация Hit будет воспроизводиться один раз, а затем автоматически запустится анимация Idle.
- Если же задано условие перехода на основе параметров, то переход будет осуществляться только в том случае, когда условие станет истинным.
В случае перехода от Moving к Idle, переход произойдет, если скорость Ruby станет меньше 0.1.
Давайте посмотрим на параметры. Видно, что параметры Hit и Launch выглядят иначе, чем параметры перемещения. Это потому, что бросок и повреждение являются одноразовыми событиями, а не долгосрочными.
Этот тип параметра называется «Триггер» . Если вы используете этот параметр в качестве условия для перехода, то переход произойдет, если триггер активируется из кода.
Теперь, когда вы увидели, как настроен контроллер, давайте изменим скрипт RubyController, чтобы он изменял параметры контроллера и анимации могли сменять друг друга.
Как и в случае с роботом , добавьте переменную Animator и в методе Start используйте GetComponent для привязки к компоненту:
Давайте заведем переменную типа Vector2 с именем lookDirection, инициализированную значением (1,0):
Vector2 lookDirection = new Vector2(1,0);
В этой переменной мы будем хранить направление, куда Ruby повернут на данный момент, так скажем направление "взгляда". По сравнению с роботом Ruby может стоять на месте, тогда Move X и Move Y равны 0, поэтому контроллер анимаций не знает, какое направление использовать, пока мы не укажем ему это напрямую, поскольку в скрипте мы знаем какое движение было последним у Ruby, значит туда он и должен смотреть.
Давайте реализуем сохранения направления, в котором смотрит Ruby в переменной lookDirection.
На данный момент считывание перемещения у нас происходит следующим образом:
float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");
Нам нужно объединить эти параметры вертикального и горизонтального движения в Vector2 с именем move, чтобы было удобнее проводить дальнейшие вычисления:
Vector2 move = new Vector2(horizontal, vertical);
Теперь нам нужен условный оператор, при помощи которого мы определим, что перемещение началось и сможем задать соответствующее направление "взгляда" Ruby.
Так как способ, которым компьютеры хранят числа с плавающей запятой, означает небольшую потерю точности, то вместо точного сравнения используется метод Mathf.Approximately.
if(!Mathf.Approximately(move.x, 0.0f) || !Mathf.Approximately(move.y, 0.0f))
{
lookDirection.Set(move.x, move.y);
lookDirection.Normalize();
}
Обратите внимание на операторы:
- оператор "!" перед Mathf.Approximately означает "не". Соответственно, если метод Mathf.Approximately сравнивает move.x с нулем и возвращает истину, если это так, то использование оператора "!" наоборот вернет нам истинное значение, если move.x не равен нулю.
- оператор "||" означает "или" в условном операторе. Значит общее условие будет истинным, если истинным будет хотя бы одна или обе части этого условия. И дальнейший код будет выполнен, если move.x не равно нули ИЛИ move.y не равно нулю.
Если наше условие вернет истину, то в вектор направления "взгляда" lookDirection мы зададим соответствующие координаты горизонтального и вертикального смещения:
lookDirection.Set(move.x, move.y);
Если оба смещения нулевые, то направление взгляда менять не нужно.
Последняя строка
lookDirection.Normalize();
выполняет нормализацию вектора, чтобы сделать его длину равной 1.
Что такое нормализация. Как было сказано ранее, типы Vector2 сохраняют позиции, но они также могут хранить направления!
Значение (1,0) хранит положение на одну единицу правее центра нашего мира, но также хранит направление вправо (если вы проведете стрелку от координаты (0,0) до (0,1), то получите стрелку, указывающую вправо - это и есть направление, заданное вектором (1,0).
Длина вектора определяется длиной этой стрелки. Так, например, Vector2, равный (0,-2), имеет длину 2 и указывает вниз. Если мы нормализуем этот Vector , он станет равным (0,-1) , то есть по-прежнему направлен вниз, но будет имеет длину 1.
В общем, вы нормализуете векторы, которые хранят направление, потому что длина не важна, важно только направление. Это значительно упрощает расчеты для Uinty.
ПРИМЕЧАНИЕ. Вы никогда не должны нормализовать вектор, хранящий позицию, потому что, тогда изменится x и y, а для позиции важны именно значения этих координат!
Осталось теперь передать вычисленный вектор направления "взгляда" в параметры аниматора:
animator.SetFloat("Look X", lookDirection.x);
animator.SetFloat("Look Y", lookDirection.y);
animator.SetFloat("Speed", move.magnitude);
Значение move.magnitude определяет величину вектора, которая определяет скорость движения персонажа.
Осталось только запустить анимацию повреждения. Отправка триггера в аниматор осуществляется через метод SetTrigger(«Имя триггера»).
Итак, в методе ChangeHealth внутри блока if (amount < 0) нам просто нужно добавить:
animator.SetTrigger("Hit");
Проверьте свой скрипт:
Сохраните скрипт и запустите игру. Персонаж теперь должен правильно анимироваться:
Сохраните изменения в проекте.