Паркур и Unity - часть 1
Доброго времени суток. Представлюсь, для тех, кто меня еще не знает. Меня зовут Дима. Я работаю C++ разработчиком уже более 5 лет. На данный момент работаю в крупной Gamedev-студии. Помимо работы увлекаюсь созданием образовательного контента для YouTube и Twitch каналов.
В своём небольшом пет-проекте я вдохновился идеей реализовать свою систему паркура. Что такое паркур? Паркур – скоростное перемещение и преодоление препятствий с использованием прыжковых элементов. Главная проблема, которую надо решить: персонаж должен уметь не только прыгать и бегать, но и взаимодействовать с препятствиями. Лучшим примером паркура в играх от 3-го лица можно назвать серию Assassin's Creed, а в играх от первого лица это бесспорно Mirror’s Edge. Последней я и буду вдохновляться. Разберу решение, к которому я пришёл на данный момент и какие задачи я при этом решил.
Что такое препятствие?
В первую очередь определимся, что мы будем воспринимать как препятствие, с которым может взаимодействовать игрок. После недолгих раздумий я остановился на параллелепипедах, вращения которых вокруг осей OX и OZ равно 0. То есть это параллелепипеды нижняя и верхняя грань которых строго параллельны поверхности "пола". Не стоит их воспринимать как набор абстрактных кубов, по которым будет лазать главный герой. Настраивая их масштабы, из них можно сделать платформу из Mario, плоский рекламный щит, трубы системы вентиляции и так далее.
Какая у вас цель?
На данный момент я остановился на самом простом – Персонаж должен вместо попытки запрыгнуть на препятствие, как в играх старой школы, попытаться на него залезть, что будет сопровождаться специальной анимацией.
Разбиение препятствия на составляющие
У каждого препятствия я выделил 5 основных элементов: четыре стены и верхняя грань. Стены – это та часть, по которой мы будем карабкаться, верхняя грань – цель. Для того, чтобы разбить препятствие, мы обратимся к компоненту BoxCollider. Используя его размеры, а также масштаб самого объекта, его вращение и координаты вычислим 8 точек углов параллелепипеда в мировой системе координат. Перейдём к реализации метода.
Разберём класс ParkourObstacle. Главная задача данного класса – хранить набор стен и верхнюю грань, а также уметь определять, к какой именно стене относится переданная ему точка. Рассмотрим реализацию, элементы вроде свойств и конструкторов я на данном этапе исключу, дабы не отвлекать от сути. Полный код будет предоставлен в конце статьи.
Управление
Все предварительные операции сделаны. Теперь можно реализовать поведение персонажа. Сложно назвать это управлением, скорее определение момента, когда нужно запускать паркур. Не буду акцентировать внимание на собственной реализации скрипта управления от первого лица. В нём есть недоработки и не думаю, что очередным FPS-контроллером можно кого-то удивить. Если Вас интересует его реализация, пишите в комментариях, напишу статью. Всю логику паркура я вынес в отдельный скрипт, не скажу, что он легко и просто подойдёт любому скрипту управления от первого лица. Тем не менее, с небольшими изменениями в нём и в своей логике управления, Вы можете брать его на вооружение.
В двух словах условия запуска паркура можно описать так:
- Есть препятствия доступные для попытки паркура
- Игрок не касается земли
- Нажата клавиша перемещения вперёд
- В данный момент игрок не "паркурит"
- Нажата клавиша прыжка
Думаю стоит пояснять только первое условие. Препятствия доступные для попытки будут определяться с помощью CapsuleCollider, который будет триггер-коллайдером. То есть будет только определять попадание и выход из него других коллайдеров с помощью функций OnTriggerEnter и OnTriggerExit:
Далее в кадре логики, проверив все эти условия и определив препятствия для паркура(Physics.Raycast из позиции персонажа, а не камеры, направленный вперёд), мы убеждаемся, что расстояние до верхней грани препятствия не превышает допустимый лимит (настраиваемый параметр), и после этого запускаем алгоритм залезания:
Карабканье
Процесс залезание на препятствие я разбил на три этапа:
- Переход от перемещения к паркуру
- Подъём вверх
- Небольшое перемещение вперед и возвращение управления персонажу.
Зачем нужен переход? В большинстве случаев точка начала залезания не будет совпадать с положением персонажа в пространстве, поэтому надо персонажа плавно переместить и повернуть лицом к стене. Скорость перемещения будет настраиваемым параметром. Далее следует подъём вверх и перемещение вперед. Данный код будет выполнятся в рамках одной Корутины.
Логика умещается в три метода:
- StartClimbing - Вычисление стартовых точек, отключение управления запуск карабканья, сохранение скорости персонажа до начала паркура
- Climbing - Корутина, выполняющая три этапа перемещения.
- StopClimbing - Возвращение управления персонажу, придача ему скорости до паркура.
За подробностями прошу в код:
Заключение
Наш персонаж научился определять, что есть препятствие, разбивать его на составляющие элементы и залезать на них. В данный момент я работаю над механикой бега по стенам. Благодарю за внимание, пишите свои пожелания и вопросы в комментариях. За развитием событий следите на DTF и на Youtube. Полная разработка, подробные объяснения и демонстрацию работы можно найти в данном видео:
Просто напомню, что у нас есть подсайт /unity (:
Вы правы. Я не обратил на него внимания. Но думаю, что данный материал будет интересен не только в рамках разработки на Unity. Логика будет работать и для других движков. Unity идеально подходит для быстрого создания POC-а.
На счёт подсайта по движкам. Когда я последний раз проверял подсайт UE, там небыли ни единой статьи об UE5, все публиковались в какие-то другие посайты. Возможно, подсайт Unity так же живёт на какой-то своей волне,)
Лучшим примером паркура в играх от первого лица это бесспорно Mirror’s Edge.Не соглашусь, Mirror’s Edge скорее был первой годной игрой про паркур, а вот на сегодняшний день первое место - это Dying Light.
Те, кто проходил там испытания ловкости на время не дадут соврать.
Если рассматривать его для выполнения игровых задач - да.
А по ощущениям все же Mirror’s Edge лучше.
Спасибо за материал! Но не забывайте ставить теги, пожалуйста
Сейчас попробую найти подходящий пример использования и добавлю.