Unreal Engine C++: Додавання анімацій бою

Цей урок має на меті показати, як додати бойову систему до вашої гри, використовуючи C++ в Unreal Engine 5. Кроки для цього наступні:

  1. Створення анімаційного монтажу
  2. Посилання на анімаційний монтаж
  3. Відтворення анімації з C++
  4. Додавання хит-боксів до анімації атаки

Створення анімаційного монтажу

Імпортуйте ваші анімації атак у проект, або ж імпортуючи FBX, або з торгового майданчика Fab. Для цього проекту я використовую анімації з “RamsterZ Free Anims Volume 1”.

Перед тим, як почати, одне зауваження для анімацій, на які я посилаюся вище: нам потрібно увімкнути рух кореня. Для цього виберіть анімації, які ви хочете додати до монтажу, і в панелі деталей активуйте рух кореня.

Це дозволяє персонажу рухатись завдяки анімаціям, інакше персонаж повернеться до початкової позиції, як тільки анімація завершиться.

pic

Увімкнення руху кореня

Після імпорту анімацій, клацніть правою кнопкою миші на анімацію, яку ви хочете відтворити, і виберіть створення нового анімаційного монтажу в меню створення.

pic

Створення анімаційного монтажу

Отримання посилання на анімаційний монтаж

Запустіть C++ редактор, перейшовши до Tools -> Open Xcode/Open Visual Studio Project.

pic

Запуск редактора

Перейдіть до заголовного файлу класу персонажа та додайте наступну змінну під заголовком.

Параметри в UPROPERTY, такі як EditAnywhere і BlueprintReadWrite, дозволяють змінювати цю змінну безпосередньо в редакторі Blueprint.

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Combat")  
UAnimMontage* AttackMontage;

У конструкторі класу (в моєму випадку це: AtestProjectCharacter::AtestProjectCharacter())

Отримуємо посилання на анімаційний монтаж наступним чином.

static ConstructorHelpers::FObjectFinder AttackMontageAsset(TEXT("/Script/Engine.AnimMontage'/Game/Characters/Mannequins/Animations/Manny/H2H_Kick02_Montage.H2H_Kick02_Montage'"));  

if (AttackMontageAsset.Succeeded())  
 {  
 AttackMontage = AttackMontageAsset.Object;  
 }

Шлях до файлу в макросі “TEXT” можна знайти, клацнувши правою кнопкою миші на файлі, вибравши Copy Reference і вставивши це в подвійні лапки (“”).

pic

Посилання на шлях до анімації

Відтворення анімації

Перш за все, нам потрібно додати нашу нову функцію до заголовного файлу:

UFUNCTION(BlueprintCallable, Category="Combat")  
void PlayAttackAnimation();

Далі додаємо цю функцію в CPP файл для початку атаки:

void AtestProjectCharacter::PlayAttackAnimation()  
{  
 if (AttackMontage && GetMesh() && GetMesh()->GetAnimInstance())  
 {  
 UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();  
 AnimInstance->Montage_Play(AttackMontage);  
 }  
}

Умова перевіряє наступне:

  • AttackMontage (анімаційний монтаж) є дійсним.
  • Персонаж має дійсну складову скелетної сітки (GetMesh() повертає вказівник, який не є нульовим).
  • Скелетна сітка має екземпляр анімації (GetAnimInstance() повертає вказівник, який не є нульовим).

Екземпляр анімації керує анімаціями, що відтворюються на скелетній сітці.

Додавання хіт-боксів

Останнім кроком перед тим, як ми отримаємо функціональний рух атаки, є додавання хіт-боксів, які дозволять нам контролювати зіткнення між нашою атакою та її оточенням.

Почніть з створення користувацького класу, що розширює клас анімаційних сповіщень (animation notify class) в Unreal Engine.

Зробіть це, перейшовши в Tools -> New C++ Class.

pic

Створення нового класу C++ в Unreal

Виберіть усі класи та пошукайте animNotifyState, натисніть Next і назвіть ваш клас як хочете.

pic

Створення класу Anim Notify State

У нашому класі AnimNotifyState ми переозначимо функцію NotifyTick, додавши наступний рядок у заголовок:

virtual void NotifyTick(USkeletalMeshComponent * MeshComp, UAnimSequenceBase * Animation, float FrameDeltaTime, const FAnimNotifyEventReference& EventReference) override;

Ця функція notify tick викликається на кожному тикові, поки анімація знаходиться в процесі сповіщення про стан анімації.

Далі ми додамо сповіщення про стан у нашу анімацію. Для цього клацніть правою кнопкою миші на треку сповіщення та виберіть клас, який ви створили.

Якщо клас не з'являється, спробуйте перебудувати ваш C++ або натиснути кнопку "Hot Reload" у Unreal Engine. Вона знаходиться внизу праворуч, коли ви перебуваєте на рівні.

