💭회고
이전 정리했던 내용을 바탕으로 프로젝트에 실질적으로 응용해보는 과정을 작성해보았다. 작업 과정의 흐름대로 작성하기에 중간에 뜬금없는 내용이 들어가기도 하는 점을 유념해두자...! 다음 세 가지 중요한 내용을 살펴보자:
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 생성 과정:
- Content Browser에서 우클릭 → Miscellaneous(기타) → DataTable 선택
- "Pick Row Structure" 창에서 FCharacterStatInfo(본인이 만든) 구조체 선택
- 생성된 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이나 다른 데이터 소스로부터 데이터를 로드할 때 사용.
- void InitializeStats(const FCharacterStatInfo& NewStats);
- 사망 이벤트 (Delegate) 선언
- DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeath);
- 컴포넌트가 특정 이벤트(예: 캐릭터 사망)를 처리할 때 여러 바인딩된 함수들을 호출할 수 있도록 delegate를 선언.
- DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDeath);
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의 슬롯 노드 이름이 일치하지 않는 경우.
문제 해결 단계:
- Anim Montage의 슬롯 이름 확인
- Montage 에디터에서 슬롯 섹션을 확인.
- 예: "DefaultSlot" 또는 "UpperBodySlot"
- 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 디버깅 방법
애니메이션 문제를 해결하기 위한 효과적인 디버깅 방법:
- AnimBP 디버그 모드:
- 플레이 모드에서 캐릭터 선택 → 디테일 패널 → Animation Blueprint 섹션
- 현재 재생 중인 애니메이션과 상태를 실시간으로 확인
- 로그 출력:
- 몽타주 재생 함수 호출 시점에 로그 출력
UE_LOG(LogTemp, Warning, TEXT("Playing Attack Montage: %s"), *AttackMontage->GetName());
- 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. 마치며: 향후 확장 방향(아이디어)
이 시스템을 다음과 같이 확장할 수도 있다:
- 아이템과 장비 시스템 연동:
- 장비 착용 시 스탯 보너스 적용
- 무기에 따른 공격력 변경
- 레벨업 시스템:
- 경험치 관리 로직 추가
- 레벨별 스탯 성장 곡선 설계
- 스킬 시스템 확장:
- 스킬에 따른 스탯 소모(마나, 스태미나 등)
- 스킬 레벨에 따른 데미지 계산 로직
🟣오늘의 옵시디언 현황
'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 |