Die 몽타주의 섹션이 재생되지만 바로 Idle로 돌아오기 때문에 계속 누워있을수 있도록 수정해주어야 한다.
이를 위해 각 애니메이션에서 제일 마지막부분을 따로 애니메이션으로 만들기 위해 마지막 부분을 선택한 뒤 에셋생성/애니메이션 생성/현재포즈를 통해 만들어주자.
그 다음 애니메이 블루플린트에서 새로운 State Machine을 만들어주자.
이 Main States에는 Idle과 Dead가 있고 내부 에는 각각의 애니메이션이 재생되도록 한다.
이때 Idle과 Dead의 전환 조건도 만들어줘야하고 어떤 Dead애니메이션이 재생될지도 정해줘야한다. 이를 코드로 구현배자 Death포즈 여러개와 Alive 포즈를 정의해줄 enum 클래스를 선언해주자.
UENUM(BlueprintType)
enum class EDeathPose :uint8
{
EDP_Alive UMETA(DisplayName = "Alive"),
EDP_Death1 UMETA(DisplayName = "Death1"),
EDP_Death2 UMETA(DisplayName = "Death2"),
EDP_Death3 UMETA(DisplayName = "Death3"),
EDP_Death4 UMETA(DisplayName = "Death4"),
EDP_Death5 UMETA(DisplayName = "Death5"),
EDP_Death6 UMETA(DisplayName = "Death6")
};
이렇게 정의해준 enum 클래스를 Enemy 클래스에서 사용하도록 하자. 이때 애니메이션 블루프린트에서 사용할 수 있게 UPROPERTY값을 BluePrintReadOnly로 설정해주었다.
Enemy.h
protected:
UPROPERTY(BlueprintReadOnly)
EDeathPose DeathPose = EDeathPose::EDP_Alive;
그리고 Die함수 내에서 각 Death 애니메이션 섹션에 맞게 enum으로 선언된 변수도 바뀌도록 static_cast를 통해 처리해주었다.
Enemy.cpp
void AEnemy::Die()
{
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && DeathMontage)
{
AnimInstance->Montage_Play(DeathMontage);
const int32 Selection = FMath::RandRange(1, 6);
FString SectionString = FString::Printf(TEXT("Death%d"), Selection); // "Death1", "Death2" 등의 문자열 생성
DeathPose = static_cast<EDeathPose>(RandomIndex);
FName SectionName = FName(*SectionString); // FString을 FName으로 변환
AnimInstance->Montage_JumpToSection(SectionName, DeathMontage);
}
}
이렇게 해주고 이 Enum 변수값을 가져오기 위해 애니메이션이 Initialize 될때 Enemy값을 변수로 가져올 수 있도록 해주자.
이때 DeathPose의 값을 가져오는 것을 멀티스레딩를 활용하여 가져오자. 이것은 Tread Safe Update Animation을 활용하여 만들 수 있는데 이때 멀티 스레딩에서 값에 안전하게 접근하도록 Property Access를 활용한다.
이렇게 해주면 이제 Death Pose가 Alive가 아니라면 Death State로 넘어가면 되게 해주면 된다.
이렇게 해준 다음 Dead에서는 DeadPose 변수값을 가져와서 Blend Pose를 통해 각 애니메이션에 맞는 Dead 애니메이션을 마지막에 출력해주도록 하자
이렇게 해주고 블랜드 타임을 0으로 해주면 잘 작동하는 것을 볼 수 있다.
이제 Enemy가 죽었을 때 더 이상 충돌하지 않게 해주고 몇초 뒤에 Destroy 되도록 하자.
Enemy.cpp
void AEnemy::Die()
{
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && DeathMontage)
{
AnimInstance->Montage_Play(DeathMontage);
const int32 Selection = FMath::RandRange(1, 6);
FString SectionString = FString::Printf(TEXT("Death%d"), Selection); // "Death1", "Death2" 등의 문자열 생성
DeathPose = static_cast<EDeathPose>(Selection);
FName SectionName = FName(*SectionString); // FString을 FName으로 변환
AnimInstance->Montage_JumpToSection(SectionName, DeathMontage);
}
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
SetLifeSpan(3.f);
}
이렇게 해주고 HP바가 공격했을 때 표시되고 멀리 갔을 때는 다시 없어지게 구현해보자. 멀리 갔을 때 없어지는 것을 구현하기 위해 Enemy에서 현재 공격하고 있는 Actor를 저장하고 이 Actor와의 거리에 따라 없어지게 해주면 된다.
Enemy.h
UPROPERTY()
AActor* CombatTarget;
UPROPERTY()
double CombatRadius = 500.f;
Enemy.cpp
void AEnemy::BeginPlay()
{
Super::BeginPlay();
if (HealthBarWidget)
{
HealthBarWidget->SetVisibility(false);
}
}
void AEnemy::Die()
{
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && DeathMontage)
{
AnimInstance->Montage_Play(DeathMontage);
const int32 Selection = FMath::RandRange(1, 6);
FString SectionString = FString::Printf(TEXT("Death%d"), Selection); // "Death1", "Death2" 등의 문자열 생성
DeathPose = static_cast<EDeathPose>(Selection);
FName SectionName = FName(*SectionString); // FString을 FName으로 변환
AnimInstance->Montage_JumpToSection(SectionName, DeathMontage);
}
if (HealthBarWidget)
{
HealthBarWidget->SetVisibility(false);
}
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
SetLifeSpan(3.f);
}
void AEnemy::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (CombatTarget)
{
const double DistanceToTarget = (CombatTarget->GetActorLocation() - GetActorLocation()).Size();
if (DistanceToTarget > CombatRadius)
{
CombatTarget = nullptr;
if (HealthBarWidget)
{
HealthBarWidget->SetVisibility(false);
Attributes->SetHealth(100.f);
}
}
}
}
void AEnemy::GetHit_Implementation(const FVector& ImpactPoint)
{
if (HealthBarWidget)
{
HealthBarWidget->SetVisibility(true);
}
if (Attributes && Attributes->IsAlive())
{
DirectionalHitReact(ImpactPoint);
}
else
{
Die();
}
if (HitSound)
{
UGameplayStatics::PlaySoundAtLocation(
this,
HitSound,
ImpactPoint
);
}
if (HitParticles&& GetWorld())
{
UGameplayStatics::SpawnEmitterAtLocation(
GetWorld(),
HitParticles,
ImpactPoint
);
}
}
float AEnemy::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
if (Attributes)
{
Attributes->ReceiveDamage(DamageAmount);
HealthBarWidget->SetHealthPercent(Attributes->GetHealthPercent());
}
CombatTarget = EventInstigator->GetPawn();
return DamageAmount;
}
이렇게 하면 잘 작동하는 것을 볼 수 있다.
'게임공부 > Unreal Engine' 카테고리의 다른 글
[Unreal Engine][C++]21. Damage (1) | 2025.01.21 |
---|---|
[Unreal Engine][C++]20. Actor Component (0) | 2025.01.16 |
[Unreal Engine][C++]19. Actor Component (0) | 2025.01.14 |
[Unreal Engine][C++]18. Treasure2 (0) | 2025.01.03 |
[Unreal Engine][C++]17. Treasure (0) | 2025.01.03 |