📒학습 내용
🧩 언리얼 엔진 컴포넌트 시스템 이해하기
게임 코드 리팩터링: 5가지 핵심 결정 원칙
💭회고 🫧Clean Code :: 클린 코드 가이드💭회고개발에서 가장 중요한 역량 중 하나인 '클린 코드'에 대해 정리해보았다. 특히 언리얼 엔진 환경에서 어떻게 코드 품질을 높일 수
raindrovvv.tistory.com
언리얼 엔진은 컴포넌트 기반 아키텍처를 사용한다. 이는 게임 오브젝트(Actor)가 여러 기능 블록(Component)으로 구성된다는 의미...!
두 가지 주요 컴포넌트 유형
- UActorComponent : 비주얼 요소가 없는 순수 로직용 컴포넌트
- USceneComponent : 3D 공간에 위치하는 시각적/물리적 컴포넌트
가장 기본적인 원칙: 위치가 필요한가, 필요하지 않은가?
컴포넌트 선택 가이드라인
사용 목적 | 권장 컴포넌트 | 예시 |
순수 게임 로직 | UActorComponent | 스텟 시스템, 버프/디버프 관리, 인벤토리 |
시각적/위치 기반 요소 | USceneComponent | 메시, 파티클, 충돌 영역 |
💡 UActorComponent: 비주얼 없는 순수 로직
UActorComponent는 다음과 같은 상황에서 최적의 선택:
- 시각적 표현이 필요 없는 게임 시스템
- 위치나 회전 정보가 불필요한 기능
- 순수 데이터 처리 및 계산이 주 목적인 경우
// 스텟 컴포넌트 예시
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class GAME_API UCharacterStatComponent : public UActorComponent
{
GENERATED_BODY()
private:
// 캐릭터 기본 스텟
UPROPERTY(EditAnywhere, Category = "Stats", meta = (AllowPrivateAccess = "true"))
float BaseHealth = 100.0f;
UPROPERTY(EditAnywhere, Category = "Stats", meta = (AllowPrivateAccess = "true"))
float BaseAttackPower = 10.0f;
// 현재 스텟 값 (버프 등이 적용된 최종값)
UPROPERTY(VisibleAnywhere, Category = "Stats", meta = (AllowPrivateAccess = "true"))
float CurrentHealth;
UPROPERTY(VisibleAnywhere, Category = "Stats", meta = (AllowPrivateAccess = "true"))
float CurrentAttackPower;
public:
// 생성자
UCharacterStatComponent();
// 게임 시작시 호출
virtual void BeginPlay() override;
// 스텟 관련 주요 함수들
float GetAttackPower() const;
float GetCurrentHealth() const;
void TakeDamage(float DamageAmount);
// 버프 적용 함수
void ApplyAttackBuff(float Multiplier, float Duration);
};
UActorComponent의 장점
- 가벼운 메모리 사용: 변환 정보(Transform)가 없어 메모리 사용량이 적다.
- 단순한 구조: 계층 구조나 부착(Attachment) 개념이 없어 관리가 간단.
- 성능 최적화: 렌더링이나 물리 연산과 무관하여 CPU 부하가 적다.
🌟 USceneComponent: 3D 공간에서의 위치가 중요할 때
SceneComponent는 다음과 같은 경우에 사용:
- 게임 월드 내에서 특정 위치에 존재해야 하는 요소
- 다른 컴포넌트와의 계층 구조가 필요한 경우
- 위치, 회전, 크기 조절이 필요한 기능
// 스킬 이펙트 컴포넌트 예시
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class GAME_API USkillEffectComponent : public USceneComponent
{
GENERATED_BODY()
private:
// 스킬 이펙트용 파티클 시스템
UPROPERTY(EditAnywhere, Category = "Effects", meta = (AllowPrivateAccess = "true"))
UParticleSystemComponent* SkillParticle;
// 스킬 범위 표시용 메시
UPROPERTY(EditAnywhere, Category = "Effects", meta = (AllowPrivateAccess = "true"))
UStaticMeshComponent* RangeIndicatorMesh;
public:
// 생성자
USkillEffectComponent();
// 스킬 이펙트 활성화
void ActivateSkillEffect(float Duration);
// 스킬 범위 표시
void ShowRangeIndicator(float Radius);
};
USceneComponent의 특징
- 트랜스폼 정보: 위치, 회전, 크기 정보를 가진다.
- 계층 구조: 부모-자식 관계를 형성할 수 있어 복잡한 구조 구현이 가능.
- 시각적 디버깅: 에디터에서 시각적으로 위치와 방향을 확인할 수 있다.
⚔️ 실전 적용: 캐릭터 스킬 및 스텟 시스템 구현하기
버프 컴포넌트 구현 예시
// BuffComponent.h
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class GAME_API UBuffComponent : public UActorComponent
{
GENERATED_BODY()
private:
// 활성화된 버프 목록
UPROPERTY()
TArray<FBuffInfo> ActiveBuffs;
// 버프 업데이트를 위한 타이머 핸들
FTimerHandle BuffUpdateTimerHandle;
public:
// 생성자
UBuffComponent();
// 게임 시작시 호출
virtual void BeginPlay() override;
// 버프 적용 함수
void ApplyBuff(EBuffType BuffType, float Value, float Duration);
// 현재 버프 상태 확인
float GetCurrentBuffValue(EBuffType BuffType) const;
// 버프 업데이트 함수 (타이머에 의해 주기적으로 호출)
void UpdateBuffs();
// 특정 버프 제거
void RemoveBuff(EBuffType BuffType);
};
스텟 컴포넌트와 버프 컴포넌트의 연동
// 캐릭터 클래스 내부에서의 컴포넌트 초기화
AMyCharacter::AMyCharacter()
{
// 스텟 컴포넌트 생성
StatComponent = CreateDefaultSubobject<UStatComponent>(TEXT("StatComponent"));
// 버프 컴포넌트 생성
BuffComponent = CreateDefaultSubobject<UBuffComponent>(TEXT("BuffComponent"));
}
// 스킬 사용 시 버프 적용 예시
void AMyCharacter::UseSkill(int32 SkillIndex)
{
// ... 스킬 사용 로직 ...
// 버프 적용
if (BuffComponent)
{
// 공격력 30% 증가 버프를 10초간 적용
BuffComponent->ApplyBuff(EBuffType::AttackPower, 1.3f, 10.0f);
}
// 스텟 업데이트 (버프 반영)
if (StatComponent)
{
// 버프를 고려한 최종 공격력 계산
float FinalAttackPower = StatComponent->BaseAttackPower *
BuffComponent->GetCurrentBuffValue(EBuffType::AttackPower);
StatComponent->SetCurrentAttackPower(FinalAttackPower);
}
}
🔍실무 팁 : 실제 프로젝트에서는 스텟 컴포넌트와 버프 컴포넌트 간의 의존성을 최소화하는 것이 좋다. 이벤트 기반 통신이나 인터페이스를 활용하여 두 컴포넌트가 직접적으로 서로를 참조하지 않도록 설계하면 유지보수가 더 용이해진다.
🌐 리플리케이션(Replication) 구현하기
멀티플레이어 게임에서는 컴포넌트의 상태를 네트워크로 동기화해야 한다.
// StatComponent.h의 생성자 내부
UStatComponent::UStatComponent()
{
// 컴포넌트 리플리케이션 활성화
SetIsReplicatedByDefault(true);
}
// 리플리케이션 속성 설정
void UStatComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// 현재 체력을 모든 클라이언트에게 복제
DOREPLIFETIME(UStatComponent, CurrentHealth);
// 공격력은 소유 클라이언트에게만 복제
DOREPLIFETIME_CONDITION(UStatComponent, CurrentAttackPower, COND_OwnerOnly);
}
// 리플리케이션 이벤트 처리
UFUNCTION()
void UStatComponent::OnRep_CurrentHealth()
{
// 체력 변경 시 UI 업데이트 등의 로직
if (OwnerCharacter)
{
// 캐릭터 상태 UI 업데이트
OwnerCharacter->UpdateHealthUI(CurrentHealth);
}
}
🔵블루프린트와의 통합
C++ 컴포넌트를 블루프린트에서 활용하는 방법
// StatComponent.h에 블루프린트 호출 가능 함수 추가
UFUNCTION(BlueprintCallable, Category = "Stats")
float GetCurrentHealth() const { return CurrentHealth; }
UFUNCTION(BlueprintCallable, Category = "Stats")
void SetCurrentHealth(float NewHealth);
// 블루프린트에서 이벤트 발생시키기
UFUNCTION(BlueprintImplementableEvent, Category = "Stats")
void OnHealthChanged(float NewHealth, float OldHealth);
// C++ 내부에서 이벤트 호출
void UStatComponent::SetCurrentHealth(float NewHealth)
{
float OldHealth = CurrentHealth;
CurrentHealth = FMath::Clamp(NewHealth, 0.0f, MaxHealth);
// 블루프린트에서 구현 가능한 이벤트 발생
OnHealthChanged(CurrentHealth, OldHealth);
}
🟣오늘의 옵시디언 현황
'Dev. > UE 언리얼 엔진' 카테고리의 다른 글
[TIL_250410] 프로젝트 진행 상황 정리 (0) | 2025.04.10 |
---|---|
캐릭터 스킬, 스텟 로직 컴포넌트 구조로 만들기 (응용) (0) | 2025.04.09 |
🚨트러블슈팅 :: Git 잠금 파일 오류 해결 가이드 (0) | 2025.04.07 |
템플릿을 활용한 게임 개발 :: HUD_CharacterSelectPanel, HUD_CharacterInfo, HUD_MapTile (0) | 2025.04.04 |
템플릿을 활용한 게임 개발 :: HUD_Menus, MainMenu // BP_WidgetMacros (1) | 2025.04.03 |