pic

Створення сповіщення про стан

pic

Hot Reload для вихідного коду

Додайте сповіщення про атаку на початку та в кінці кадру, де починаються та закінчуються атаки. Це дозволить нам отримувати виклики функцій у нашій функції tick, коли відбувається атака. У моєму випадку я додаю сповіщення про атаку з кадрів 20–25.

Ось тут персонаж виконує удар ногою.

pic

Додавання сповіщення про атаку

Тепер додамо логіку для хитбоксів, які в нашому випадку будуть сферичними трасами, розташованими на кістці стопи персонажа.

void UAttackNotifications::NotifyTick(USkeletalMeshComponent * MeshComp, UAnimSequenceBase * Animation, float FrameDeltaTime, const FAnimNotifyEventReference& EventReference)  
{  
 Super::NotifyTick(MeshComp, Animation, FrameDeltaTime, EventReference);  

 if (MeshComp && MeshComp->GetOwner())  
 {  
 AActor* Owner = MeshComp->GetOwner();  
 UWorld* World = MeshComp->GetWorld();  
 FName BoneName = TEXT("foot_r"); // Замініть на бажану назву кістки  

 FVector BoneLocation = MeshComp->GetBoneLocation(BoneName, EBoneSpaces::WorldSpace);  

 // Логування місця для відлагодження  
 UE_LOG(LogTemp, Warning, TEXT("Bone '%s' location: %s"), *BoneName.ToString(), *BoneLocation.ToString());  
 // Створюємо масив для зберігання результатів зіткнень  
 TArray HitResults;  
 if (World == nullptr)  
 {  
 UE_LOG(LogTemp, Warning, TEXT("ERROR IN WORLD"));  
 }  
 DrawDebugSphere(World, BoneLocation, 50, 16, FColor::Blue, false, 0.0f, 0.0f, 2.0f);  
 bool bHit = World->SweepMultiByChannel(  
 HitResults,  
 BoneLocation,  
 BoneLocation,  
 FQuat::Identity,  
 ECC_Visibility, // Канал трасування, може бути змінений (наприклад, ECC_Pawn)  
 FCollisionShape::MakeSphere(50)  
 );  

 if (bHit)  
 {  
 for (FHitResult Hit : HitResults)  
 {  
 AActor* HitActor = Hit.GetActor();  
 if (HitActor)  
 {  
 UE_LOG(LogTemp, Warning, TEXT("Hit Actor %s"), *HitActor->GetActorLabel());  
 }  
 }  
 }  
 }  
}

Тут ми отримуємо розташування кістки (у моєму випадку правої стопи) і зберігаємо його для кожного тіку як центр нашої сферичної траси.

FVector BoneLocation = MeshComp->GetBoneLocation(BoneName, EBoneSpaces::WorldSpace);  
// Логування місця для відлагодження  
UE_LOG(LogTemp, Warning, TEXT("Bone '%s' location: %s"), *BoneName.ToString(), *BoneLocation.ToString());

Далі ми виконуємо сферичне трасування та малюємо сферу для відлагодження, щоб побачити, де траса влучить, використовуючи наступний код:

TArray HitResults;  
 if (World == nullptr)  
 {  
 UE_LOG(LogTemp, Warning, TEXT("ERROR IN WORLD"));  
 }  
 DrawDebugSphere(World, BoneLocation, 50, 16, FColor::Blue, false, 0.0f, 0.0f, 2.0f);  
 bool bHit = World->SweepMultiByChannel(  
 HitResults,  
 BoneLocation,  
 BoneLocation,  
 FQuat::Identity,  
 ECC_Visibility, // Канал трасування, може бути змінений (наприклад, ECC_Pawn)  
 FCollisionShape::MakeSphere(50)  
 );

Нарешті, для обробки зіткнень ми можемо перебрати всі вражені актори і зробити з ними все, що нам потрібно. У моєму випадку я просто виводжу їх мітки.

if (bHit)  
{  
 for (FHitResult Hit : HitResults)  
 {  
 AActor* HitActor = Hit.GetActor();  
 if (HitActor)  
 {  
 UE_LOG(LogTemp, Warning, TEXT("Hit Actor %s"), *HitActor->GetActorLabel());  
 }  
 }  
}

Щоб використовувати цю саму функцію сповіщення для кількох анімацій, використовуючи вказівник Animation, ви можете отримати ім’я анімації і використовувати його для отримання правильної кістки.

Я б порадив зробити це за допомогою enum (перелічення) та масиву, щоб уникнути випадкових помилок.

Перекладено з: Unreal Engine C++: Adding Combat Animations

Leave a Reply

Your email address will not be published. Required fields are marked *