※ 해당 기록은 Unreal 5.5.3 버전을 기준으로 작성되었습니다.
Boss의 각 패턴에 대해 ApplyDamage()를 사용해 Player에게 데미지를 주는 로직을 추가하고, Player로부터 피해를 입는 TakeDamage()를 추가했다. 프로젝트 기한이 다가오면서 당일에 글쓰는 것이 어렵다. 그래도 여유로울 때 하나씩 올리고자 한다.
돌진 패턴은 Player와 Boss가 직접 충돌하여, Overlap된다면 데미지를 주도록 구현했다. NotifyState를 사용해서 애니메이션 시작부터 NotifyBegin()을 잡아 BeginOverlap 이벤트를 추가하였다.

Collision Response가 기본으로 Block인 상태에서, 처음엔 그저 Notify를 공격 애니메이션의 극 초반부(대충 0.0014초)쯤에 배치해서 공격 애니메이션 중에 Overlap으로 전환하고 AttackEnd가 불리며 다시 Block으로 전환하고자 했다. 이런 방식으로 구현했을 때 문제가 발생했다. LaunchCharacter()를 사용하기 전처럼, Boss와 Player가 완전 가까이 있을 경우에 플레이어를 뚫고 지나가지 못하고 그 앞에서 애니메이션이 종료되었다. 예상컨데 애니메이션이 뚫고 나갔어야 하는 시간인데 Overlap으로의 전환이 비교적 늦어서 뚫고 지나가지 못한 것 같다.
//HitCheckNotifyState.h에서
...
public:
virtual void NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration) override;
virtual void NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override;
class AActor* Owner;
class AEnemyInfo* OwnCharacter;
class UCapsuleComponent* CapsuleComp;
bool bOverlaped = false;
UFUNCTION()
void OnPlayerOverlap(
UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex,
bool bFromSweep,
const FHitResult& SweepResult
);
...
NotifyTick()도 사용할 수 있지만, 이 Notify에선 필요하지 않아서 따로 추가하지 않았다.
//HitCheckNotifyState.cpp 에서
void UHitCheckNotifyState::NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration)
{
Super::NotifyBegin(MeshComp, Animation, TotalDuration);
Owner = MeshComp->GetOwner();
OwnCharacter = Cast<AEnemyInfo>(Owner);
if (OwnCharacter)
{
CapsuleComp = Cast<UCapsuleComponent>(OwnCharacter->GetCapsuleComponent());
if (CapsuleComp)
{
// 충돌 이벤트 등록
CapsuleComp->OnComponentBeginOverlap.AddDynamic(this, &UHitCheckNotifyState::OnPlayerOverlap);
}
}
}
void UHitCheckNotifyState::NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
if (CapsuleComp)
{
// 충돌 이벤트 삭제
CapsuleComp->OnComponentBeginOverlap.RemoveDynamic(this, &UHitCheckNotifyState::OnPlayerOverlap);
}
}
void UHitCheckNotifyState::OnPlayerOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
APlayerZagreus* player = Cast<APlayerZagreus>(OtherActor);
if (!!player)
{
UGameplayStatics::ApplyDamage(player, OwnCharacter->GetDamage(), OwnCharacter->GetController(), OwnCharacter, UDamageType::StaticClass());
UE_LOG(LogTemp, Warning, TEXT("%s hit %s! Applied %d Damage!"), *OwnCharacter->GetName(), *player->GetName(), OwnCharacter->GetDamage ());
OwnCharacter->GetCapsuleComponent()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECR_Ignore);
}
}
NotifyBegin에서 이벤트를 등록해서 호출할 수 있도록 했다. OnPlayerOverlap()에서 ApplyDamage()를 호출해서 플레이어에게 데미지를 주도록 했다.
ApplyDamage()의 원형은 다음과 같다.
float UGameplayStatics::ApplyDamage(
AActor* DamagedActor,
float BaseDamage,
AController* EventInstigator,
AActor* DamageCauser,
TSubclassOf<UDamageType> DamageTypeClass
);
이름 설명 DamagedActor데미지를 입는 피해자는 누구인지 BaseDamage데미지를 얼마큼 가할건지 EventInstigator데미지를 입힌 주체가 누구인지 (ex. PlayerController , AIController) DamageCauser데미지 원인은 무엇인지 (ex. 플레이어가 화염구를 던진다면 -> 3은 PlayerController, 4는 화염구) DamageTypeClass데미지 타입은 무엇인지
둘이 근본적으로 Boss가 소환한 액터가 플레이어에게 데미지를 주는 방식이 동일하다. 각 액터에서 충돌처리를 담당하는 함수를 만들고, BeginPlay()에서 이벤트를 등록하는 것까지 같다. 탄막 패턴에서 구현한 BeginOverlap 함수는 다음과 같다.
void ACurtainFireProjectile::OnProjectileOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
player = Cast<APlayerZagreus>(OtherActor);
if (player != nullptr && enemy != nullptr)
{
UGameplayStatics::ApplyDamage(player, enemy->GetDamage(), enemy->GetInstigatorController(), enemy, UDamageType::StaticClass());
UE_LOG(LogTemp, Warning, TEXT("Projectile hit %s! Applied %.2f Damage!"), *player->GetName(), enemy->GetDamage());
this->Destroy();
}
}
UE_LOG는 Output Log에 찍히므로 굳이 추가하지 않아도 작동한다. *player->GetName()은 기본으로 제공되는 함수지만, enemy->GetDamage()는 내가 만든 Getter함수이므로 안 나올 수 있다. Boss가 직접 몸을 부딪혀 피해를 입히는건 괜찮지만, 장판이나 탄막은 그 액터에서 스폰시킬 때, 주인이 누구인지, 누가 소환한 것인지 SpawnParam을 전달해줄 필요가 있다.
//void Boss::SpawnProjectile()에서,
...
FActorSpawnParameters spawnParams;
spawnParams.bNoFail = true;
spawnParams.Owner = this;
...
for (int32 i = -2; i < 3; i++)
{
...
ACurtainFireProjectile* projectile = GetWorld()->SpawnActor <ACurtainFireProjectile>(ProjectileFactory, ArrowComp->GetComponentTransform(), spawnParams);
...
}
이런 식으로 SpawnActor() 마지막에 spawnParams를 추가해 줄 경우 이 액터를 소환시킨 주체가 누구인지 전달시킬 수 있다. 그래서 Boss가 가진 데미지 정보를 액터에게 넘겨주어 액터가 그 정보를 사용할 수 있다.
피해를 주는 것이 있으면 피해를 받는 부분도 있어야 할 것이다. TakeDamage()함수를 사용하면 ApplyDamage()를 통해 호출된 데미지를 입을 수 있다.
// Boss.h에서
float TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;
// Boss.cpp에서
float ABoss::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
DamageToSelf(DamageAmount);
UE_LOG(LogTemp, Error, TEXT("Player Hit Boss"));
fsm->OnTakeDamage();
GM->SetBossHP(this->GetNowHp(), this->GetMaxHp());
return DamageAmount;
}
//EnemyInfo.cpp에서
void AEnemyInfo::DamageToSelf(float damageValue)
{
SetNowHp(FMath::Clamp(GetNowHp () - damageValue, 0.f, GetMaxHp ()));
}
헤더에서 함수를 오버라이드하고, Cpp에서 TakeDamage()가 호출되었을 때 실행시키고자 하는 기능을 그 안에 넣어주기만 하면 된다.
TakeDamage()의 원형은 다음과 같다.
virtual float TakeDamage
(
float DamageAmount,
struct FDamageEvent const & DamageEvent,
class AController * EventInstigator,
AActor * DamageCauser
)
이름 설명 DamageAmount데미지량은 얼마인가? DamageEventData package that fully describes the damage received. (적당히 번역하기 까다롭다...) EventInstigator데미지를 입힌 주체가 누구인지 (ex. PlayerController , AIController) DamageCauser데미지 원인은 무엇인지 (ex. 플레이어가 화염구를 던진다면 -> 3은 PlayerController, 4는 화염구)
데미지를 주고 받는 부분이 완성되었다. 이젠 Boss가 페이즈를 전환하고, 죽을 수 있도록 FSM을 완성해야한다. 몬스터도 만들어야한다.