icon안동민 개발노트

컴포넌트 (Component) 시스템 활용


 언리얼 엔진의 컴포넌트 시스템은 게임 객체의 기능을 모듈화하고 재사용성을 높이는 강력한 도구입니다.

 이 절에서는 컴포넌트의 개념, 주요 유형, 그리고 C++에서의 활용 방법을 살펴보겠습니다.

UActorComponent의 개념

 UActorComponent는 모든 컴포넌트의 기본 클래스로, 액터에 부착되어 특정 기능을 제공합니다. 주요 특징은 다음과 같습니다.

  1. 독립적인 기능 모듈화
  2. 재사용성과 확장성 제공
  3. Tick 기능을 통한 주기적 업데이트
  4. 액터의 생명주기와 연동

주요 컴포넌트 유형

  1. UActorComponent : 기본 컴포넌트 클래스
  2. USceneComponent : 변환(위치, 회전, 스케일) 정보를 가진 컴포넌트
  3. UPrimitiveComponent : 렌더링 또는 충돌 기능을 가진 컴포넌트

 예시

  • UStaticMeshComponent : 정적 메시 표현
  • USkeletalMeshComponent : 스켈레탈 메시 표현
  • UAudioComponent : 사운드 재생
  • UParticleSystemComponent : 파티클 시스템 표현

C++에서 커스텀 컴포넌트 생성 및 구현

 기본 컴포넌트 클래스 선언

// MyComponent.h
##pragma once
 
##include "CoreMinimal.h"
##include "Components/ActorComponent.h"
##include "MyComponent.generated.h"
 
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class MYPROJECT_API UMyComponent : public UActorComponent
{
    GENERATED_BODY()
 
public:    
    UMyComponent();
 
protected:
    virtual void BeginPlay() override;
 
public:    
    virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="MyComponent")
    float MyFloat;
 
    UFUNCTION(BlueprintCallable, Category="MyComponent")
    void MyFunction();
};

 컴포넌트 구현

// MyComponent.cpp
##include "MyComponent.h"
 
UMyComponent::UMyComponent()
{
    PrimaryComponentTick.bCanEverTick = true;
    MyFloat = 0.0f;
}
 
void UMyComponent::BeginPlay()
{
    Super::BeginPlay();
}
 
void UMyComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
    MyFloat += DeltaTime;
}
 
void UMyComponent::MyFunction()
{
    UE_LOG(LogTemp, Log, TEXT("MyComponent::MyFunction called. MyFloat: %f"), MyFloat);
}

액터에 컴포넌트 추가 및 관리

 액터의 생성자에서 컴포넌트를 추가하고 설정할 수 있습니다.

// MyActor.h
UCLASS()
class MYPROJECT_API AMyActor : public AActor
{
    GENERATED_BODY()
 
public:
    AMyActor();
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components")
    UMyComponent* MyComponent;
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components")
    UStaticMeshComponent* MeshComponent;
};
 
// MyActor.cpp
AMyActor::AMyActor()
{
    MyComponent = CreateDefaultSubobject<UMyComponent>(TEXT("MyComponent"));
    MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComponent"));
    RootComponent = MeshComponent;
}

컴포넌트 간 상호작용

 컴포넌트 간 상호작용은 주로 다음과 같은 방법으로 구현합니다.

  1. 직접 참조
  2. 이벤트 또는 델리게이트 사용
  3. 인터페이스 활용

 예시 (이벤트 사용)

// MyHealthComponent.h
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChangedSignature, float, Health);
 
UCLASS()
class MYPROJECT_API UMyHealthComponent : public UActorComponent
{
    GENERATED_BODY()
 
public:
    UPROPERTY(BlueprintAssignable, Category="Health")
    FOnHealthChangedSignature OnHealthChanged;
 
    UFUNCTION(BlueprintCallable, Category="Health")
    void TakeDamage(float DamageAmount);
 
private:
    UPROPERTY(EditAnywhere, Category="Health")
    float Health;
};
 
