액터에 컴포넌트 추가하기

액터에 컴포넌트를 추가하는 방법을 다룬 튜토리얼입니다.

Choose your operating system:

Windows

macOS

Linux

컴포넌트액터 에서 서브오브젝트로 사용할 수 있도록 설계된 오브젝트 유형입니다.

컴포넌트는 비주얼 이펙트 연출, 사운드 재생 또는 행동 로직 처리 같은 일반적인 행동을 다양한 클래스에서 공유하는 데 유용합니다.

컴포넌트를 직접 만들 때 사용 가능한 컴포넌트는 총 세 가지로, 액터 컴포넌트(Actor Components), 씬 컴포넌트(Scene Components), 그리고 프리미티브 컴포넌트(Primitive Components) 입니다.

  • 액터 컴포넌트 (UActorComponent 클래스)는 이동, 인벤토리 또는 어트리뷰트 관리, 기타 비물리적 개념 같은 추상적인 행동에 유용합니다. 액터 컴포넌트에는 트랜스폼(Transform) 프로퍼티가 없습니다. 즉, 액터 컴포넌트의 물리적 위치나 회전 행동이 월드에 구현되지 않습니다.

  • 씬 컴포넌트 (클래스 USceneComponent, UActorComponent 의 자손)는 지오메트리 표현이 필요하지 않은 위치 기반 동작을 지원합니다. 여기에는 스프링 암, 카메라, 물리적 포스, 컨스트레인트(물리적 오브젝트는 해당 없음), 오디오가 포함됩니다.

  • 프리미티브 컴포넌트 (클래스 UPrimitiveComponent, USceneComponent 의 자손)는 지오메트리 표현이 있는 씬 컴포넌트로, 주로 비주얼 요소 렌더링이나 물리적 오브젝트와의 충돌 또는 오버랩에 사용됩니다. 스태틱 또는 스켈레탈 메시, 스프라이트, 빌보드, 파티클 시스템 외에도 박스, 캡슐, 스피어 콜리전 볼륨이 여기에 해당합니다.

구현 방법 선택

블루프린트

C++

프로젝트 설정

이 튜토리얼에서는 HealthComponent라는 액터 컴포넌트를 위한 게임플레이 로직을 생성합니다. 컴포넌트가 액터에 어태치되면 체력에서 대미지를 감소시키는 함수 기능이 액터에 포함됩니다.

예시에서는 삼인칭 템플릿 프로젝트에 있는 삼인칭 캐릭터를 사용합니다.

  1. 먼저 신규(New) > 게임(Games) > 삼인칭(Third Person) > 블루프린트(Blueprint) 프로젝트를 생성하고 프로젝트 이름은 HealthComponent 로 짓습니다.

    클릭하면 최대 크기로 볼 수 있습니다.

  2. 편집(Edit) > 프로젝트 세팅(Project Settings) > 엔진(Engine) > 입력(Input) > 바인딩(Bindings) > 액션 매핑(Action Mappings) 으로 이동한 후, 추가(+) 를 클릭하여 이름이 OnDamagePressed 인 새로운 액션 매핑을 생성하고 값을 1 로 설정합니다.

    클릭하면 최대 크기로 볼 수 있습니다.

입력에는 플레이어 캐릭터에 대미지를 가하는 것과 같은 이벤트 기반 행동에 버튼이나 키 누름을 매핑할 수 있는 액션 매핑이 있습니다.

액터 컴포넌트 생성: Health 컴포넌트

이 섹션에서는 어느 액터에나 어태치될 수 있는 액터 컴포넌트(Actor Component)인 Health 컴포넌트를 생성합니다.

플레이어의 체력 값을 유지하거나 감소시키는 로직을 만들기 시작하려면 다음 단계에 따릅니다.

  1. 콘텐츠 브라우저(Content Browser) 에서 추가/신규(Add/New) > 블루프린트 클래스(Blueprint Class) 를 클릭합니다. 부모 클래스 선택(Pick Parent Class) 메뉴에서 Actor Component 를 선택하고 BP_HealthComponent 라는 이름의 새로운 액터 컴포넌트를 생성합니다.

    ![](03_BP_CreateBPActorComponentClass.png)(w:600)

  2. 클래스 디폴트(Class Defaults) 에서 내 블루프린트(My Blueprint) 패널로 이동합니다. 그리고 변수(Variables) 카테고리에서 추가(+) 를 클릭하여 두 개의 float 변수 HealthMaxHealth 를 생성합니다.

    ![](04_BP_AddVariables.png)

  3. MaxHealth 변수를 선택하고 디테일(Details) 패널로 이동해서 MaxHealth 의 기본값을 100.0 으로 설정합니다.

    ![](05_BP_MaxHealthDetails.png)

이벤트 디스패처 생성: HandleTakeDamage

이벤트 디스패처는 이벤트 디스패처에 스스로를 바인드하거나 이벤드 디스패처를 '리슨'하는 액터에서 메서드를 호출할 수 있습니다. 그러면 이벤트 디스패처가 이벤트를 호출할 때 리슨하는 액터가 이벤트에 반응하게 됩니다.

