슈팅게임 몬스터 총알 피격

주상돈·2025년 3월 17일

TIL

목록 보기
40/53
float AEPEnemyCharacter::TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	if (bIsDead)
	{
		return 0.f;
	}


	float ModifiedDamage = DamageAmount;

	if (DamageEvent.IsOfType(FPointDamageEvent::ClassID))
	{
		const FPointDamageEvent* PointDamageEvent = static_cast<const FPointDamageEvent*>(&DamageEvent);
		if (PointDamageEvent && PointDamageEvent->HitInfo.BoneName == HeadBoneName)
		{
			ModifiedDamage *= 4.0f;
			UE_LOG(LogTemp, Warning, TEXT("Headshot! Damage doubled to: %f"), ModifiedDamage);
		}
	}


	ModifiedDamage = Super::TakeDamage(ModifiedDamage, DamageEvent, EventInstigator, DamageCauser);
	
	Health -= ModifiedDamage;

	UE_LOG(LogTemp, Warning, TEXT("Enemy took damage: %f, Remaining Health: %f"), ModifiedDamage, Health);

	// 체력이 0 이하라면 사망 처리
	if (Health <= 0.f)
	{
		Health = 0.0f;
		bIsDead = true;

		if (DeathMontage)
		{
			UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
			if (AnimInstance)
			{
				float MontageDuration = AnimInstance->Montage_Play(DeathMontage, 1.0f);
				UE_LOG(LogTemp, Warning, TEXT("Playing DeathMontage, Duration: %f"), MontageDuration);
			}
		}
		// AnimInstance를 중지시키고 RagDoll 활성화
		GetMesh()->SetCollisionProfileName(TEXT("Ragdoll"));
		GetMesh()->SetSimulatePhysics(true);
		// 애니메이션 모드를 물리 시뮬레이션에 맞게 변경
		GetMesh()->SetAnimationMode(EAnimationMode::AnimationSingleNode);

		AEPAIController* AIController = Cast<AEPAIController>(GetController());
		if (AIController)
		{
			AIController->UnPossess();
		}
		GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
		SetActorTickEnabled(false);

		DropLoot();
	}

	return ModifiedDamage;
}

EnemyCharacter.cpp

우선 언리얼 엔진에서 기본적으로 액터가 데미지를 받게해주는 함수인 TakeDamage를 이용하여 구현해 봤다.

우선 기본적으로 데미지는 슈팅게임이기때문에 플레이어가 쏘는 총알에의해 Damage를 받게된다. 그 총알의 DamageAmount만큼의 데미지를 입는데 슈팅게임이다보니 헤드샷을 맞췄을 때, 추가적으로 데미지를 주게 구현했다. 일단 4배로 되어있는데 나중에 이 수치는 수정하면되니까…

그리고 아래는 총알 cpp 파일이다.

// Fill out your copyright notice in the Description page of Project Settings.


#include "EPBullet.h"
#include "Particles/ParticleSystem.h"
#include "Kismet/GameplayStatics.h"
// Sets default values
AEPBullet::AEPBullet()
{

	Senen = CreateDefaultSubobject<USceneComponent>(TEXT("Scene"));
	SetRootComponent(Senen);
	
	Range = 1000.0f;
	//MuzzleEffect = nullptr;
	HitEffect = nullptr;
	static ConstructorHelpers::FObjectFinder<UParticleSystem> MuzzleEffectObject(TEXT("ParticleSystem'/Game/Weapons/Realistic_Starter_VFX_Pack_Vol2/Particles/Hit/P_Leather.P_Leather'"));

	if (MuzzleEffectObject.Succeeded())
	{
		MuzzleEffect = MuzzleEffectObject.Object;
		UE_LOG(LogTemp, Warning, TEXT("MuzzleEffectObject.Succeeded"));
	}
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	LifeTime = 3.0f;

}

// Called when the game starts or when spawned
void AEPBullet::BeginPlay()
{
	Super::BeginPlay();

	FHitResult Hit;
	FVector StartTrace = GetActorLocation();
	FVector EndTrace = (GetActorRotation().Vector() * Range) + StartTrace;
	// 총알의 진행 방향: 시작점에서 끝점 방향으로 정규화된 벡터
	FVector ShotDirection = (EndTrace - StartTrace).GetSafeNormal();
	DrawDebugLine(GetWorld(), StartTrace, EndTrace,FColor::Red,true);
	UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), MuzzleEffect, StartTrace);
	if (GetWorld()->LineTraceSingleByChannel(Hit, StartTrace, EndTrace, ECC_GameTraceChannel1))
	{
		AActor* HitActor = Hit.GetActor();
		if (Hit.GetActor()->ActorHasTag(FName("Enemy")))
		{
			float DamageAmount = 10.0f;
			UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), HitEffect, Hit.ImpactPoint);
			// 데미지 적용
			UGameplayStatics::ApplyPointDamage(
				HitActor,      // 피해를 입는 액터
				DamageAmount,        // 데미지
				ShotDirection,       // 총알 방향
				Hit,           // 히트 정보 (HitInfo에 BoneName 포함)
				GetInstigatorController(),
				this,
				UDamageType::StaticClass()
			);
			
		}
		
	}
	GetWorldTimerManager().SetTimer(LifeTimerHandle, this, &AEPBullet::DestroyBullet, LifeTime, false);

}

// Called every frame
void AEPBullet::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);


}

void AEPBullet::DestroyBullet()
{
	Destroy();
}

Bullet.cpp

우선 다볼 필요없이 액터가 Hit하면 그 Hit한 액터가 Enemy의 이름(Tag)를 가진지 확인한다.

아래는 EnemyCharacter.cpp 생성자 부분이다.

AEPEnemyCharacter::AEPEnemyCharacter()
{
	PrimaryActorTick.bCanEverTick = true;

	Tags.Add(FName("Enemy"));

	bUseControllerRotationYaw = true;
	GetCharacterMovement()->bOrientRotationToMovement = false;

	MaxHealth = 200.0f;
	AttackDamage = 20.0f;
	Health = MaxHealth;
	patrolRadius = 1000.0f;
	CombatDuration = 5.0f;
	ChaseSpeed = 500.0f;
	PatrolSpeed = 150.0f;
	AttackRange = 100.0f;
	bIsInCombat = false;
	HeadBoneName = FName(TEXT("head"));
	bIsDead = false;
}

Tags.Add 를 사용하여 액터의 태그를 달아주고 아래에 HeadBoneName 이라는 FName 의 변수를 선언하여 헤드샷 즉, 머리를 맞추는 스켈레탈 본의 이름을 선언해준다. 구현하는 몬스터들의 스켈레탈 메시에서 얼굴에 해당하는 본의 이름이 다달라서 이렇게 구현하였다.


그 후 몬스터들의 콜리전에서 총알 Bullet 을 Block으로 세팅해준다.


이제 몹들이 총알을 맞는다.


그리고 피가 0이되면 죽는다.

0개의 댓글