Цей урок має на меті показати, як додати бойову систему до вашої гри, використовуючи C++ в Unreal Engine 5. Кроки для цього наступні:
- Створення анімаційного монтажу
- Посилання на анімаційний монтаж
- Відтворення анімації з C++
- Додавання хит-боксів до анімації атаки
Створення анімаційного монтажу
Імпортуйте ваші анімації атак у проект, або ж імпортуючи FBX, або з торгового майданчика Fab. Для цього проекту я використовую анімації з “RamsterZ Free Anims Volume 1”.
Перед тим, як почати, одне зауваження для анімацій, на які я посилаюся вище: нам потрібно увімкнути рух кореня. Для цього виберіть анімації, які ви хочете додати до монтажу, і в панелі деталей активуйте рух кореня.
Це дозволяє персонажу рухатись завдяки анімаціям, інакше персонаж повернеться до початкової позиції, як тільки анімація завершиться.
Увімкнення руху кореня
Після імпорту анімацій, клацніть правою кнопкою миші на анімацію, яку ви хочете відтворити, і виберіть створення нового анімаційного монтажу в меню створення.
Створення анімаційного монтажу
Отримання посилання на анімаційний монтаж
Запустіть C++ редактор, перейшовши до Tools -> Open Xcode/Open Visual Studio Project.
Запуск редактора
Перейдіть до заголовного файлу класу персонажа та додайте наступну змінну під заголовком.
Параметри в 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 і вставивши це в подвійні лапки (“”).
Посилання на шлях до анімації
Відтворення анімації
Перш за все, нам потрібно додати нашу нову функцію до заголовного файлу:
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.
Створення нового класу C++ в Unreal
Виберіть усі класи та пошукайте animNotifyState, натисніть Next і назвіть ваш клас як хочете.
Створення класу 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. Вона знаходиться внизу праворуч, коли ви перебуваєте на рівні.
Створення сповіщення про стан
Hot Reload для вихідного коду
Додайте сповіщення про атаку на початку та в кінці кадру, де починаються та закінчуються атаки. Це дозволить нам отримувати виклики функцій у нашій функції tick, коли відбувається атака. У моєму випадку я додаю сповіщення про атаку з кадрів 20–25.
Ось тут персонаж виконує удар ногою.
Додавання сповіщення про атаку
Тепер додамо логіку для хитбоксів, які в нашому випадку будуть сферичними трасами, розташованими на кістці стопи персонажа.
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