다음 단계에 따라 HandleTakeDamage 이벤트 디스패처의 생성을 시작합니다.

  1. 내 블루프린트(My Blueprint) 패널에서 이벤트 디스패처(Event Dispatchers) 카테고리로 이동하고 추가(+) 를 클릭하여 HandleTakeDamage 라는 이름의 새로운 이벤트 디스패처를 생성합니다.

    ![](06_BP_AddEventDispatcher.png)

  2. 디테일(Details) 패널로 이동하여 입력(Input) 카테고리에서 추가(+) 버튼을 클릭한 후 다음 변수를 생성합니다.

    변수 이름

    타입

    설명

    DamageActor

    BP_HealthComponent

    대미지를 입을 액터.

    Health

    float

    액터의 기본 체력.

    Damage

    float

    적용할 기본 대미지.

    DamageType

    대미지 타입

    입히는 대미지를 설명하는 클래스.

    InstigatedBy

    컨트롤러

    대미지의 원인을 제공하는 컨트롤러. 예) 수류탄을 던지는 플레이어.

    DamageCauser

    액터

    대미지의 원인이 되는 액터. 예) 폭발한 수류탄.

    ![](07_BP_AddDispatcherInputs.png)

  3. 컴파일(Compile)저장(Save) 을 클릭합니다.

    ![](08_BP_CompileSaveButtons.png)

Health 컴포넌트의 Event Begin Play 스크립트 생성

  1. MaxHealth float를 이벤트 그래프(Event Graph) 에 드래그하여 사본을 만듭니다.

    Copy Node Graph

    ![](09_BPScript_01_01.png)

  2. Health float를 이벤트 그래프 로 드래그하고 Set Health 를 선택하여 Set Health 노드를 생성합니다.

    Copy Node Graph

    ![](10_BPScript_01_02.png)

  3. Max Health float의 출력 핀을 Set Health 노드의 Health 입력 핀에 연결하고, BeginPlay 이벤트(Event BeginPlay) 노드의 출력 실행 핀을 Set Health 노드의 입력 실행 핀에 연결합니다.

    Copy Node Graph

    ![](11_BPScript_01_03.png)

  4. Set Health 노드의 출력 실행 핀에서 드래그합니다. 액션(Actions) 메뉴에서 컨텍스트에 따라(Context Sensitive) 박스의 체크를 해제한 다음 대미지 수신 시에 이벤트 바인딩(Bind Event to On Take Any Damage) 을 검색하고 선택합니다.

    Copy Node Graph

    ![](12_BPScript_01_04.png)

  5. 대미지 수신 시에 이벤트 바인딩(Bind Event to On Take Any Damage) 노드의 타깃(Target) 핀에서 드래그하여 액션(Actions) 메뉴에서 컴포넌트(Components) 카테고리의 Get Owner 를 검색하고 선택합니다.

    Copy Node Graph

    ![](13_BPScript_01_05.png)

  6. 대미지 수신 시에 이벤트 바인딩(Bind Event to On Take Any Damage) 노드의 이벤트(Event) 핀에서 드래그하여 액션(Actions) 메뉴에서 이벤트 생성(Create Event) 을 검색하고 선택합니다.

    Copy Node Graph

    ![](14_BPScript_01_06.png)

  7. 이벤트 생성(Create Event) 노드에서 함수 선택(Select Function) 드롭다운 메뉴를 클릭하고 일치하는 함수 생성(Create matching function) 을 선택합니다.

    ![](15_BPScript_01_07.png)

함수 생성: TakeDamage

Health 컴포넌트의 Event BeginPlay 스크립트 생성을 마치면 마지막 단계에 생성한 함수가 자동으로 열립니다.

플레이어 캐릭터가 대미지를 입을 때 체력을 감소시키는 로직을 처리할 함수 스크립트를 생성해야 합니다.