// MyHealthComponent.cpp
void UMyHealthComponent::TakeDamage(float DamageAmount)
{
    Health -= DamageAmount;
    OnHealthChanged.Broadcast(Health);
}
 
// MyActor.cpp
void AMyActor::BeginPlay()
{
    Super::BeginPlay();
    
    UMyHealthComponent* HealthComp = FindComponentByClass<UMyHealthComponent>();
    if (HealthComp)
    {
        HealthComp->OnHealthChanged.AddDynamic(this, &AMyActor::OnHealthChanged);
    }
}
 
void AMyActor::OnHealthChanged(float NewHealth)
{
    UE_LOG(LogTemp, Log, TEXT("Health changed to: %f"), NewHealth);
}

컴포넌트 기반 설계의 장점

  1. 모듈화 : 기능을 독립적인 단위로 분리
  2. 재사용성 : 여러 액터에서 동일한 컴포넌트 사용 가능
  3. 유연성 : 런타임에 컴포넌트 추가 / 제거 가능
  4. 확장성 : 기존 액터의 기능을 쉽게 확장 가능
  5. 협업 용이성 : 팀원 간 작업 분담이 쉬움

효과적인 컴포넌트 구조 설계

  1. 단일 책임 원칙 적용 : 각 컴포넌트는 하나의 주요 기능에 집중
  2. 인터페이스 활용 : 컴포넌트 간 결합도 낮추기
  3. 설정 가능성 고려 : UPROPERTY를 활용하여 에디터에서 쉽게 설정 가능하도록 구현
  4. 재사용성 고려 : 범용적으로 사용할 수 있는 컴포넌트 설계

성능 최적화를 위한 컴포넌트 사용 전략

  1. 필요한 경우에만 Tick 활성화
PrimaryComponentTick.bCanEverTick = false;
  1. 컴포넌트 생성 시점 최적화
  • 생성자에서 필수 컴포넌트만 생성
  • 필요시 런타임에 동적으로 컴포넌트 생성
  1. 컴포넌트 개수 관리
  • 불필요한 컴포넌트 제거
  • 기능 통합을 통한 컴포넌트 수 최소화
  1. 캐싱 활용
  • 자주 사용하는 컴포넌트 참조 캐싱

모듈화와 재사용성 향상 방안

  1. 제네릭 컴포넌트 설계
  • 템플릿 활용으로 다양한 데이터 타입 지원
  1. 컴포넌트 상속 구조 활용
  • 공통 기능을 기본 클래스로 추출
  1. 컴포지션 패턴 적용
  • 여러 작은 컴포넌트를 조합하여 복잡한 기능 구현
  1. 에디터 지원 강화
  • 커스텀 에디터 UI를 통한 컴포넌트 설정 용이성 제공

 예시 (제네릭 컴포넌트)

template<typename T>
class TInventoryComponent : public UActorComponent
{
    GENERATED_BODY()
 
private:
    TArray<T> Items;
 
public:
    void AddItem(const T& Item);
    T GetItem(int32 Index) const;
};
 
// 사용 예
UInventoryComponent<FWeaponData>* WeaponInventory;
UInventoryComponent<FItemData>* ItemInventory;

 컴포넌트 시스템은 언리얼 엔진에서 강력하고 유연한 게임 객체 설계를 가능하게 합니다. 적절한 컴포넌트 설계와 활용을 통해 코드의 재사용성을 높이고, 유지보수를 용이하게 하며, 확장 가능한 게임 시스템을 구축할 수 있습니다.

 컴포넌트의 독립성을 유지하면서도 효과적인 상호작용 메커니즘을 구현하는 것이 중요합니다.

 또한, 성능을 고려한 최적화 전략을 적용하여 게임의 전반적인 효율성을 높일 수 있습니다. 지속적인 리팩토링과 모듈화 노력을 통해 프로젝트의 복잡성을 관리하고, 팀의 생산성을 향상시킬 수 있습니다.