Part 3 https://dzen.ru/a/Y7v8b3i9vgE1Yljt
У персонажа уже есть анимация, но мы не можем ее посмотреть, поскольку наш пугливый кот еще не умеет ходить. Давайте это исправим.
Откройте префаб JohnLemon для редактирования в режиме префаба.
В инспекторе уже есть два компонента: Transform и Animator. Но для того, чтобы сделать игру быстро и качественно, нам необходимо подключить физическую систему игрового движка, что поможет нам не заниматься просчетами столкновений и наложения сил, а сосредоточиться на логической и визуальной составляющих игры.
Но прежде чем мы займемся физической системой давайте доработаем префаб кота. С чем это связано?..
Игры работают так же, как мультфильмы: на экране отображается изображение, которое меняется много раз в секунду, создавая иллюзию движения. Эти изображения называются кадрами, а процесс вывода этих кадров на экран называется рендерингом . Для мультфильмов каждый кадр уже известен и нарисован, и эти кадры просто последовательно запускаются друг за другом. Но в играх следующее изображение может сильно отличаться от текущего, потому что игрок оказывает влияние на то, что произойдет дальше. Каждое изображение должно быть обработано на основе пользовательского ввода — и, поскольку это изменение может происходить каждую долю секунды, программирование, которое определяет, что отображать, также работает с этой скоростью. Это называется циклом обновления (Update loop).
Каждый раз, когда отображается кадр, по порядку происходит ряд вещей. Если упрощенно, то у пользовательских компонентов вызывается их метод Update, и новое изображение отображается на экране. Эти обновления различаются по длине в зависимости от сложности вычислений и рендеринга.
Трехмерная анимация отличается от двумерной, с которой мы до этого работали. Это уже не анимация спрайтами, это скелетная анимация. Ее принцип мы посмотрели в предыдущем уроке, когда изменили поворот части руки через компонент Transform. Соответственно скелетная анимация меняет значения свойств компонента Transform и по умолчанию он выполняет это в соответствии с рендерингом, то есть с циклом обновления (Update loop). Поэтому если мы будем изменять положение физического тела в Update loop и одновременно применять анимацию, наш кот просто не будет перемещаться или будет перемещаться неправильно, с ошибками.
Однако есть еще один отдельный цикл, который выполняет все физические операции. Этот цикл обновляется через равные промежутки времени FixedUpdate, которые можно изменить в настройках. Именно по этой причине FixedUpdate следует использовать при применении сил, крутящих моментов или других функций, связанных с физикой, поскольку вы знаете, что они будут выполняться точно синхронно с самим физическим движком.
Метод, соответствующий Update loop, вызывается перед каждым визуализируемым кадром и называется Update(). А метод, который вызывается перед тем, как физическая система разрешает любые взаимодействия физических объектов, называется соответственно FixedUpdate(). По умолчанию он вызывается 50 раз в секунду.
С учетом этих новых знаний давайте начнем...
Чтобы реагировать на физику, персонажу нужны еще два компонента: Rigidbody и Collider, которые помечают игровой объект как часть физической системы, которая может двигаться и взаимодействовать с другими частями этой физической системы, например, стенами или врагами.
Для начала добавим компонент Rigidbody, кликая по кнопке "Add Component", выберем компонент Rigidbody. Обратите внимание, что в трехмерных проектах используется компонент без окончания 2D.
Теперь, если запустить игру, то персонаж начнет падать вниз, так как у него появился компонент физического тела, а значит на него есть масса и на него действует сила гравитации.
Давайте избавимся от этого падения. Можно убрать гравитацию через флажок «Use Gravity», но персонаж все равно может упасть, если столкновение толкнет его вниз, или улететь, если столкновение подтолкнет его вверх. Нам этого не нужно, поэтому в данном случае удобнее будет ограничить движение персонажа при помощи привязок физического тела. Найдите компонент Rigidbody и щелкните стрелку, чтобы развернуть свойство Constraints.
В трехмерной сцене цветные стрелки в правом верхнем углу представляют положительное направление каждой оси, а серая стрелка напротив представляет отрицательное направление:
· ось Х красная
· ось Y зеленая
· ось Z синяя
Наша задача предотвратить движение персонажа вверх и вниз, поэтому установите флажок Freeze Position Y.
Что касается поворота персонажа, то здесь нам необходимо оставить только вращение персонажа вокруг своей вертикальной оси, которая направлена вверх, то есть оси Y, а остальные вращения убрать.
Теперь компонент Rigidbody настроен, и ваш персонаж будет реагировать на воздействие физической системы правильно. Тем не менее, он еще не может взаимодействовать с другими объектами физической системы— ничто не может столкнуться с ним, и он не может столкнуться ни с чем другим. Для этого ему необходим коллайдер.
Коллайдеры определяют форму объекта для физических столкновений, позволяя вашему персонажу взаимодействовать с другими объектами на сцене. Существуют компоненты коллайдера самых разных форм, но самый простой из них, который лучше всего подходит JohnLemon, — это Capsule Collider.
Добавим компонент Capsule Collider , кликая по кнопке "Add Component", выберем компонент Capsule Collider. Обратите внимание, что в трехмерных проектах используется компонент без окончания 2D.
По умолчанию коллайдер не покрывает модель персонажа, поэтому нужно изменить размер и положение Capsule Collider, чтобы он приблизился к форме JohnLemon. Измените свойство «Center» на ( 0, 0.7, 0 ). Свойство «Radius» на 0.4, а свойство «Height» на 1.4.
Следующий шаг заставить нашего персонажа перемещаться под управлением игрока. Для этого нам необходимо создать скрипт. Скрипт – это такой же компонент, который мы можем добавить игровому объекту на сцене.
Добавим компонент скрипта персонажу. Скрипт назовем PlayerController.
Сразу перенесем созданный скрипт в папку "Scripts":
Откроем скрипт, два раза кликнув на нем. Мы знакомы уже с классом, который работает с пользовательским вводом и умеет определять какие клавиши были нажаты игроком. Это класс Input, а методы определяющие нажатие клавиш перемещения по горизонтали и вертикали соответственно:
Input.GetAxis ("Horizontal");
Input.GetAxis ("Vertical");
В методе Update определим были ли нажаты клавиши перемещения:
float deltaHorizontal = Input.GetAxis ("Horizontal");
float deltaVertical = Input.GetAxis ("Vertical");
Далее для удобства работы в трехмерном пространстве заведем дополнительную переменную для хранения перемещения. В Unity трехмерное пространство представлено тремя координатами, которые вместе образуют вектор. Тип данных для представления вектора в Unity называется Vector3.
Обе наши переменные deltaHorizontal и deltaVertical находятся в области действия метода Update, потому что это блок кода, в котором они были объявлены. Говорят, что они являются локальными для метода Update. Если вы хотите использовать переменную в нескольких разных методах, вы можете создавать переменные за пределами области действия метода. Такие переменные называются глобальными.
Заведем глобальную переменную для хранения перемещения:
Vector3 movement;
И зададим значение этой переменной в методе Update() после того, как определили нажатие клавиш перемещения персонажа.
movement.Set(deltaHorizontal, 0f, deltaVertical);
Координаты вектора movement теперь будут по оси x = deltaHorizontal, по оси y = 0f, по оси z = deltaVertical.
Теперь нужно решить небольшую проблему. Если мы вспомним определение методов Input.GetAxis(), то догадаемся, что deltaHorizontal и deltaVertical могут иметь максимальное значение 1.
Значит вектор движения movement по координатам X и Z может иметь максимальное значение 1.
Если игрок нажимает клавишу перемещения вправо, то
deltaHorizontal = 1, deltaVertical = 0,
значит персонаж идет строго по положительному направлению оси X, и его скорость задается вектором (1, 0, 0). Длина вектора скорости (расстояние от начальной до конечной точки вектора) вычисляется по формуле:
Соответственно персонаж перемещается вдоль положительного направления оси X со скоростью равной 1.
Если игрок нажимает клавишу перемещения вверх, то
deltaHorizontal = 0, deltaVertical = 1,
значит персонаж идет строго по положительному направлению оси Z, и его скорость задается вектором (0, 0, 1). Длина вектора скорости вычисляется по указанной выше формуле и равна 1. Соответственно персонаж перемещается вдоль положительного направления оси Z со скоростью равной 1.
Но если игрок нажмет одновременно клавиши перемещения вправо и вверх? Тогда по формуле длина вектора скорости будет равна:
Это означает, что ваш персонаж будет двигаться быстрее по диагонали, чем по одной из осей. Чтобы этого не произошло, необходимо нормализовать вектор перемещения. Нормализация вектора означает сохранение направления вектора тем же самым, но изменение его величины на 1. То есть уменьшаются координаты вектора таким образом, чтобы направление его сохранилось, а длина при этом стала равна 1.
Таким образом мы избавимся от ошибки перемещения персонажа с разной скоростью!
Нормализация вектора в коде осуществляется вызовом метода Normalize():
movement.Normalize();
Таким образом наш скрипт на данном этапе выглядит следующим образом:
Теперь давайте переместим нашего пугливого кота согласно полученному вектору перемещения. Не забываем, что мы "подключили" физику, поэтому перемещать нужно именно физическое тело, то есть прикладывать силу к компоненту физического тела. Для этого заведем переменную, которую свяжем с компонентом Rigidbody и вектор перемещения будем задавать именно для нее.
Rigidbody rBody;
rBody = GetComponent<Rigidbody>();
Перемещение физического тела - это задание ему силы скорости.
rBody.velocity = movement;
При этом вспомним про циклы обновления Update loop и FixedUpdate и изменение скорости физического тела будет производить в новом методе, который так и называется FixedUpdate(). Код скрипта:
Отлично, если запустить игру, то наш кот теперь может перемещаться. Но давайте добавим смену анимации.
Для этого нам необходимо обратиться к компоненту Animator аналогично, как мы сделали это с компонентом Rigidbody. Заведем глобальную переменную, свяжем ее с компонентом аниматора.
Animator anim;
anim = GetComponent<Animator>();
Осталось изменить параметр аниматора. В предыдущей части мы создали для Animator логический параметр "IsWalking", который использовали в условиях перехода между анимациями. Теперь наша задача изменять этот параметр в зависимости от того, нажал игрок клавиши перемещения или нет.
Давайте заведем вспомогательную логическую переменную isWalking, значение которой изначально будет ложное.
Если пользователь нажал хотя бы одну из клавиш перемещения, то значение переменной меняется на истинное.
bool isWalking = false;
if (deltaHorizontal != 0 || deltaVertical != 0)
{
isWalking = true;
}
Теперь, когда мы знаем текущее состояние персонажа, мы можем изменить параметр Animator следующим образом:
anim.SetBool("IsWalking", isWalking);
Обращаемся к переменной anim, которая хранит привязку к Animator, затем используем метод SetBool для изменения логического (bool) параметра. В этом методе на первом месте пишем имя самого параметра точно так, как мы его назвали в аниматоре! А на втором месте пишем значение, которое хотим задать этому параметру. В нашем случае это значение хранится в переменной isWalking.
Код скрипта:
Теперь у кота изменяется анимация, когда он стоит или ходит.
Видео того, что должно получиться можно посмотреть по ссылке:
Пока что перемещение не учитывает поворот нашего героя в сторону движения. Этим мы займемся в следующей части.
Part 5 https://dzen.ru/a/Y743PgiNZA2nhK_m