그러려면 아래 단계를 따릅니다.

  1. 생성된 함수의 이름을 Take Damage 로 변경합니다.

    ![](16_RenameFunction.png)

  2. 내 블루프린트(My Blueprint) 패널에서 변수(Variables) 카테고리로 이동하여 Health 변수를 이벤트 그래프(Event Graph) 로 드래그합니다.

    Copy Node Graph

    ![](17_BPScript_02_01.png)

  3. Take Damage 노드의 Damage 변수에서 드래그하여 액션(Actions) 메뉴에서 빼기(Subtract) 를 검색하고 선택합니다.

    Copy Node Graph

    ![](18_BPScript_02_02.png)

  4. Health 핀에서 드래그하여 빼기(Subtract) 노드의 A 뺄셈 float 에 연결합니다.

    Copy Node Graph

    ![](19_BPScript_02_03.png)

  5. 빼기(Subtract) 노드의 결과에서 드래그하여 액션(Actions) 메뉴에서 Clamp (float) 을 검색하고 선택합니다.

    Copy Node Graph

    ![](20_BPScript_02_04.png)

  6. 내 블루프린트(My Blueprint) 패널에서 변수(Variables) 카테고리로 이동하여 Max Health 변수를 Clamp (float) 노드의 Max float 변수 핀으로 드래그합니다.

    Copy Node Graph

    ![](21_BPScript_02_05.png)

  7. Take Damage 노드의 실행 핀에서 드래그하여 액션(Actions) 메뉴에서 Branch 를 검색하고 선택합니다.

    Copy Node Graph

    ![](22_BPScript_02_06.png)

  8. Take Damage 노드의 Damage 변수에서 드래그하여 액션(Actions) 메뉴에서 <=(작거나 같음) 연산자를 검색하고 선택합니다.

    Copy Node Graph

    ![](23_BPScript_02_07.png)

  9. <= 연산자 노드의 boolean 반환 핀에서 드래그하여 Branch 노드의 Condition 핀에 연결합니다.

    Copy Node Graph

    ![](24_BPScript_02_08.png)

  10. Branch 노드의 False 실행 핀에서 드래그하여 액션(Actions) 메뉴에서 Set Health 를 검색하고 선택합니다.

    Copy Node Graph

    ![](25_BPScript_02_09.png)

  11. Clamp (float) 노드의 Return Value 에서 드래그하여 Set Health 노드의 Health 핀에 연결합니다.

    Copy Node Graph

    ![](26_BPScript_02_10.png)

  12. Set Health 노드의 출력 실행 핀에서 드래그하여 액션(Actions) 메뉴에서 Call Handle Take Damage 를 검색하고 선택합니다.

    Copy Node Graph

    ![](27_BPScript_02_11.png)

  13. Set Health 노드의 Health 출력 핀을 Call Handle Take Damage 노드의 Health 입력 핀에 연결합니다.

    Copy Node Graph

    ![](28_BPScript_02_12.png)

  14. Call Handle Take Damage 노드의 Damaged Actor 입력 핀에서 드래그하여 액션(Actions) 메뉴에서 셀프 레퍼런스 가져오기(Get a reference to self) 를 검색하고 선택합니다.

    Copy Node Graph

    ![](29_BPScript_02_13.png)

  15. Take Damage 노드의 Damage , Damage Type , Instigated By , Damage Causer 핀을 Call Handle Take Damage 노드의 해당 핀에 연결합니다.

    Copy Node Graph

    ![](30_BPScript_02_14.png)

  16. 컴파일(Compile)저장(Save) 을 클릭합니다.

    ![](31_CompileSaveButton.png)

완성된 블루프린트

Take Damage 함수

Copy Node Graph

![](32_BPFinalScript1.png)

Begin Play

Copy Node Graph

![](33_BPFinalScript2.png)

체력 컴포넌트를 삼인칭 캐릭터에 추가

이제 체력 컴포넌트 클래스를 생성했으니, 체력 컴포넌트 클래스의 액터 컴포넌트(Actor Component)를 삼인칭 캐릭터 클래스에 추가하고 OnDamagePressed 액션 매핑(Action Mapping)을 바인딩하면 플레이어에게 대미지를 입힐 수 있습니다.

  1. 콘텐츠(Content) > ThirdPersonBP > Blueprints 폴더로 이동하고 ThirdPersonCharacter 를 더블클릭하여 클래스 디폴트(Class Defaults) 를 엽니다.

    ![](34_OpenThirdPerCharacter.png)

  2. 컴포넌트(Components) 패널에서 컴포넌트 추가(Add Component) 버튼을 클릭하여 BP Health Component 를 검색하고 선택합니다.

    ![](35_AddBPHealthComponent.png)

  3. 이벤트 그래프(Event Graph) 를 우클릭하여 액션(Actions) 메뉴에서 OnDamagePressed 를 검색하고 선택합니다.

    Copy Node Graph

    ![](36_BPScript_03_01.png)

  4. OnDamagePressed 입력 액션(InputAction OnDamagePressed) 노드의 Pressed 실행 핀에서 드래그하여 액션(Actions) 메뉴에서 Apply Damage 를 검색하고 선택합니다.

    Copy Node Graph

    ![](37_BPScript_03_02.png)

  5. Apply Damage 노드의 Damaged Actor 핀에서 드래그하여 액션(Actions) 메뉴에서 셀프 레퍼런스 가져오기(Get a reference to self) 를 검색하고 선택합니다. Base Damage float 값을 20.0 으로 설정합니다.

    Copy Node Graph

    ![](38_BPScript_03_03.png)

    Apply Damage 함수는 게임플레이 목적의 유용한 일반 함수가 포함된 게임플레이 스태틱스 라이브러리(Gameplay Statics Library)의 일부입니다.

  6. 이벤트 그래프(Event Graph) 를 우클릭한 후 액션(Actions) 메뉴에서 이벤트 BeginPlay(Event BeginPlay) 를 검색하고 선택합니다.

    Copy Node Graph

    ![](39_BPScript_03_04.png)

  7. 컴포넌트(Components) 패널에서 BP_HealthComponent 의 사본을 드래그하여 이벤트 그래프(Event Graph) 로 가져갑니다.

    Copy Node Graph

    ![](40_BPScript_03_05.png)

  8. BP Health Component 핀에서 드래그하여 액션(Actions) 메뉴에서 Handle Take Damage에 이벤트 바인딩(Bind Event to Handle Take Damage) 을 검색하고 선택합니다.

    Copy Node Graph

    ![](41_BPScript_03_06.png)

  9. Event BeginPlay 노드의 출력 실행 핀을 Handle Take Damage에 이벤트 바인딩 의 입력 실행 핀과 연결합니다.

    Copy Node Graph

    ![](42_BPScript_03_07.png)

  10. Handle Take Damage에 이벤트 바인딩이벤트(Event) 핀에서 드래그하여 액션(Actions) 메뉴에서 커스텀 이벤트 추가(Add Custom Event) 를 검색하고 선택합니다. 커스텀 이벤트의 이름은 OnHealthChanged 로 짓습니다.

    Copy Node Graph

    ![](43_BPScript_03_08.png)

  11. OnHealthChanged 노드의 Health 핀에서 드래그하여 액션(Actions) 메뉴에서 <=(작거나 같음) 연산자를 검색하고 선택합니다.

    Copy Node Graph

    ![](44_BPScript_03_09.png)

  12. OnHealthChanged 노드의 실행 핀에서 드래그하여 액션(Actions) 메뉴에서 Branch 를 검색하고 선택합니다.

    Copy Node Graph

    ![](45_BPScript_03_10.png)

  13. <=(작거나 같음) 연산자 노드의 boolean 반환 핀에서 드래그하여 Branch 노드의 Condition 핀에 연결합니다.

    Copy Node Graph

    ![](46_BPScript_03_11.png)

  14. Branch 노드의 True 실행 핀에서 드래그하여 액션(Actions) 메뉴에서 Destroy Actor 를 검색하고 선택합니다.

    Copy Node Graph

    ![](47_BPScript_03_12.png)

    체력 값이 0이 된 플레이어는 월드에서 제거됩니다.

  15. OnHealthChanged 노드의 실행 핀에서 드래그하여 액션(Actions) 메뉴에서 Print String 을 검색하고 선택합니다.

    Copy Node Graph

    ![](48_BPScript_03_13.png)

    Print String을 사용하면 플레이어 캐릭터가 대미지를 입을 때마다 화면에 현재 체력 값을 출력할 수 있습니다.

  16. OnHealthChanged 노드의 Health 핀을 Print String 노드의 In String 핀에 연결합니다.

    ![](49_BPScript_03_14.png)

  17. 컴파일(Compile)저장(Save) 을 클릭합니다.

    ![](50_CompileSaveButton.png)

완성된 블루프린트

Copy Node Graph

![](51_BPFinalScript3.png)

최종 결과물

이제 체력 컴포넌트의 함수 기능을 테스트할 준비가 되었습니다.

WASD 키를 사용해 캐릭터를 움직일 수 있습니다. 숫자 1을 누르면 캐릭터는 체력이 0이 될 때까지 대미지를 입고, 체력이 0이 되면 월드에서 소멸됩니다.

툴바 로 이동하여 플레이 를 클릭합니다.

![](52_PlayButton.png)

프로젝트 설정

이 튜토리얼에서는 HealthComponent라는 액터 컴포넌트를 위한 게임플레이 로직을 생성합니다. 컴포넌트가 액터에 어태치되면 체력에서 대미지를 감소시키는 함수 기능이 액터에 포함됩니다.

예시에서는 삼인칭 템플릿 프로젝트에 있는 삼인칭 캐릭터를 사용합니다.

  1. 먼저 신규(New) > 게임(Games) > 삼인칭(Third Person) > C++ 프로젝트를 생성하고 프로젝트 이름은 HealthComp 로 짓습니다.

    클릭하면 최대 크기로 볼 수 있습니다.

  2. 편집(Edit) > 프로젝트 세팅(Project Settings) > 엔진(Engine) > 입력(Input) > 바인딩(Bindings) > 액션 매핑(Action Mappings) 으로 이동한 후, 추가(+) 를 클릭하여 이름이 OnDamagePressed 인 새로운 액션 매핑을 생성하고 값을 1 로 설정합니다.

    클릭하면 최대 크기로 볼 수 있습니다.

    입력에는 플레이어 캐릭터에 대미지를 가하는 것과 같은 이벤트 기반 행동에 버튼이나 키 누름을 매핑할 수 있는 액션 매핑이 있습니다.

액터 컴포넌트 생성: Health 컴포넌트

델리게이트는 일반적인 타입 안전(type-safe) 방식으로 C++ 오브젝트에 멤버 함수를 호출할 수 있습니다. 액터가 델리게이트에 바인딩되면 해당 델리게이트의 멤버 함수 이벤트에 반응하게 됩니다.

다음 단계에 따라 OnHealthChanged 델리게이트의 생성을 시작합니다.

  1. 툴(Tools) > 새로운 C++ 클래스(C++ Class) 를 클릭하여 C++ 클래스 를 생성한 다음, 부모 클래스 선택(Choose Parent Class) 메뉴에서 액터 컴포넌트(Actor Component) 를 선택하고 다음(Next) 을 클릭합니다.

    클릭하면 최대 크기로 볼 수 있습니다.

  2. 액터 컴포넌트 클래스를 HealthComponent 로 명명하고 생성(Create) 을 클릭합니다.

    클릭하면 최대 크기로 볼 수 있습니다.

  3. HealthComponent.h 파일 내에서 다음 델리게이트를 선언합니다.

    DECLARE_DYNAMIC_MULTICAST_DELEGATE_SixParams(FOnHealthChangedSignature, UHealthComponent*, HealthComponent, float, Health, float, DamageAmount, const class UDamageType*, DamageType, class AController*, InstigatedBy, AActor*, DamageCauser);
  4. 클래스 디폴트(Class Defaults) 에서 다음 메서드와 변수를 선언합니다.

    public:
    
        UPROPERTY(BlueprintAssignable, Category = "Events")
        FOnHealthChangedSignature OnHealthChanged;
    
    protected:
    
        UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
        float Health;
    
        UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
        float MaxHealth;
    
        UFUNCTION(BlueprintCallable)
        void HandleTakeDamage(AActor* DamageActor, float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser);
  5. HealthComponent.cpp 파일로 이동해 HealthComponent 생성자 에서 다음 클래스 변수를 초기화합니다.

    UHealthComponent::UHealthComponent()
    {
        MaxHealth = 100.0f;
    }
  6. BeginPlay 클래스 메서드에 대해 다음 정의를 구현합니다.

    void UHealthComponent::BeginPlay()
    {
        Super::BeginPlay();
        //이 액터 컴포넌트의 오너 구하기.
        AActor* MyOwner = GetOwner();
    
        if (MyOwner)
        {
            //이제 오너 오브젝트가 OnTakeAnyDamage 함수에 반응하도록 바인딩되었습니다.        
            MyOwner->OnTakeAnyDamage.AddDynamic(this,&UHealthComponent::HandleTakeDamage);
        }
        //Health를 MaxHealth와 같게 설정.         
        Health = MaxHealth;
    }
  7. HandleTakeDamage 메서드에 대해 다음 코드를 선언합니다.

    void UHealthComponent::HandleTakeDamage(AActor* DamageActor, float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser) 
    { 
        if (Damage <= 0.0f) 
        { 
            return; 
        }
        Health = FMath::Clamp(Health - Damage, 0.0f, MaxHealth); OnHealthChanged.Broadcast(this, Health, Damage, DamageType, InstigatedBy, DamageCauser); 
    }
  8. 코드를 컴파일(Compile) 합니다.

완성된 코드

Health Component.h

    #pragma once

    #include "CoreMinimal.h"
    #include "Components/ActorComponent.h"
    #include "HealthComponent.generated.h"

    DECLARE_DYNAMIC_MULTICAST_DELEGATE_SixParams(FOnHealthChangedSignature, UHealthComponent*, HealthComponent, float, Health, float, DamageAmount, const class UDamageType*, DamageType, class AController*, InstigatedBy, AActor*, DamageCauser);

    UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
    class HEALTHCOMP_API UHealthComponent : public UActorComponent
    {
    GENERATED_BODY()

    public: 

        // 이 컴포넌트의 프로퍼티에 적용되는 디폴트값 설정
        UHealthComponent();

    protected:

        // 게임 시작 시 호출
        virtual void BeginPlay() override;

    public: 

        // 프레임마다 호출
        virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

    public:

        UPROPERTY(BlueprintAssignable, Category = "Events")
        FOnHealthChangedSignature OnHealthChanged;

    protected:

        UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
        float Health;

        UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
        float MaxHealth;

        UFUNCTION(BlueprintCallable)
        void HandleTakeDamage(AActor* DamageActor, float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser);
    };

Health Component.cpp

    #include "HealthComponent.h"

    // 이 컴포넌트의 프로퍼티에 적용되는 디폴트값 설정
    UHealthComponent::UHealthComponent()
    {
        // 이 컴포넌트가 게임 시작 시 초기화되고 프레임마다 틱되도록 설정.  이 설정이 필요 없는 경우
        // 비활성화하면 퍼포먼스가 향상됩니다.
        PrimaryComponentTick.bCanEverTick = true;

        MaxHealth = 100.0f;
    }

    // 게임 시작 시 호출
    void UHealthComponent::BeginPlay()
    {
        Super::BeginPlay();

        //이 액터 컴포넌트의 오너 구하기.
        AActor* MyOwner = GetOwner();

        if(MyOwner)
        {
            //이제 오너 오브젝트가 OnTakeAnyDamage 함수에 반응하도록 바인딩되었습니다.        
            MyOwner->OnTakeAnyDamage.AddDynamic(this,&UHealthComponent::HandleTakeDamage);
        }
        //Health를 MaxHealth와 같게 설정.
        Health = MaxHealth;
    }

    // 프레임마다 호출
    void UHealthComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
    {
        Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
    }

    void UHealthComponent::HandleTakeDamage(AActor* DamageActor, float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser)
    {
        if (Damage <= 0.0f)
        {
            //대미지 양이 0 이하.
            return;
        } 
        Health = FMath::Clamp(Health - Damage, 0.0f, MaxHealth); OnHealthChanged.Broadcast(this, Health, Damage, DamageType, InstigatedBy, DamageCauser);
    }

Health 컴포넌트를 HealthCompCharacter에 추가

이제 체력 컴포넌트 클래스를 생성했으니, 체력 컴포넌트 클래스의 액터 컴포넌트(Actor Component)를 HealthCompCharacter 클래스에 추가하고 OnDamagePressed 액션 매핑(Action Mapping)을 바인딩하면 플레이어에게 대미지를 입힐 수 있습니다.

  1. 콘텐츠 브라우저(Content Browser) 에서 C++ 클래스(C++ Classes) 로 이동한 다음 HealthCompCharacter 를 더블클릭하여 HealthCompCharacter.h 파일을 엽니다.

  2. 클래스 디폴트(Class Defaults) 에서 다음 코드를 선언합니다.

    protected:
    
        virtual void BeginPlay() override;
    
        UPROPERTY(EditDefaultsOnly,BlueprintReadWrite)
        class UHealthComponent* HealthComponent; 
    
        UFUNCTION()
        void OnHealthChanged(UHealthComponent* HealthComp, float Health, float DamageAmount, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser);
    
        //플레이어에 대한 대미지를 호출하는 함수
        UFUNCTION()
        void DamagePlayerCharacter();
    
        //플레이어에게 가할 대미지 타입의 컨테이너
        UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Weapon")
        TSubclassOf<UDamageType> GenericDamageType;
  3. HealthCompCharacter.cpp 파일로 이동하여 다음 클래스 라이브러리를 포함시킵니다.

    #include "HealthComponent.h"
    #include "Kismet/GameplayStatics.h"

    게임플레이 스태틱스 라이브러리(Gameplay Statics Library)에는 게임플레이 목적에 맞는 다양한 유틸리티 헬퍼 함수가 있습니다. 이 인스턴스에는 ApplyDamage 메서드를 사용하겠습니다.

  4. AHealthCompCharacter 생성자 내에서 Health 컴포넌트 클래스를 초기화합니다.

    AHealthCompCharacter::AHealthCompCharacter()
    {
        HealthComponent = CreateDefaultSubobject<UHealthComponent>(TEXT("HealthComponent"));
    }
  5. AHealthCompChatacter::BeginPlay 메서드로 이동하여 다음 코드를 추가합니다.

    void AHealthCompCharacter::BeginPlay()
    {
        Super::BeginPlay();
        HealthComponent->OnHealthChanged.AddDynamic(this, &AHealthCompCharacter::OnHealthChanged);
    }
  6. AHealthCompCharacter::OnHealthChanged 메서드에서 체력이 0이 되면 플레이 캐릭터가 소멸하는 다음 로직을 구현합니다.

    void AHealthCompCharacter::OnHealthChanged(UHealthComponent* HealthComp, float Health, float DamageAmount, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser)
    {
        if (Health <= 0.0f)
        {
            Destroy();
        }
        UE_LOG(LogTemp, Warning, TEXT("The Player's Current Health is: %f"), Health);
    }

    UE_LOG는 데이터를 출력 로그로 시각화하는 명령과 함께 사용할 다양한 상세도 유형을 포함하는 매크로입니다.

  7. 다음 코드를 추가하여 AHealthCompCharacter::DamagePlayerCharacter 메서드를 구현합니다.

    void AHealthCompCharacter::DamagePlayerCharacter()
    {
        UGameplayStatics::ApplyDamage(this, 20.0f, GetInstigatorController(),this,GenericDamageType);
    }
  8. AHealthCompCharacter::SetupPlayerInputComponent 메서드로 이동하여 OnDamagePressed 바인딩을 DamagePlayerCharacter 함수에 할당합니다.

    void AHealthCompCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
    {
        //OnDamagePressed 액션 이벤트 바인딩
        PlayerInputComponent->BindAction("OnDamagePressed", IE_Pressed, this, &AHealthCompCharacter::DamagePlayerCharacter);
    }
  9. 코드를 컴파일(Compile) 합니다.

완성된 코드

HealthCompCharacter.h

    #pragma once

    #include "CoreMinimal.h"
    #include "GameFramework/Character.h"
    #include "HealthCompCharacter.generated.h"

    UCLASS(config=Game)
    class AHealthCompCharacter : public ACharacter
    {
        GENERATED_BODY()

        /** 캐릭터 뒤에 카메라를 배치하는 카메라 붐 */
        UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
        class USpringArmComponent* CameraBoom;

        /** 카메라 따라가기 */
        UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
        class UCameraComponent* FollowCamera;

    public:

        AHealthCompCharacter();

        /** 베이스 회전 속도, 단위는 도(º)/초. 다른 스케일 값 조절로 인해 최종 회전 속도가 영향을 받을 수 있습니다. */
        UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Input)
        float TurnRateGamepad;

    protected:

        /** 앞뒤 입력으로 호출 */
        void MoveForward(float Value);

        /** 좌우 입력으로 호출 */
        void MoveRight(float Value);

        /** 
        * 입력을 통해 호출되어 지정된 속도로 회전 
        * @param Rate   정규화된 비율이며, 1.0인 경우 지정된 회전 속도의 100%를 의미합니다.
        */
        void TurnAtRate(float Rate);

        /**
        * 입력을 통해 호출되어 지정된 속도로 올려다보기/내려다보기 
        * @param Rate   정규화된 비율이며, 1.0인 경우 지정된 회전 속도의 100%를 의미합니다.
        */
        void LookUpAtRate(float Rate);

        /** 터치 입력 시작 시 핸들러 */
        void TouchStarted(ETouchIndex::Type FingerIndex, FVector Location);

        /** 터치 입력 중지 시 핸들러 */
        void TouchStopped(ETouchIndex::Type FingerIndex, FVector Location);

    protected:

        // APawn 인터페이스
        virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
        // APawn 인터페이스 종료

    public:

        /** CameraBoom 서브오브젝트 반환 **/
        FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
        /** FollowCamera 서브오브젝트 반환 **/
        FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }

    protected:

        virtual void BeginPlay() override;

        UPROPERTY(EditDefaultsOnly, BlueprintReadWrite)
        class UHealthComponent* HealthComponent;

        UFUNCTION()
        void OnHealthChanged(UHealthComponent* HealthComp, float Health, float DamageAmount, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser);

        //플레이어에 대한 대미지를 호출하는 함수
        UFUNCTION()
        void DamagePlayerCharacter();

        //플레이어에게 가할 대미지 타입의 컨테이너
        UPROPERTY(EditDefaultsOnly,BlueprintReadOnly, Category = "Weapon")
        TSubclassOf<UDamageType> GenericDamageType;
    };

HealthCompCharacter.cpp

    #include "HealthCompCharacter.h"
    #include "HeadMountedDisplayFunctionLibrary.h"
    #include "Camera/CameraComponent.h"
    #include "Components/CapsuleComponent.h"
    #include "Components/InputComponent.h"
    #include "GameFramework/CharacterMovementComponent.h"
    #include "GameFramework/Controller.h"
    #include "GameFramework/SpringArmComponent.h"
    #include "HealthComponent.h"
    #include "Kismet/GameplayStatics.h"

//////////////////////////////////////////////////////////////////////////
// AHealthCompCharacter

    AHealthCompCharacter::AHealthCompCharacter()
    {
        // 콜리전 캡슐 크기 설정
        GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);

        // 입력에 대한 회전 속도 설정
        TurnRateGamepad = 50.f;

        // 컨트롤러 회전 시 회전하지 않습니다. 카메라에만 영향을 미치도록 합니다.
        bUseControllerRotationPitch = false;
        bUseControllerRotationYaw = false;
        bUseControllerRotationRoll = false;

        // 캐릭터 무브먼트 환경설정
        GetCharacterMovement()->bOrientRotationToMovement = true; // 캐릭터가 입력 방향으로 이동    
        GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f); // 위의 캐릭터가 이동하는 회전 속도

        // 참고: 이 변수를 비롯한 많은 변수는 조정하기 위해 다시 컴파일하지 않고도
        // 캐릭터 블루프린트에서 미세조정하여 반복작업 시간을 단축할 수 있습니다.
        GetCharacterMovement()->JumpZVelocity = 700.f;
        GetCharacterMovement()->AirControl = 0.35f;
        GetCharacterMovement()->MaxWalkSpeed = 500.f;
        GetCharacterMovement()->MinAnalogWalkSpeed = 20.f;
        GetCharacterMovement()->BrakingDecelerationWalking = 2000.f;

        // 카메라 붐 생성(콜리전 있을 시 플레이어 쪽으로 들어옴)
        CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
        CameraBoom->SetupAttachment(RootComponent);
        CameraBoom->TargetArmLength = 400.0f; // 캐릭터 뒤의 카메라가 이 거리에서 따라옴 
        CameraBoom->bUsePawnControlRotation = true; // 컨트롤러 기반으로 암 회전

        // 카메라 따라가기 생성
        FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
        FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // 카메라를 붐 끝에 어태치하여 붐이 컨트롤러 오리엔테이션에 맞추어 조절되도록 함
        FollowCamera->bUsePawnControlRotation = false; // 카메라가 암 기준으로 회전하지 않음

        // 참고: 캐릭터로부터 상속받는 메시 컴포넌트에 대한 스켈레탈 메시와 애님 블루프린트 레퍼런스는 
        // C++ 직접 콘텐츠 레퍼런스를 방지하기 위해 이름이 MyCharacter인 파생 블루프린트 에셋에서 설정됨

        HealthComponent = CreateDefaultSubobject<UHealthComponent>(TEXT("HealthComponent"));
    }

    //////////////////////////////////////////////////////////////////////////
    // 입력

    void AHealthCompCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
    {
        // 게임플레이 키 바인딩 설정
        check(PlayerInputComponent);
        PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
        PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);
        PlayerInputComponent->BindAxis("Move Forward / Backward", this, &AHealthCompCharacter::MoveForward);
        PlayerInputComponent->BindAxis("Move Right / Left", this, &AHealthCompCharacter::MoveRight);

        // 2가지 버전의 회전 바인딩이 있어 서로 다른 종류의 디바이스를 다양한 방식으로 처리할 수 있습니다.
        // 'turn'은 마우스와 같은 절대 델타를 제공하는 디바이스를 처리합니다.
        // 'turnrate'는 아날로그 조이스틱과 같이 변화의 속도를 취급할 디바이스에 사용합니다.
        PlayerInputComponent->BindAxis("Turn Right / Left Mouse", this, &APawn::AddControllerYawInput);
        PlayerInputComponent->BindAxis("Turn Right / Left Gamepad", this, &AHealthCompCharacter::TurnAtRate);
        PlayerInputComponent->BindAxis("Look Up / Down Mouse", this, &APawn::AddControllerPitchInput);
        PlayerInputComponent->BindAxis("Look Up / Down Gamepad", this, &AHealthCompCharacter::LookUpAtRate);

        // 터치 디바이스 처리
        PlayerInputComponent->BindTouch(IE_Pressed, this, &AHealthCompCharacter::TouchStarted);
        PlayerInputComponent->BindTouch(IE_Released, this, &AHealthCompCharacter::TouchStopped);

        //OnDamagePressed 액션 이벤트 바인딩
        PlayerInputComponent->BindAction("OnDamagePressed", IE_Pressed, this, &AHealthCompCharacter::DamagePlayerCharacter);
    }

    void AHealthCompCharacter::TouchStarted(ETouchIndex::Type FingerIndex, FVector Location)
    {
        Jump();
    }

    void AHealthCompCharacter::TouchStopped(ETouchIndex::Type FingerIndex, FVector Location)
    {
        StopJumping();
    }

    void AHealthCompCharacter::TurnAtRate(float Rate)
    {
        // 속도 정보로부터 이 프레임에 대한 델타 계산
        AddControllerYawInput(Rate * TurnRateGamepad * GetWorld()->GetDeltaSeconds());
    }

    void AHealthCompCharacter::LookUpAtRate(float Rate)
    {
        // 속도 정보로부터 이 프레임에 대한 델타 계산
        AddControllerPitchInput(Rate * TurnRateGamepad * GetWorld()->GetDeltaSeconds());
    }

    void AHealthCompCharacter::MoveForward(float Value)
    {
        if ((Controller != nullptr) && (Value != 0.0f))
        {
            // 앞쪽 찾기
            const FRotator Rotation = Controller->GetControlRotation();
            const FRotator YawRotation(0, Rotation.Yaw, 0);

            // 앞쪽 벡터 구하기
            const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
            AddMovementInput(Direction, Value);
        }
    }

    void AHealthCompCharacter::MoveRight(float Value)
    {
        if ( (Controller != nullptr) && (Value != 0.0f) )
        {
            // 오른쪽 찾기
            const FRotator Rotation = Controller->GetControlRotation();
            const FRotator YawRotation(0, Rotation.Yaw, 0);

            // 오른쪽 벡터 구하기 
            const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
            // 해당 방향으로 이동 추가
            AddMovementInput(Direction, Value);
        }
    }

    void AHealthCompCharacter::BeginPlay()
    {
        Super::BeginPlay();
        HealthComponent->OnHealthChanged.AddDynamic(this, &AHealthCompCharacter::OnHealthChanged);
    }

    void AHealthCompCharacter::OnHealthChanged(UHealthComponent* HealthComp, float Health, float DamageAmount, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser)
    {
        if (Health <= 0.0f)
        {
            Destroy();
        }
        UE_LOG(LogTemp, Warning, TEXT("The Player's Current Health is: %f"), Health);
    }

    void AHealthCompCharacter::DamagePlayerCharacter()
    {
        UGameplayStatics::ApplyDamage(this, 20.0f, GetInstigatorController(), this, GenericDamageType);
    }

최종 결과물

이제 체력 컴포넌트의 함수 기능을 테스트할 준비가 되었습니다.

WASD 키를 사용해 캐릭터를 움직일 수 있습니다. 숫자 1을 누르면 캐릭터는 체력이 0이 될 때까지 대미지를 입고, 체력이 0이 되면 월드에서 소멸됩니다.

툴바 로 이동하여 플레이 를 누릅니다.

![](05_PlayButton.png)

추가 리소스

컴포넌트 추가와 블루프린트를 연결하는 여러 개념이 있으며, 아래 링크에서 관련 정보를 추가로 확인할 수 있습니다.