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이되면 죽는다.