Механика бега, автоматическое переключение анимаций персонажа, гравитация, столкновения и границы карты.
В предыдущей статье мы доделали нашего первого врага и главному герою жизненно важно стало научиться бегать.
Но давайте, прежде чем добавить ему возможность переходить с шага на бег, сделаем так, чтобы анимации переключались в зависимости от его скорости движения.
Не забудьте подписать на канал и поехали!
Автоматическое переключение и смешивание анимаций
Есть в аниматоре Unity такая замечательная вещь как Blend Tree, которая в зависимости от значения анимационных параметров переключается между входящими в неё анимациями. Да не просто переключаться, а плавно переходит между ними, смешивая их между собой.
Суть задумки в том, чтобы переключаться между состоянием покоя, ходьбы и бега в зависимости от скорости движения персонажа. Анимационный параметр скорости (Velocity) у нас уже имеется, так что остаётся только создать этот самый Blend Tree, кликнув в окне аниматора на пустое место правой клавишей мыши и во всплывающем меню выбрать Create State -> From New Blend Tree.
Создав Blend Tree нужно его открыть кликнув двойным щелчком мыши по нему. В открывшейся вкладке, кликнув на его блок, мы увидим в инспекторе поле Motion, в которое нужно добавить с помощью иконки "+" и пункта Add Motion Field, три поля движения - для анимации покоя, ходьбы и бега.
Добавляем три поля.
Галочку с Automate Threshholds убираем, чтобы самим выставить значения скорости, которые будут соответствовать нашим анимациям.
В пустые поля Motion перетащим нужные нам анимации Idle, Walk и Run, найдя их в папках нашего проекта (т.к. анимации есть и у волка, то важно не перепутать). Удобно будет переключится в базовый слой аниматора (Base Layer), чтобы кликнув на имеющиеся анимации, быстро найти их в папках проекта.
Выставим значения скорости 0, 2 и 4 для покоя, ходьбы и бега соответственно.
Теперь переключимся в базовый слой аниматора (Base Layer) и удалим блоки анимации Idle и Walk.
Blend Tree сделаем блоком по умолчанию, кликнув на нём правой кнопкой мыши и выбрав Set as Layer Default State.
Из блока Death сделаем переход в него при значении isDeath равным false.
Запустим и проверим. Если нигде ничего не упустили, то персонаж двигается во время ходьбы и переходит в анимацию покоя при остановке. Но происходит это совсем не плавно. А всё потому, что значение скорости мы меняем в нашем коде, просто задавая 0, либо 2, а анимационный параметр мгновенно его принимает. Для плавности перехода анимации нам нужно анимационный параметр менять постепенно.
Плавное изменение параметров анимации
Для плавного изменения анимационного параметра Velocity нам потребуется ещё одна переменная, в которой будем хранить промежуточные значения скорости во время её постепенного изменения. Назовём её currentSpeed и сделаем пока публичной, чтобы в инспекторе за ней наблюдать.
В методе Update() будем её изменять с помощью математического метода интерполяции в зависимости от времени Mathf.MoveTowards().
- В качестве первого аргумента впишем туда currentSpeed, который будем плавно менять.
- В качестве второго аргумента текущую скорость нашего контроллера персонажа controller.velocity.magnitude
- В качестве третьего аргумента будет Time.deltaTime умноженный на коэффициент, который будет определять скорость изменения анимации. Выберем пока его равным 4.
В аниматор будем передавать теперь currentSpeed, вместо speed.
Mathf - это стандартная библиотека, содержащая множество полезных математических функций, как, например, MoveTowards(), которая меняет первый аргумент в сторону второго аргумента шагом равным третьему аргументу. Таким образом можно плавно за несколько шагов привести первое значение ко второму. Чтобы привязать шаги ко времени используем Time.deltaTime.
Теперь анимация меняется плавно и скорость её изменения мы можем настроить с помощью коэффициента, который при желании можно вынести в отдельную переменную и настраивать прям в инспекторе.
Добавляем персонажу возможность бегать
Чтобы наш герой смог драпать от волка сверкая пятками, нам потребуется не только переключить анимацию, но и изменить его скорость. Поэтому предлагаю добавить 2 новых поля walkSpeed и runSpeed, значения которых мы и будем использовать для разных режимов передвижения.
Сделаем так, чтоб при нажатой клавиши Shift, персонаж бегал, в при отпускании снова переходил на шаг.
Теперь можно совершать марафонские забеги с волком и от волка! Соотношение скоростей бега героя и волка можно будет подобрать так, чтобы играть было достаточно интересно. Это уже геймдизайн!
Blend Tree еще и удобен тем, что мы можем подобрать подходящие скорости проигрывания анимаций (в третьем столбце, там где сейчас единицы), чтобы движение персонажа выглядело более естественным.
Смена скорости врагов
По тому же принципе мы можем сделать смешивание анимаций и смену скорости движения для волка. А то что-то он носится как угорелый. Пусть бродит в спокойной режиме, а при обнаружении игрока переходит на бег.
Создадим в его аниматоре анимационный параметр Velocity и Blend Tree, в котором выберем подходящие анимации для покоя, ходьбы и бега.
В основном слое аниматора (Base Layer) заменим блок анимации бега на Belnd Tree и сделаем переходы между ним и блоком анимации атаки. Переход к атаке надо сделать без галочки Has Exit Time, да и обратно лучше тоже без нее, но при этом в настройках смешивания лучше сделать пересечение анимаций как можно больше, чтобы момент с анимационным событием точно отработал.
В скрипте AnimalMove добавим два поля - walkSpeed и runSpeed. Будем переключать скорость компонента движения при сработке DetectTrigger. В методе Start() зададим первоначальную скорость равную скорости ходьбы.
А чтобы управлять анимационным параметром Velocity добавим метод Update() и переменную currentSpeed. Будем плавно её изменять с помощью Mathf.MoveTowards().
Еще нужно внести изменения в скрипт Enemy, потому-что там в методе FollowPlayer() мы тоже изменяем скорость. Во время преследования волк должен переключаться между нулевой скоростью и runSpeed, а при выходе из метода должен перейти в walkSpeed.
Лучше скорость бега игрока (runSpeed) сделать 4.5 или 5, чтоб было проще убежать от волка.
Теперь когда волк теряет игрока, то спокойным шагом продолжает гулять по точкам маршрута. А когда обнаруживает, то переходит на бег и начинает преследовать.
Выносим повторяющийся код в отдельный метод
Переход к блужданию у нас всегда сопровождается проверкой, достаточно ли у волка точек маршрута. И эта строчка кода встречается в скрипте AnimalMove трижды. Поэтому будет правильно вынести её в отдельный метод. Назовём его например Walk(). Сделаем себе даже вывод сообщения в консоль на случай, если вдруг забудем добавить точки маршрута.
Теперь еще в двух местах мы можем заменить эту строчку на вызов метода Walk(). Плюс предлагаю сделать так, чтоб когда у игрока заканчивается здоровье, то волк с чувством выполненного долга шёл гулять дальше.
Для этого давайте в методе PlayerDetect() будем сохранять ссылку на скрипт Player, чтобы волк всегда через него мог узнать жив игрок или нет - надо ли продолжать его кусать. Добавим в AnimalMove поле player.
В методе PlayerDetect() сделаем сохранение ссылки на игрока. Вот только появилась проблема - внутренняя переменная метода называется точно также как и наша ссылка на игрока - player. Об этом нам VisualStudio и сообщает. Ошибка не критическая и скрипт не сломается, но логика нарушится.
В таких случаях я просто добавляю к имени внутренней переменной какие-то символы. Например, её можно назвать playerLink и проблема решена. Главное не забыть поменять ее имя везде в методе, где она встречается.
Теперь имея ссылку на игрока мы можем в скрипте Enemy в методе DamageEnable каждый раз при выключении триггера проверять, а жив ли игрок. Для проверки сделаем отдельный метод CheckPlayer(), а при его запуске, на всякий случай, будем проверять, что ссылка на игрока не равна null.
В самом методе, если ссылка на игрока есть и здоровье игрока меньше или равно нулю, то будем выключать анимацию атаки и запускать метод отправляющий волка на дальнейшую прогулку. Ну и конечно надо не забыть волку сказать, что больше живых игроков не обнаружено, поставив поле isPlayerDetect в значение false.
Теперь волк почуяв, что здоровье персонажа иссякло, пойдёт по своим делам. Правда если наткнётся на лежащего игрока, то снова куснёт и снова, убедившись, что персонаж мертв, отправится гулять. Оставим пока так. :)
Столкновение игрока с объектами
Неплохо бы еще сделать, чтоб игрок не проходил сквозь деревья, кусты и камни. Проходит он сквозь них потому-что у них нет коллайлера и нам надо его добавить. Например, SphereCollider или CapsuleCollider, подогнав их под размеры объекта. Но точнее будет воспользоваться MeshCollider, который примет форму объекта.
Причём поскольку наши объекты являются экземплярами префаба, то мы можем добавленный компонент сохранить в префабе, нажав правую клавишу мыши на компоненте и выбрав Added Component -> Apply to Prefab.
Благодаря этому действию на всех экземплярах префаба на сцене появится коллайдер. Тоже самое можно делать и с любыми изменения внесенными в настройки компонентов префаба на сцене. Например, поставить галочку возле Convex, чтобы коллайдер имел меньшую детализацию, чем сам объект и был более оптимизированным, ну и сохранить это изменение в префабе.
Теперь можно протестировать и заметить, что персонаж иногда умудряется забираться на куст, а спускаться не спешит и начинает ходить по воздуху.
Это потому-что в игре у нас пока нет гравитации. Но сначала в настройках объекта Player в его компоненте CharacterController выставим значение поля Step Offset на 0. Благодаря этому персонаж не сможет перешагивать на возвышенности выше уровня земли. Нечего ему лазать по кустам!
Простейшая гравитация
Давайте добавим простейшую гравитацию в скрипте Player в виде постоянного движения вниз.
Теперь даже если игрок куда-то заберется, то потом упадёт оттуда на землю.
Надо все объекты на сцене проверить на наличие на них коллайдеров и добавить в случае отсутствия.
Игрок будет биться об все ёлки, камни и кусты, а убегание от волка станет интереснее.
Границы игровой карты
Ну и напоследок сделаем еще одну простую, но важную штуку. Запретим игроку выбегать за край карты. Для этого просто создадим пустые объекты, добавим на них BoxCollider'ы и разместим их по границам нашей карты.
Эти объекты можно разместить внутри объекта Ground и назвать например Border. Жмём Edit Collider и с помощью квадратиков на гранях коллайдера вручную выставляем его длину и расположение. Объект можно продублировать с помощью CTRL+D и два из них развернуть на 90 градусов по Y. Теперь область перемещения игрока будет ограничена этими границами. Scale по Y лучше увеличить, чтобы игрок эти границы точно не смог перешагнуть.
Надеюсь, что этот урок был полезен! Подписывайтесь на канал, чтобы не пропустить продолжение, оставляйте свои комментарии и ставьте лайки, чтобы помочь моему каналу развиваться, ну и не забывайте практиковать всё прочитанное, потому-что только практика помогает до конца всё понять и запомнить!
Подборка всех уроков по разработке игры:
А вот тут мой дипломный проект, на котором я сам практиковался: