캐릭터 스킬, 스텟 로직 컴포넌트 구조로 만들기 (응용)

2025. 4. 9. 23:03·Dev./UE 언리얼 엔진

💭회고

이전 정리했던 내용을 바탕으로 프로젝트에 실질적으로 응용해보는 과정을 작성해보았다. 작업 과정의 흐름대로 작성하기에 중간에 뜬금없는 내용이 들어가기도 하는 점을 유념해두자...! 다음 세 가지 중요한 내용을 살펴보자:

1. 데이터 관리를 위한 구조체(USTRUCT) 생성 방법
2. 스탯 관리를 위한 컴포넌트 설계 및 구현 방법
3. (+) 애니메이션 시스템 연동 시 발생할 수 있는 문제점과 해결책

 

[ActorComponent] 캐릭터 스킬, 스텟 로직 컴포넌트 구조로 만들기

📒학습 내용🧩 언리얼 엔진 컴포넌트 시스템 이해하기 게임 코드 리팩터링: 5가지 핵심 결정 원칙💭회고 🫧Clean Code :: 클린 코드 가이드💭회고개발에서 가장 중요한 역량 중 하나인 '클

raindrovvv.tistory.com

🗺️마인드맵

📒학습 내용

1. 캐릭터 스탯 구조체 만들기

1.1 USTRUCT 구조체 생성

언리얼 엔진에서 데이터를 효율적으로 관리하기 위해 구조체를 활용한다. USTRUCT는 블루프린트에서도 사용 가능한 구조체를 만드는 매크로다.

// CharacterStatInfo.h

#pragma once

#include "CoreMinimal.h"
#include "Engine/DataTable.h"
#include "CharacterStatInfo.generated.h"

USTRUCT(BlueprintType)
struct FCharacterStatInfo : public FTableRowBase
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Stat")
    float MaxHP;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Stat")
    float Attack;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Stat")
    float Defense;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Stat")
    float MoveSpeed;

    FCharacterStatInfo()
        : MaxHP(100.f)
        , Attack(10.f)
        , Defense(5.f)
        , MoveSpeed(600.f)
    {
    }
};
📌 구조체 내에 기본값을 설정하면 실수로 초기화를 누락했을 때 발생할 수 있는 버그를 예방할 수 있다. 특히 다수의 팀원이 협업하는 환경에서 더욱 중요...!

1.2 USTRUCT와 에디터 표시 관련 의문점 해결

구조체 파일을 만들었는데 에디터의 C++ 클래스 목록에 표시되지 않아 혼란스러울 수 있다. 이는 정상적인 현상이다.

USTRUCT와 UCLASS의 차이점

특성 USTRUCT  UCLASS
에디터 표시 C++ 클래스 목록에 표시되지 않음 C++ 클래스 목록에 표시됨
주요 용도 데이터 구조, DataTable 행 게임 오브젝트, 컴포넌트
상속 제한적 상속 지원 완전한 상속 지원
리플렉션 부분적 리플렉션 지원 완전한 리플렉션 지원

구조체는 단순 데이터 컨테이너로, 클래스처럼 독립적인 기능을 갖지 않기 때문에 C++ 클래스 목록에 표시되지 않는다.

1.3 DataTable로 구조체 활용하기

DataTable 생성 과정:

생성!

  1. Content Browser에서 우클릭 → Miscellaneous(기타) → DataTable 선택
  2. "Pick Row Structure" 창에서 FCharacterStatInfo(본인이 만든) 구조체 선택
  3. 생성된 DataTable에서 행(Row)을 추가하여 캐릭터별 스탯 설정
📌 실무 팁: DataTable은 게임 밸런싱 단계에서 디자이너가 직접 수치를 조정할 수 있어 프로그래머와 디자이너 간 협업에 매우 유용합. CSV 파일로 내보내기/가져오기 기능을 활용하면 대량의 데이터를 빠르게 수정할 수 있다.

2. 스탯 컴포넌트 설계 및 구현

이제 앞서 만든 구조체를 활용하여 캐릭터의 스탯을 관리할 컴포넌트를 구현!

2.1 StatComponent 헤더 파일

// StatComponent.h

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "CharacterStatInfo.h"
#include "StatComponent.generated.h"

// 각 캐릭터의 HP, 공격력, 방어력 등 스탯을 관리하는 컴포넌트
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class JOSEON12KNIGHTS_API UStatComponent : public UActorComponent
{
    GENERATED_BODY()

public:
    UStatComponent();

protected:
    // 초기화된 스탯 정보(최대 체력, 공격력, 방어력, 이동속도 등)
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Stat")
    FCharacterStatInfo BaseStats;

    // 현재 HP
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Stat")
    float CurrentHP;

public:
    // DataTable에서 로드하거나, 직접 스탯 세팅
    UFUNCTION(BlueprintCallable, Category="Stat")
    void InitializeStats(const FCharacterStatInfo& NewStats);

    // 현재 HP
    UFUNCTION(BlueprintCallable, Category="Stat")
    float GetCurrentHP() const { return CurrentHP; }

    // 최대 HP
    UFUNCTION(BlueprintCallable, Category="Stat")
    float GetMaxHP() const { return BaseStats.MaxHP; }

    // 공격력, 방어력, 이동속도 Getter
    UFUNCTION(BlueprintCallable, Category="Stat")
    float GetAttack() const { return BaseStats.Attack; }

    UFUNCTION(BlueprintCallable, Category="Stat")
    float GetDefense() const { return BaseStats.Defense; }

    UFUNCTION(BlueprintCallable, Category="Stat")
    float GetMoveSpeed() const { return BaseStats.MoveSpeed; }

    // HP 변경 (데미지/회복)
    UFUNCTION(BlueprintCallable, Category="Stat")
    void ChangeHP(float Delta);

    // 사망 처리 Delegate
    DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeath);
    UPROPERTY(BlueprintAssignable, Category="Stat")
    FOnDeath OnDeath;

protected:
    virtual void BeginPlay() override;
};
더보기

1. 클래스 선언 및 언리얼 매크로 적용

  • UCLASS 매크로
    UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)) 매크로를 사용하여 이 컴포넌트가 사용자 정의 그룹에 속하며 블루프린트로도 생성할 수 있다. 이 옵션 덕분에 디자이너나 개발자는 블루프린트 에디터에서 쉽게 이 컴포넌트를 액터에 추가하거나 설정할 수 있다.

2. 멤버 함수 및 이벤트 선언

  • 스탯 초기화 함수
    • void InitializeStats(const FCharacterStatInfo& NewStats);
      • 외부에서 데이터를 받아 스탯을 초기화할 때 호출. 주로 DataTable이나 다른 데이터 소스로부터 데이터를 로드할 때 사용.
    •  
  • 사망 이벤트 (Delegate) 선언
    • DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeath);
      • 컴포넌트가 특정 이벤트(예: 캐릭터 사망)를 처리할 때 여러 바인딩된 함수들을 호출할 수 있도록 delegate를 선언.

2.2 StatComponent 구현 파일

// StatComponent.cpp

#include "StatComponent.h"

UStatComponent::UStatComponent()
{
    PrimaryComponentTick.bCanEverTick = false;
}

void UStatComponent::InitializeStats(const FCharacterStatInfo& NewStats)
{
    BaseStats = NewStats;
    CurrentHP = BaseStats.MaxHP;
}

void UStatComponent::ChangeHP(float Delta)
{
    CurrentHP = FMath::Clamp(CurrentHP + Delta, 0.0f, BaseStats.MaxHP);
    if (CurrentHP <= 0.0f)
    {
        // HP가 0 이하가 되면 사망 이벤트 발생
        OnDeath.Broadcast();
    }
}

void UStatComponent::BeginPlay()
{
    Super::BeginPlay();
    CurrentHP = BaseStats.MaxHP;
}

 

2.3 델리게이트(Delegate) 활용하기

OnDeath 델리게이트는 캐릭터의 사망 상태를 다른 시스템(애니메이션, 게임 로직 등)에 알리는 코드.

// 사용 예시 (예: PlayerCharacter.cpp)
void APlayerCharacter::SetupComponents()
{
    // StatComponent 초기화
    StatComponent = CreateDefaultSubobject<UStatComponent>(TEXT("StatComponent"));
    
    // 사망 이벤트 연결
    StatComponent->OnDeath.AddDynamic(this, &APlayerCharacter::OnPlayerDeath);
}

void APlayerCharacter::OnPlayerDeath()
{
    // 사망 애니메이션 재생
    // 게임오버 UI 표시
    // 리스폰 로직 등
}

델리게이트는 언리얼의 이벤트 시스템으로, 시스템 간 결합도를 낮추면서 효과적인 통신이 가능하게 한다.


3. 애니메이션 시스템 연동 및 트러블슈팅

애니메이션 시스템과 스탯 컴포넌트를 연동할 때 발생하는 문제를 해결해보자...

3.1 공격 모션이 재생되지 않는 문제

공격 애니메이션이 재생되지 않는 가장 흔한 원인은 Anim Montage의 슬롯 이름과 AnimGraph의 슬롯 노드 이름이 일치하지 않는 경우.

문제 해결 단계:

  1. Anim Montage의 슬롯 이름 확인
    • Montage 에디터에서 슬롯 섹션을 확인.
    • 예: "DefaultSlot" 또는 "UpperBodySlot"
  2. AnimGraph에 슬롯 노드 배치
    • AnimBP의 AnimGraph에서 똑같은 이름의 슬롯 노드를 추가.
    • Montage와 슬롯 노드의 이름이 일치해야 한다.
📌 실무 팁: 프로젝트 내 모든 애니메이션 슬롯 이름을 팀 내에서 표준화하고 문서화하는 것이 좋다. 예를 들어 "UpperBodySlot"은 항상 상체 애니메이션용, "FullBodySlot"은 전신 애니메이션용으로 통일하면 혼란을 줄일 수 있다.

3.2 상하체 분리 애니메이션 설정

복잡한 캐릭터 애니메이션은 종종 상체와 하체를 분리하여 처리한다.

예를 들어, 이동 중 공격 모션을 상체에만 적용하는 경우가 일반적.

설정 과정:

[ StateMachine Output (하체 움직임) ] → [ Layered Blend per Bone ] → [ Final Animation Pose ]
                                          ↑
                      [ Slot 'DefaultSlot' (상체 공격 모션) ]
  • Layered Blend per Bone 노드 설정:
    • Base Pose: StateMachine 출력(걷기/달리기)
    • Blend Pose: Slot 노드 출력(공격 모션)
    • Blend 기준점: "spine_03" 같은 상체 기준 뼈
📌 실무 팁: 성능 최적화를 위해 초기 개발 단계에서는 상하체 분리 없이 전체 몸에 애니메이션을 적용하고, 프로토타입이 안정화된 후 필요한 부분만 분리하는 접근법이 효율적이다.

3.3 디버깅 방법

애니메이션 문제를 해결하기 위한 효과적인 디버깅 방법:

  1. AnimBP 디버그 모드:
    • 플레이 모드에서 캐릭터 선택 → 디테일 패널 → Animation Blueprint 섹션
    • 현재 재생 중인 애니메이션과 상태를 실시간으로 확인
  2. 로그 출력:
    • 몽타주 재생 함수 호출 시점에 로그 출력
    UE_LOG(LogTemp, Warning, TEXT("Playing Attack Montage: %s"), *AttackMontage->GetName());
    
  3. Visual Logger 활용:
    • 애니메이션 전환 시점을 Visual Logger로 기록하면 문제 식별이 용이함

4. 컴포넌트 연동 및 플레이어 캐릭터 구현

마지막으로 만든 StatComponent와 기타 컴포넌트를 PlayerCharacter 클래스에 통합.

4.1 PlayerCharacter 헤더 파일 수정

PlayerCharacter 클래스에 StatComponent와 BuffComponent를 추가.

// PlayerCharacter.h

// 필요한 헤더 포함
#include "BuffComponent.h"
#include "StatComponent.h"

// 클래스 내부에 추가
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Component")
UBuffComponent* BuffComponent;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Component")
UStatComponent* StatComponent;

4.2 컴포넌트 초기화 및 사용

// PlayerCharacter.cpp

APlayerCharacter::APlayerCharacter()
{
    // 컴포넌트 생성
    StatComponent = CreateDefaultSubobject<UStatComponent>(TEXT("StatComponent"));
    BuffComponent = CreateDefaultSubobject<UBuffComponent>(TEXT("BuffComponent"));
    
    // 기타 초기화...
}

void APlayerCharacter::BeginPlay()
{
    Super::BeginPlay();
    
    // DataTable에서 스탯 정보 로드 (예시)
    if (CharacterDataTable)
    {
        FCharacterStatInfo* FoundStats = CharacterDataTable->FindRow<FCharacterStatInfo>(CharacterRowName, TEXT(""));
        if (FoundStats)
        {
            StatComponent->InitializeStats(*FoundStats);
        }
    }
    
    // 이벤트 연결
    StatComponent->OnDeath.AddDynamic(this, &APlayerCharacter::HandleDeath);
}

void APlayerCharacter::HandleDeath()
{
    // 사망 처리 로직
    bIsDead = true;
    GetCharacterMovement()->StopMovementImmediately();
    
    // 사망 애니메이션 재생
    if (DeathMontage)
    {
        PlayAnimMontage(DeathMontage);
    }
    
    // UI 갱신, 게임플레이 상태 변경 등
}

5. 마치며: 향후 확장 방향(아이디어)

이 시스템을 다음과 같이 확장할 수도 있다:

  1. 아이템과 장비 시스템 연동:
    • 장비 착용 시 스탯 보너스 적용
    • 무기에 따른 공격력 변경
  2. 레벨업 시스템:
    • 경험치 관리 로직 추가
    • 레벨별 스탯 성장 곡선 설계
  3. 스킬 시스템 확장:
    • 스킬에 따른 스탯 소모(마나, 스태미나 등)
    • 스킬 레벨에 따른 데미지 계산 로직

🟣오늘의 옵시디언 현황

'Dev. > UE 언리얼 엔진' 카테고리의 다른 글

[TIL_250411] 프로젝트 진행 상황 점검  (0) 2025.04.11
[TIL_250410] 프로젝트 진행 상황 정리  (0) 2025.04.10
[ActorComponent] 캐릭터 스킬, 스텟 로직 컴포넌트 구조로 만들기  (0) 2025.04.08
🚨트러블슈팅 :: Git 잠금 파일 오류 해결 가이드  (0) 2025.04.07
템플릿을 활용한 게임 개발 :: HUD_CharacterSelectPanel, HUD_CharacterInfo, HUD_MapTile  (0) 2025.04.04
'Dev./UE 언리얼 엔진' 카테고리의 다른 글
  • [TIL_250411] 프로젝트 진행 상황 점검
  • [TIL_250410] 프로젝트 진행 상황 정리
  • [ActorComponent] 캐릭터 스킬, 스텟 로직 컴포넌트 구조로 만들기
  • 🚨트러블슈팅 :: Git 잠금 파일 오류 해결 가이드
raindrovvv
raindrovvv
raindrovvv 님의 블로그 입니다.
  • raindrovvv
    raindrovvv 님의 블로그
    raindrovvv
  • 전체
    오늘
    어제
    • 분류 전체보기 (101) N
      • Dev. (94) N
        • UE 언리얼 엔진 (49) N
        • Unity 유니티 (0)
        • Wwise 와이즈 (7)
        • 게임 네트워크 (8)
        • 그래픽스 Graphics (22)
        • 프로젝트 (4)
        • 기타 개발 관련 (4)
      • Computer Science (0)
        • 하드웨어 HW (0)
        • 소프트웨어 SW (0)
        • 통신 (0)
        • 데이터 (0)
      • 블로그 (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    AI
    게임음향
    언리얼엔진
    머티리얼
    Git
    unrealengine
    TA
    셰이더
    게임네트워크
    게임개발
    게임사운드
    고라니
    게임
    Unreal
    언리얼
    그래픽스
    Wwise
    네트워크
    UE
    깃
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
raindrovvv
캐릭터 스킬, 스텟 로직 컴포넌트 구조로 만들기 (응용)
상단으로

티스토리툴바