Actor Component로 체력 분리
16강. Actor Component로 체력 분리
1. 이번 강의에서 해결할 문제
플레이어와 적 클래스에 체력 감소, Clamp, 사망 판정을 각각 복사했습니다. 피해 수신과 체력 이벤트를 독립 Component로 완성해 두 Actor에서 재사용합니다.
2. 학습 목표
3. 핵심 개념
Actor Component는 World 위치 없이 Actor에 붙는 재사용 기능에 적합합니다. HealthComponent는 피해 원인이 플레이어인지 적인지 몰라도 ApplyDamage 경로를 통해 Damage 값만 받습니다. 사망 결과는 Delegate로 외부에 알립니다.
모든 기능을 Character에 몰아넣으면 적, 파괴 가능한 상자, 보스가 체력 코드를 복사하게 됩니다. Component는 “체력이 있는 대상”이라는 기능 축으로 재사용 경계를 만듭니다.
4. 단계별 실습
파일 경로: Source/TopDownSurvival/Components/SurvivalHealthComponent.h · 완성
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "SurvivalHealthComponent.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(
FOnHealthChanged, float, CurrentHealth, float, MaxHealth);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeath);
UCLASS(ClassGroup = "Survival", meta = (BlueprintSpawnableComponent))
class TOPDOWNSURVIVAL_API USurvivalHealthComponent : public UActorComponent
{
GENERATED_BODY()
public:
USurvivalHealthComponent();
UFUNCTION(BlueprintCallable, Category = "Health")
void ConfigureHealth(float NewMaxHealth);
UFUNCTION(BlueprintPure, Category = "Health")
float GetCurrentHealth() const { return CurrentHealth; }
UFUNCTION(BlueprintPure, Category = "Health")
float GetMaxHealth() const { return MaxHealth; }
UFUNCTION(BlueprintPure, Category = "Health")
bool IsDead() const { return CurrentHealth <= 0.0f; }
UPROPERTY(BlueprintAssignable, Category = "Health")
FOnHealthChanged OnHealthChanged;
UPROPERTY(BlueprintAssignable, Category = "Health")
FOnDeath OnDeath;
protected:
virtual void BeginPlay() override;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Health",
meta = (ClampMin = "1.0"))
float MaxHealth = 100.0f;
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "Health")
float CurrentHealth = 100.0f;
private:
UFUNCTION()
void HandleOwnerDamaged(
AActor* DamagedActor,
float Damage,
const UDamageType* DamageType,
AController* InstigatedBy,
AActor* DamageCauser);
void SetCurrentHealth(float NewHealth);
};
파일 경로: Source/TopDownSurvival/Components/SurvivalHealthComponent.cpp
#include "Components/SurvivalHealthComponent.h"
#include "GameFramework/Actor.h"
USurvivalHealthComponent::USurvivalHealthComponent()
{
PrimaryComponentTick.bCanEverTick = false;
}
void USurvivalHealthComponent::BeginPlay()
{
Super::BeginPlay();
CurrentHealth = MaxHealth;
if (AActor* Owner = GetOwner())
{
Owner->OnTakeAnyDamage.AddDynamic(
this, &USurvivalHealthComponent::HandleOwnerDamaged);
}
OnHealthChanged.Broadcast(CurrentHealth, MaxHealth);
}
void USurvivalHealthComponent::ConfigureHealth(float NewMaxHealth)
{
MaxHealth = FMath::Max(1.0f, NewMaxHealth);
SetCurrentHealth(MaxHealth);
}
void USurvivalHealthComponent::HandleOwnerDamaged(
AActor* DamagedActor,
float Damage,
const UDamageType* DamageType,
AController* InstigatedBy,
AActor* DamageCauser)
{
if (Damage > 0.0f && !IsDead())
{
SetCurrentHealth(CurrentHealth - Damage);
}
}
void USurvivalHealthComponent::SetCurrentHealth(float NewHealth)
{
const float Previous = CurrentHealth;
CurrentHealth = FMath::Clamp(NewHealth, 0.0f, MaxHealth);
if (!FMath::IsNearlyEqual(Previous, CurrentHealth))
{
OnHealthChanged.Broadcast(CurrentHealth, MaxHealth);
}
if (Previous > 0.0f && IsDead())
{
OnDeath.Broadcast();
}
}
Character와 EnemyBase 생성자에 추가
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
TObjectPtr<USurvivalHealthComponent> HealthComponent;
HealthComponent = CreateDefaultSubobject<USurvivalHealthComponent>(TEXT("HealthComponent"));
5. 코드가 동작하는 이유
ApplyDamage는 대상 Actor의 OnTakeAnyDamage를 발생시키고 HealthComponent가 Owner 이벤트를 받아 체력을 변경합니다. Actor 클래스는 피해 계산을 복사하지 않습니다. CurrentHealth가 양수에서 0으로 전이할 때만 OnDeath를 Broadcast해 사망 처리가 한 번만 실행됩니다.
6. 자주 하는 실수와 해결법
7. 직접 실습
8. 이해 점검 질문 3개
9. 핵심 요약
Actor Component로 체력 분리 미니 퀴즈
선택 즉시 정답과 해설을 확인할 수 있습니다. 결과는 이 브라우저에만 저장됩니다.
학습을 마쳤나요?
직접 실습과 점검 질문까지 확인한 뒤 완료로 표시하세요.