1. 충돌 감지를 위한 박스 컴포넌트 생성
protected:
// 충돌 감지를 위한 박스 컴포넌트
UPROPERTY(Category = Collision, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
UBoxComponent* CollisionBox;
먼저, 박스 컴포넌트를 protected 변수로 선언을 해주었습니다.
// 박스 컴포넌트 생성
CollisionBox = CreateDefaultSubobject<UBoxComponent>(TEXT("CollisionBox"));
// 박스 컴포넌트를 메시의 소켓에 달아준다
CollisionBox->SetupAttachment(GetMesh(), TEXT("LION_-Spine_Socket"));
// 박스 컴포넌트의 프로파일을 몬스터의 프로파일로 설정한다
CollisionBox->SetCollisionProfileName(TEXT("Animal"));
// 일단 임의로 크기를 잡아준 것, 크기는 블루프린트로 수정할 것임
CollisionBox->SetBoxExtent(FVector(20.f, 20.f, 20.f));
박스 컴포넌트를 생성하고 메시에 부착한 뒤에 프로파일을 설정해 주었습니다.
박스 컴포넌트가 메시에 부착된 모습입니다.
2. 충돌시에 호출되는 함수 생성
// 충돌이 시작할시에 호출되는 델리게이트에 등록하는 함수
UFUNCTION()
void WeaponBeginOverlap(UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
bool bFromSweep, const FHitResult& SweepResult);
// 충돌이 종료될시에 호출되는 델리게이트에 등록하는 함수
UFUNCTION()
void WeaponEndOverlap(UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
언리얼에서 제공해주는 델리게이트를 이용하기 위해 함수를 선언해주었습니다.
등록시에 주의사항은 해당 델리게이트가 지원하는 함수와 인자를 똑같이 맞추어야 합니다.
인자가 다를시에는 컴파일 에러가 발생합니다.
// 델리게이트에 생성한 함수 등록
WeaponBox->OnComponentBeginOverlap.AddDynamic(this, &APlayerSurvivor::WeaponBeginOverlap);
WeaponBox->OnComponentEndOverlap.AddDynamic(this, &APlayerSurvivor::WeaponEndOverlap);
생성한 함수를 AddDynamic 함수를 사용해서 델리게이트에 등록해주었습니다.
이제 충돌이 시작하거나 종료될 때, 해당 함수들이 자동으로 호출됩니다.
void APlayerSurvivor::WeaponBeginOverlap(UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
bool bFromSweep, const FHitResult& SweepResult)
{
FString strText = FString::Printf(TEXT("BeginOverlap"));
PrintViewport(0.5f, FColor::Yellow, strText);
}
충돌시에 자동으로 호출이 잘 되는지 확인하기 위해 뷰포트에 텍스트를 띄워보도록 하겠습니다.
충돌시에 함수가 잘 호출되는 것을 확인할 수 있었습니다.
3. 데미지 함수와 몬스터 Hit 모션 적용
// Actor 클래스에 존재하는 언리얼이 제공해주는 데미지 함수
float AAnimal::TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent,
class AController* EventInstigator, AActor* DamageCauser)
{
// 몬스터 Hit 모션 실행
if (IsValid(AnimalAnim))
AnimalAnim->SetAnimType(EAnimalAnim::Hit);
float fDamge = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
return fDamge;
}
몬스터 클래스에 TakeDamage 함수를 정의해주었습니다. TakeDamage 함수는 Actor 클래스에 존재하는 언리얼이 제공해주는 기본 함수입니다. 이 함수를 사용하면 Actor를 상속받는 객체끼리 서로에게 데미지를 가할 수 있습니다.
그리고 데미지를 받으면 몬스터 객체가 Hit 모션을 취할 수 있도록 해주었습니다.
void APlayerSurvivor::WeaponBeginOverlap(UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex,
bool bFromSweep, const FHitResult& SweepResult)
{
// WeaponBeginOverlap이 계속 발동되어 설정해놓은 bool 변수
if (bBeginOverlapEnable)
{
// 이 변수를 false로 초기화해주고 Attack이 끝나면 노티파이를 이용해 다시 true로 바꾼다
bBeginOverlapEnable = false;
FDamageEvent DamageEvent;
// 여기서 OtherActor는 플레이어와 충돌한 객체, 즉 몬스터 객체이다
float fDamage = OtherActor->TakeDamage(10.f, DamageEvent, GetController(), this);
}
}
아까 정의해두었던 WeaponBeginOverlap 함수를 이용해 몬스터 객체에 데미지를 주었습니다.
WeaponBeginOverlap 함수가 공격을 한 번 휘두를 때마다 2, 3번씩 호출되어서 한 번만 처리해주도록 bool 변수를 이용하였습니다. bBeginOverlapEnable 변수는 공격이 끝날 때, 노티파이를 통해 다시 true로 바꿔주었습니다.
if 문에 들어오면 OtherActor(몬스터 객체)의 TakeDamage 함수를 호출해서 몬스터 객체에 데미지를 주었습니다.
TakeDamage 함수가 호출되어서 몬스터 객체가 Hit 모션을 실행하는 모습입니다.
4. 몬스터 HP 설정 및 Death 모션 적용
// Actor 클래스에 존재하는 언리얼이 제공해주는 데미지 함수
float AAnimal::TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent,
class AController* EventInstigator, AActor* DamageCauser)
{
// 몬스터 Hit 모션 실행
if (IsValid(AnimalAnim))
AnimalAnim->SetAnimType(EAnimalAnim::Hit);
float fDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
// 현재 체력에서 fDamage의 크기만큼 감소시킨다
fHP -= fDamage;
// 만약 HP가 0보다 작아진다면 Death 모션 적용
if (fHP <= 0.f)
{
if (IsValid(AnimalAnim))
AnimalAnim->SetAnimType(EAnimalAnim::Death);
}
return fDamage;
}
각 몬스터마다 HP를 다르게 세팅을 하고 데미지를 받을 때마다 HP를 감소시켜줬습니다.
자신의 HP가 0보다 작아진다면 Death 모션을 적용하였습니다.
Death 모션을 적용한 모습입니다.
void UAnimalAnim::AnimNotify_DeathEnd()
{
AAnimal* pOwner = Cast<AAnimal>(TryGetPawnOwner());
if (IsValid(pOwner))
pOwner->SetDestroyEnable(true);
}
void AAnimal::SetDestroyEnable(bool DestroyEnable)
{
bDestroyEnable = DestroyEnable;
}
그리고 나서 노티파이를 사용해서 Death 모션이 종료되면 SetDestroyEnable 함수를 호출하여 몬스터 객체의 bDestroyEnable 변수를 true로 바꿔주었습니다. (bDestroyEnable의 초기 값은 false입니다.)
void AAnimal::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (bDestroyEnable)
this->Destroy();
}
매초마다 들어오는 Tick 함수를 이용해 bDestroyEnable 변수가 true라면 Destroy 함수를 호출해서 객체를 월드에서 제거하였습니다.
AAnimal::~AAnimal()
{
if (SpawnPoint)
SpawnPoint->Respawn();
}
// 몬스터가 죽었을시에 다시 스폰해주는 함수
void ASpawnPoint::Respawn()
{
if (!IsValid(SpawnType))
return;
bSpawnEnable = true; // 스폰이 가능하도록 설정
SpawnTime = 1.f;
SpawnDuration = 0.f;
SpawnAnimal = nullptr;
}
몬스터 객체의 소멸자에서 Respawn 함수를 호출하여 몬스터 객체를 다시 생성할 수 있도록 하였습니다.
다음에 해야할 일
랜드 스케이프 제작
'게임 개발 (언리얼 엔진)' 카테고리의 다른 글
UE4 생존게임 제작 - 6 (PreyAnimal AI Controller 및 Data Table 제작) (0) | 2020.11.04 |
---|---|
UE4 생존게임 제작 - 5 (PredatorAnimal AI Controller 제작) (0) | 2020.11.03 |
UE4 생존게임 제작 - 4 (랜드스케이프 제작) (0) | 2020.11.01 |
UE4 생존게임 제작 - 2 (몬스터 제작) (0) | 2020.10.30 |
UE4 생존게임 제작 - 1 (캐릭터 제작) (1) | 2020.10.29 |