Unreal Engine - Разработка ч.49 - Продвинутое управление (Enhanced Inputs) - Конвертируем проект ч.5
Привет! Так как я случайно вайпнул проект и всё что с ним связанное повторим всё что до этого усвоили и добавим немного перчинки реализовав свою первую игровую механику на C++
Для начала мы создадим класс наследуясь от класса Character. Этот класс отличается от Pawn и про его отличия подробно мы погворим чуть позже. У класса Character есть куча приемуществ перед Pawn, в нём уже есть удобные штуки по типу компонента перемещения, готовая репликация для онлайна и разные готовые функции которые мы можем забирать.
Так как дальше мы будем говорить в основном о анимации и роли её в логике я подготовил персонажа для дальнейшей работы с ним. Его я взял с просторов mixamo и конвертировал под UE. Это только базовый пероснаж, позже на нём мы потренируемся делать все теханиматорские фишки такие как создание аним пост процессов, создание динамических контроллеров а так же физической анимации одежды:
Так как дальше мы будем говорить в основном о анимации и роли её в логике я подготовил персонажа для дальнейшей работы с ним. Его я взял с просторов mixamo и конвертировал под UE. Это только базовый пероснаж, позже на нём мы потренируемся делать все теханиматорские фишки такие как создание аним пост процессов, создание динамических контроллеров а так же физической анимации одежды:
rar
Vampire.rar12.21 Mb
Внутри архива вы найдете все сорсы из MAYA которые нужны для работы с анимацией. Все сцены подготовлены к чистке мокапа навешенного на них - только садись и делай. Со временем когда я буду двигаться по темам я буду заливать финальные версии анимации для этого персонажа вместе с сорсами для вашего изучения.
Ранее мы уже создавали движение персонажа вперед и назад, поэтому мы
легко сможем создать нужные нам для дальнейшей работы IMC и IA, а так
же позже связать их с функциями.
легко сможем создать нужные нам для дальнейшей работы IMC и IA, а так
же позже связать их с функциями.
Тем не менее я хочу оставновиться конкретно на этом персонаже. Сегодня мы заного сделаем функцию Look и Move и сделаем так чтобы наш персонаж не "летал" вокруг как в предыдущем примере.
Для начала я создам указатели на IA и IMC которые мы позже добавим в BP который, кстати тоже не плохо было бы создать позже. Для начала я сделаю предварительное объявление всех необходимых мне классов в своём .h файле:
Из за того что мы наследуемся от ACharacter список будет очень маленьким. Капсль коллизии, компонент движения а так же компонент скелетал меша уже есть в базовом ACharacter, поэтому мы добавим в него камеру, спрингарм и всё что нужно для работы c EIMS а именно класс ELPS, IMC и IA, далее в private секции я обьявлю все нужные переменные и включих их в систему reflection UE используя UPROPERTY макрос:
Так же я заранее обьяввил все функции которые мне понадобятся. Обратите внимание на то что функции Jump нет, её мы унаследуем от ACharacter. Сама по себе функция представляет из себя канонически верный способ делать любые подобные фишки в UE, если вам интересно как она работает вы можете залезть .h и .cpp файлы Character, там есть много чего инетресного, но для простоты мы опустим этот момент.
Далее я в .cpp файле в разделе BeginPlay объявлю вызов EIS и присвою ей приоритет:
После я свяжу все IMC с функциями в SetupPlayerInputComponent:
Обратите внимание на первые две связки. В неё я с помощью наследования от ACharacter получаю доступ Jump функции потому что мой AVampire is a ACharacter. Связал я эту функцию с нешим IA и нашим же IMC. Такая практика достаточно популярна. Вы можете наслдеоваться от базового класса, создать варацию своего базового класса и потом от неё наследовать вариации вызывая функции внутри каждого отдельно инстанса, так вы легко добьетесь чистоты кода.
Далее я перейду в конструктор класса чтобы создать все нужные мне компоненты а так задать нужные параметры. Для начала я запрещу нашему персонажу использовать вращениие контроллера:
Далее я перейду в конструктор класса чтобы создать все нужные мне компоненты а так задать нужные параметры. Для начала я запрещу нашему персонажу использовать вращениие контроллера:
Далее я заранее расставлю параметры для CharacterMovementComponent который мы наследуем от ACharacter:
Запомните такой тип записи! Его в UE мы будем использовать постоянно. Сначала я вызвал функцию GetCharacterMovment() и через . оператор получил доступ к нужной мне переменной. Так как я хочу сделать так чтобы мой персонаж не вертелся во все стороны а поворачивался по чуть чуть мне нужно задать ему параеметры.
Первым я вызываю bOrientRotationToMovement если мы поставим его значение в true то наш персонаж будет поворачиваться в торону вектора ускорения. Далее я задаю значение с какой скоростью наш персонаж будет поворачиваться в сторону вектора ускорения меняя переменную RotationRate.
Первым я вызываю bOrientRotationToMovement если мы поставим его значение в true то наш персонаж будет поворачиваться в торону вектора ускорения. Далее я задаю значение с какой скоростью наш персонаж будет поворачиваться в сторону вектора ускорения меняя переменную RotationRate.
По такой же аналогии я настраиваю остальные интересующие меня параметры:
JumpZVelocity - отвечает за то с каким значением мы будем ускоряться при прыжке
AirControl - отвечает за то насколько сильно мы можем управляеть персонажем во время падения где 0 = мы не можем управлять персонажем, а 1 = у нас полный контроль на полной скорости от переменной MaxWalkSpeed
MaxWalkSpeed - задает максимальную скорость перемещения персонажа по земле а так же участвует в поведении AirControl
MinAnalogWalkSpeed - задает минимальное значение которое будет активировать перемещение персонажа с аналогового контроллера (Чопа привет!)
BrakingDecelerationWalking - задает константное значение которое будет использовано для того чтобы "душить" преедвижение персонажа когда инпут поступать перестанет, другими словами отвечает за то как быстро отсановится персонаж после отпускания клавиш ввода
BrakingDecelerationFalling - это параметр, который определяет скорость замедления (декларации) персонажа в воздухе при использовании торможения. Параметр указывает, как быстро персонаж замедляется, когда он перестает двигаться в горизонтальном направлении в воздухе.
Далее я создаю компоненты и задаю им нужную иерархию:
AirControl - отвечает за то насколько сильно мы можем управляеть персонажем во время падения где 0 = мы не можем управлять персонажем, а 1 = у нас полный контроль на полной скорости от переменной MaxWalkSpeed
MaxWalkSpeed - задает максимальную скорость перемещения персонажа по земле а так же участвует в поведении AirControl
MinAnalogWalkSpeed - задает минимальное значение которое будет активировать перемещение персонажа с аналогового контроллера (Чопа привет!)
BrakingDecelerationWalking - задает константное значение которое будет использовано для того чтобы "душить" преедвижение персонажа когда инпут поступать перестанет, другими словами отвечает за то как быстро отсановится персонаж после отпускания клавиш ввода
BrakingDecelerationFalling - это параметр, который определяет скорость замедления (декларации) персонажа в воздухе при использовании торможения. Параметр указывает, как быстро персонаж замедляется, когда он перестает двигаться в горизонтальном направлении в воздухе.
Далее я создаю компоненты и задаю им нужную иерархию:
Обратите внимание что я запретил камере брать вращение от контроллера а компоненту камеры Spring - разрешил. В конце выравниваю наш компонент Skeletal Mesh по высоте нашей капсюли коллизии:
Делаю я это програмно, так чтобы как бы я позже не менял значенее капсюля в BP или из под С++ наш персонаж всегда будет ориентирован по нижней границе капсюля.
После чего мы можем приступить к имплементации наших игровых функций. Начнём с Look:
После чего мы можем приступить к имплементации наших игровых функций. Начнём с Look:
Её мы уже делали. У нас есть 2д вектор и мы передаем его значения в EIS.
Далее сделаем Move. С ним всё интересней:
Во первых мы начинаем с того что обьявляем переменную которая будет значенеи в вектор из нашего инпута. Далее я делаю проверку на наличие контроллера, а он будет так как мы наследуем эту переменную от Pawn, как альтернатива можно писать GetPlayerController() так как эпики прямой доступ всё чаще закрывают созадавая специальные гетеры чтобы народ точно ничего не ломал. После чего в теле я обьявляю две переменные тип FRotator, про этот тип мы поговорим в следуйщей теме чуть подробней, пока что просто следуйте за текстом, и задаю ей значние равное вращению контроллера. На самом дееле вот такая запись даже правильней будет по версии стандартов UE:
Далее я я создаю ещё один константный ротатор YawRotation и задаю значение его вектора вручную по XYZ. Y вектору я присвоил значение переменной Rotation, таким образом я буду подавать значение из контроллера на Y вектор и наш персонаж поворачиваться используя вращение а не телепортироваться. Далее я получаю передний вектор через тип данных FRotationMatrix о которых я тоже поговорю в следующей раз. Если коротко то она представляет собой матрицу вращения, которая позволяет вам преобразовывать векторы из одной системы координат в другую, применяя заданное вращение. Это конструктор который поможет нам преобразовать один тип данных в другой. Подробней поговоримв в следующий раз, пока что просто запишите как оно есть. Точно таким же способом мы получаем доступ к нашему правому вектору и после чего добавляем движение в наш компонент движения с помощью фукнци AddMovementInput. Давайте разберем эту функцию чуть подробней:
Её способ записи, т.е синтаксис, выглядит вот так, мы подаем в неё вектор направления а так же значение на которое мы будем его умножать, этоо значение по умолчанию — 1.0, но его можно изменять для настройки скорости движения. Булевый флаг указывает, нужно ли принудительно применять движение, игнорируя некоторые проверки. По умолчанию значение false. В нашем случае мы подаем значение ForwardDirection и умножаем его на значение MovementVector.Y, а так же RightDirection умножаем на MovementVector.X
Этот код управляет движением персонажа, используя текущий угол поворота контроллера. Сначала определяются направления для движения вперёд и вправо относительно этого угла. Затем персонаж перемещается в этих направлениях в зависимости от значений, которые задаёт вектор движения.
Jump на себя целиком берет ACharacter, мы нам останется подключить IMC и IA к нашему будущему блюпринту. Остался только функция Dash. Я её взял специально как отправную точку чтобы подвести итог изученых материалов и разобрать одну очень важную функцию в UE которую вы будете использовать постоянно.
Дэш по сути своей представляет из себя простой телепорт персонажа в напарвлении вектора движения. Способов его реализации множество. Во всех туторах вы встретите следующеие:
- Через Launch персонажа используя валосити или последний нормализированный вектор
- Через SetActorLocation то есть банальный телепорт
Этот код управляет движением персонажа, используя текущий угол поворота контроллера. Сначала определяются направления для движения вперёд и вправо относительно этого угла. Затем персонаж перемещается в этих направлениях в зависимости от значений, которые задаёт вектор движения.
Jump на себя целиком берет ACharacter, мы нам останется подключить IMC и IA к нашему будущему блюпринту. Остался только функция Dash. Я её взял специально как отправную точку чтобы подвести итог изученых материалов и разобрать одну очень важную функцию в UE которую вы будете использовать постоянно.
Дэш по сути своей представляет из себя простой телепорт персонажа в напарвлении вектора движения. Способов его реализации множество. Во всех туторах вы встретите следующеие:
- Через Launch персонажа используя валосити или последний нормализированный вектор
- Через SetActorLocation то есть банальный телепорт
- Через AnimationMontage
- Через Motion Warping
Все эти способы имеют место быть но не стабильны. Вот в этом туторе подробно рассказано что к чему в этих методах.
- Интересный способ задавания в ручную Валосити очень актуальный и стабильный. Плюсом то что он универсальный и позволяет делать основные механики передвижения (перекаты, подкаты и дэшы) на этой концепции. Удобно клёво но не все ещё не дает нужного уровня контроля над движением.
- Быстрая, чистая и удобная имплементация этой механики, и схожих ей лежит в GameplayAbilitySystem (GAS) плагине от Эпиков. И завернута она в функцию Apply Root Motion Constant Force with Callbacks:
- Через Motion Warping
Все эти способы имеют место быть но не стабильны. Вот в этом туторе подробно рассказано что к чему в этих методах.
- Интересный способ задавания в ручную Валосити очень актуальный и стабильный. Плюсом то что он универсальный и позволяет делать основные механики передвижения (перекаты, подкаты и дэшы) на этой концепции. Удобно клёво но не все ещё не дает нужного уровня контроля над движением.
- Быстрая, чистая и удобная имплементация этой механики, и схожих ей лежит в GameplayAbilitySystem (GAS) плагине от Эпиков. И завернута она в функцию Apply Root Motion Constant Force with Callbacks:
Вот так она выглядит из под BP. Обо всех её приемуществах поведает всё тот же автор что и нашел предыдущее решение, у него же на патреоне за 12$ можно забрать выдранную ноду в свой проект, не устанавливать же целый GAS ради одной ноды.
Тем не менее в нашем случае я буду использовать простой путь. Я буду использовать SetActorLocation через Lerp в тике:
Для этого мне нужно будет подготовить ряд перменных, в .h файле:
Тем не менее в нашем случае я буду использовать простой путь. Я буду использовать SetActorLocation через Lerp в тике:
Для этого мне нужно будет подготовить ряд перменных, в .h файле:
FTimerHandle - это структура в Unreal Engine, которая используется для управления таймерами в игровом коде. Она позволяет отслеживать и управлять состоянием таймеров, созданных с помощью менеджера таймеров. DashDuration и DashStartTime переменные отвечают за активацию и деактивацю таймера а DashStartPosition и DashEndPosition за векторы начала и конца движения, буливая переменная bIsDashing флаг таймера который мы будем переключать. Решение топорное, но рабочее. Каждая переменная помечана для reflection и при жеалнии её можно вывести в BP для дальнейшей конфигурации. Так же мне понадобиться сделать функцию для завершения деша, я обьявлю её тут в .h файле:
А определю её в .cpp:
Далее я имплементирую Dash:
Для начала мы делаем проверку переменная bIsDashing установлено заранее в false, чтобы условие if (bIsDashing) в коде не блокировало выполнение рывка. Когда рывок начинается, bIsDashing устанавливается в true, чтобы предотвратить повторный запуск рывка до завершения текущего. Далее мы задаем переменные DashStartPosition и DashEndPosition а так же получаем передний вектор. Конечную точку мы назначли равную начальная позиция + передний вектор * 1500.f то есть я хочу сдвинуть нашего персонажа на 1500 юнититов относительно его текущей позиции.
Далее я задаю время получая его из мира и как раз таки переключаю наш булиан в true чтобы позже запустить таймера для завершения рывка. Теперь когда тело готово, надо сделать голову. Её мы будем делать в тике так как я буду применять Lerp:
Далее я задаю время получая его из мира и как раз таки переключаю наш булиан в true чтобы позже запустить таймера для завершения рывка. Теперь когда тело готово, надо сделать голову. Её мы будем делать в тике так как я буду применять Lerp:
Выглядит это так. Лерп делается с помощью ввода позиции точки А и конечной точки B через альфу, альфа означет если 0 то мы находимся в точке А а если 1 то в B всё что между линейная интерполяция или же лерп по простому. Хоть мы и будем тикать каждый кадр и учел тот факт что FPS может проседать создав переменную ElapsedTime которая зависит от реального времени, прошедшего с начала рывка, а не от
количества кадров. Это обеспечивает, что рывок будет длиться
определенное время независимо от изменений частоты кадров.
количества кадров. Это обеспечивает, что рывок будет длиться
определенное время независимо от изменений частоты кадров.
Таким образом дистанция рывка будет оставаться постоянной, даже если фреймрейт изменяется.
Подробно и про Лерп и про остальные функции мы поговорим сильно позже, но его реализация имеет место быть. У неё есть ограничения. После того как мы начали деш - закончить его гиморойно, точно так же нельзя по простому взять и передать полученное валосити к скорости движения и плавно её заглушить, поэтому после быстрого деша вы не увидите плавного замедления а замедление сразу будет установленно в значение MaxWalkSpeed или в значение того состояния в котором будет персонаж по вашей же логике. Я лично считаю деш через motionwarp удобней как и для геим дизайнера так и для программиста, а страшная Apply Root Motion Constant Force with Callbacks вообще покрывает все возможные сценарии. Моя же цель была показать имплементацию какой то механики и показать обширность того о чём нужно думать дизайня систему, а так же то что наследование уже готовой работы будет вполне естественным процессом при разработке любого игрового контроллера. Уж больно много нужно думать о разных не очевидных вещах.
Чуть не забыл. У рутмоушена всё впорядке с репликацией как и у лерпа, поэтому выбирайте что то что вам проще поддерживать, я хз вообще кто эти слухи распускает но ему нужно настучать по голове.
Ладно с этим всем разобрались давайте создадим BP всё таки и посмотрим результат работы в движке:
Подробно и про Лерп и про остальные функции мы поговорим сильно позже, но его реализация имеет место быть. У неё есть ограничения. После того как мы начали деш - закончить его гиморойно, точно так же нельзя по простому взять и передать полученное валосити к скорости движения и плавно её заглушить, поэтому после быстрого деша вы не увидите плавного замедления а замедление сразу будет установленно в значение MaxWalkSpeed или в значение того состояния в котором будет персонаж по вашей же логике. Я лично считаю деш через motionwarp удобней как и для геим дизайнера так и для программиста, а страшная Apply Root Motion Constant Force with Callbacks вообще покрывает все возможные сценарии. Моя же цель была показать имплементацию какой то механики и показать обширность того о чём нужно думать дизайня систему, а так же то что наследование уже готовой работы будет вполне естественным процессом при разработке любого игрового контроллера. Уж больно много нужно думать о разных не очевидных вещах.
Чуть не забыл. У рутмоушена всё впорядке с репликацией как и у лерпа, поэтому выбирайте что то что вам проще поддерживать, я хз вообще кто эти слухи распускает но ему нужно настучать по голове.
Ладно с этим всем разобрались давайте создадим BP всё таки и посмотрим результат работы в движке:
Камера крутится, персонаж поворачивается, деш работает. Деш влияет в том числе на Z координату поэтому наш персонаж "зависнит" в воздухе, чтобы реализовать иное повоедение через lerp придется сильно и долго покумекать. Возможным решением будет забор какого то значения из Character.h и дальнейший вычет его во время лерпа, но всё это не удобно и не оптимально. Метод через валосити и монтажку проще, удобней и быстрее.
Выбирайте что вам лучше подходит. В следующий раз будет очень много математики. Мужайтесь.
#ue5