본문으로 건너뛰기

안동민 개발노트

본문 시작

컴포넌트 시스템 이해와 활용

액터 기능을 컴포넌트로 조합하는 원리를 이해하고 C++에서 생성·부착·초기화하는 방법을 익힙니다.

이전 절에서 입력이 PawnCharacter 동작으로 이어지는 흐름을 봤다면, 이제 누가 실제 동작을 수행하는가를 분해해야 합니다.

정답은 컴포넌트(Component)입니다.

언리얼 엔진은 기능을 컴포넌트 단위로 조립하는 구조를 중심으로 동작하며, 이 구조를 이해해야 확장성과 유지보수성이 높은 게임플레이 코드를 만들 수 있습니다.

Actor는 위치와 기능을 컴포넌트 계층으로 조립한다

움직임, 충돌, 카메라, 메시, 사운드는 Actor 자체가 아니라 목적에 맞는 Component로 붙인다.

  1. Actor logic

    UActorComponent Transform이 필요 없는 체력, 상태, 인벤토리 같은 순수 로직을 맡긴다.

  2. Scene transform

    USceneComponent 위치, 회전, 스케일과 부모-자식 계층이 필요한 기능을 붙인다.

  3. Specialized

    Mesh / Camera 렌더링, 충돌, 카메라 암처럼 엔진이 제공하는 구체 컴포넌트를 선택한다.

필요한 기능선택대표 예
데이터와 Tick만 필요UActorComponentHealth, Inventory, Ability
공간 위치가 필요USceneComponentSpringArm, SceneRoot
렌더링/충돌이 필요특화 ComponentStaticMesh, Capsule, Camera

컴포넌트 시스템이란 무엇인가요?

언리얼 엔진의 컴포넌트 시스템은 액터(Actor)의 기능을 모듈화하고 재사용성을 높이기 위한 설계 패턴입니다.

쉽게 말해, 하나의 액터가 가진 모든 복잡한 기능을 액터 자체에 직접 구현하는 대신, 각각의 기능을 담당하는 작은 부품들을 만들고, 필요에 따라 이 부품들을 액터에 붙였다 떼었다 하면서 기능을 조립하는 방식입니다.

예를 들어, 플레이어 캐릭터 액터를 생각해보세요.

이 캐릭터는 3D 모델을 가지고, 움직이고, 점프하고, 충돌을 감지하며, 소리를 낼 수 있어야 합니다.

이 모든 기능을 캐릭터 클래스 하나에 모두 구현한다면 코드가 매우 복잡해지고, 나중에 차량이나 드론처럼 다른 형태의 액터를 만들 때 해당 기능을 재사용하기 어려워질 겁니다.

하지만 컴포넌트 시스템을 사용하면 다음과 같이 나눌 수 있습니다.

  • 시각적 표현: UStaticMeshComponent (정적 메시) 또는 USkeletalMeshComponent (스켈레탈 메시)
  • 이동: UCharacterMovementComponent (캐릭터 전용 이동) 또는 UFloatingPawnMovement (범용 이동)
  • 충돌 감지: UCapsuleComponent, UBoxComponent, USphereComponent
  • 사운드 재생: UAudioComponent
  • 파티클 효과: UParticleSystemComponent
  • 카메라 시점: UCameraComponent, USpringArmComponent

각 컴포넌트는 특정한 하나의 기능을 담당하며, 이 컴포넌트들을 원하는 액터에 부착함으로써 해당 액터가 필요한 기능을 가지게 됩니다.


컴포넌트 분류와 역할

로직 컴포넌트와 공간 컴포넌트 분리

컴포넌트 선택은 기능이 3D 위치를 가져야 하는지로 갈립니다. 데이터만 있으면 ActorComponent, 부착과 변환이 필요하면 SceneComponent입니다.

  1. 컴포넌트 선택표
  2. 기준 ActorComponent

    SceneComponent 특화 계열

  3. Transform 없음 위치, 회전, 스케일

    보유 시각, 충돌, 카메라 위치에 사용

  4. 대표 예 Health, Inventory, Stat

    Root, SpringArm StaticMesh, Camera, Capsule

  5. 실패 신호 위치

    필요한데 붙일 수 없음 Root가 없어 배치 기준이 흔들림 시각과 로직이 한 클래스에 과밀함

모든 컴포넌트의 최상위 부모는 UActorComponent입니다.

UActorComponent 계열은 로직과 데이터를 가질 수 있지만, 3D 월드의 위치/회전/크기 같은 변환(Transform) 정보는 직접 가지지 않습니다.

예를 들어 체력 관리용 UHealthComponent는 공간 좌표가 필요 없으므로 UActorComponent로 충분합니다.

반대로 USceneComponentUActorComponent를 상속하면서 3D 변환 정보를 가질 수 있는 타입입니다.

따라서 USceneComponent 계열은 다른 컴포넌트나 액터에 붙어 특정 위치에 존재할 수 있습니다.

UStaticMeshComponent, UCapsuleComponent, USpringArmComponent가 대표적인 예입니다.

  • UActorComponent
    • 3D 변환 정보를 가지지 않음.
    • 주로 순수 로직이나 데이터 관리에 사용.
    • 예: UHealthComponent, UInventoryComponent
  • USceneComponent
    • UActorComponent를 상속받고, 3D 변환 정보를 가짐.
    • 3D 월드 내에 시각적으로 존재하거나 특정 위치에 영향을 미칠 때 사용.
    • 계층 구조(Hierarchy)를 형성할 수 있음 (부모-자식 관계).
    • 예: UStaticMeshComponent, UCapsuleComponent, UCameraComponent, USpringArmComponent

컴포넌트 활용: C++에서 추가하고 설정하기

이제 실제 C++ 코드를 통해 액터에 컴포넌트를 추가하고 설정하는 방법을 알아보겠습니다.

이전 절에서 만든 AMyCharacter 클래스를 확장하여 카메라를 추가해 봅시다.

컴포넌트 선언 (헤더 파일)

AMyCharacter.h 파일에 USpringArmComponentUCameraComponent를 위한 포인터 변수를 선언합니다.

AMyCharacter.h
// AMyCharacter.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputAction.h"
#include "MyCharacter.generated.h"

// 미리 선언 (Forward Declarations) - include보다 가볍습니다.
class UInputMappingContext;
class UInputAction;
class USpringArmComponent; // 스프링 암 컴포넌트 추가
class UCameraComponent;    // 카메라 컴포넌트 추가

UCLASS()
class MYFIRSTCPPPROJECT_API AMyCharacter : public ACharacter
{
    GENERATED_BODY()

public:
    AMyCharacter();

protected:
    virtual void BeginPlay() override;

public:
    virtual void Tick(float DeltaTime) override;

    virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

protected:
    // ... 기존 입력 관련 UPROPERTY 변수들 ...

    // 새로운 컴포넌트들을 UPROPERTY로 선언
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
    USpringArmComponent* CameraBoom; // 카메라를 팔처럼 지지하는 컴포넌트

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
    UCameraComponent* FollowCamera; // 실제 카메라 렌더링을 담당하는 컴포넌트

    // ... 기존 입력 액션 바인딩 함수들 ...
};

#include 지시문은 USpringArmComponent.hUCameraComponent.h를 직접 추가하지 않고, 클래스 정의만 미리 알려주는 class 키워드를 사용했습니다.

이는 컴파일 시간을 줄이는 데 도움이 되며, .cpp 파일에서 실제로 컴포넌트의 멤버 함수를 호출할 때만 해당 헤더를 포함하면 됩니다.

컴포넌트 생성 및 부착 (소스 파일 - 생성자)

AMyCharacter.cpp 파일의 생성자에서 컴포넌트를 생성하고, 액터의 계층 구조에 맞게 부착(Attach)합니다.

AMyCharacter.cpp
// AMyCharacter.cpp
#include "MyCharacter.h"
#include "EnhancedInputSubsystems.h"
#include "EnhancedInputComponent.h"
#include "GameFramework/SpringArmComponent.h" // UPROPERTY로 선언한 컴포넌트의 헤더를 여기서 포함
#include "Camera/CameraComponent.h"        // UPROPERTY로 선언한 컴포넌트의 헤더를 여기서 포함

AMyCharacter::AMyCharacter()
{
    PrimaryActorTick.bCanEverTick = true;

    // CharacterMovementComponent 설정 (ACharacter는 기본으로 가짐)
    GetCharacterMovement()->bOrientRotationToMovement = true;
    bUseControllerRotationYaw = false;

    // 캡슐 컴포넌트 (ACharacter의 RootComponent)가 YAW 회전을 따르도록 설정
    // 이렇게 해야 카메라 회전과 캐릭터 회전이 일치합니다.
    GetCharacterMovement()->bUseControllerDesiredRotation = true;
    GetCharacterMovement()->bOrientRotationToMovement = false;

    // 1. CameraBoom (USpringArmComponent) 생성 및 부착
    CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
    CameraBoom->SetupAttachment(RootComponent); // 캐릭터의 루트 컴포넌트 (CapsuleComponent)에 부착
    CameraBoom->TargetArmLength = 300.0f; // 카메라와 캐릭터 사이의 거리
    CameraBoom->bUsePawnControlRotation = true; // 컨트롤러의 회전(Yaw)을 따라가도록 설정
    CameraBoom->bInheritPitch = true; // 컨트롤러의 피치(Pitch) 회전을 상속받도록 설정
    CameraBoom->bInheritYaw = true;   // 컨트롤러의 야(Yaw) 회전을 상속받도록 설정
    CameraBoom->bInheritRoll = true;  // 컨트롤러의 롤(Roll) 회전을 상속받도록 설정
    CameraBoom->bDoCollisionTest = true; // 카메라와 장애물 충돌 시 카메라가 당겨지도록 설정

    // 2. FollowCamera (UCameraComponent) 생성 및 부착
    FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
    FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // CameraBoom에 부착
    FollowCamera->bUsePawnControlRotation = false; // 카메라 자체는 컨트롤러 회전을 따르지 않음 (스프링암이 따르므로)

    // ... 기존 변수 기본값 설정 ...
}

// ... BeginPlay(), Tick(), SetupPlayerInputComponent() 등 기존 함수들 ...
설명
  • CreateDefaultSubobject<T>(TEXT("Name")): 이 함수는 컴포넌트를 생성할 때 사용합니다. T는 생성할 컴포넌트의 타입이고, TEXT("Name")은 에디터에서 보일 컴포넌트의 이름입니다. 이 함수는 생성자에서만 호출되어야 합니다.
  • SetupAttachment(ParentComponent, SocketName): 생성된 컴포넌트를 다른 컴포넌트(부모)에 부착하여 계층 구조를 만듭니다.
    • CameraBoom->SetupAttachment(RootComponent);: CameraBoomAMyCharacterRootComponent (기본적으로 UCapsuleComponent)에 부착합니다. 이렇게 하면 캐릭터가 움직이면 카메라 붐도 함께 움직입니다.
    • FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);: FollowCameraCameraBoom에 부착합니다. USpringArmComponent::SocketName은 스프링 암 컴포넌트가 카메라를 부착할 수 있는 기본 소켓 이름입니다.
  • 컴포넌트 속성 설정: 생성된 컴포넌트의 포인터를 통해 다양한 속성을 설정할 수 있습니다. 예를 들어 TargetArmLength는 카메라와 타겟 사이의 거리를 조절합니다.
  • bUseControllerRotationYawbOrientRotationToMovement: ACharacter의 기본 회전 설정입니다.
    • bUseControllerRotationYaw = false;로 설정하고 GetCharacterMovement()->bOrientRotationToMovement = false;로 설정하면 캐릭터 자체가 이동 방향으로 회전하는 것을 멈춥니다.
    • GetCharacterMovement()->bUseControllerDesiredRotation = true;는 캐릭터가 APlayerController의 회전 값을 따라가도록 합니다. 이는 CameraBoom이 컨트롤러의 회전을 따를 때 캐릭터의 방향과 카메라의 방향을 일치시키는 데 중요합니다.
    • bUsePawnControlRotationCameraBoomAPlayerControllerControlRotation (플레이어의 시점 회전)을 따르도록 합니다. 이는 마우스나 스틱으로 시점을 움직일 때 카메라가 함께 회전하게 만듭니다.

아래 다이어그램은 새 컴포넌트를 만들 때 어떤 타입을 선택하고, 어디에서 생성·부착·초기화 책임을 나눌지를 한 번에 정리한 것입니다.

컴포넌트는 위치 필요성, 부착 계층, 엔진 전용 기능으로 고른다

어떤 책임을 부품으로 뺄지 정하면 생성자와 초기화 위치도 자연스럽게 정해진다.

선택지맞는 책임구현 위치
UActorComponent위치가 없는 체력, 인벤토리, 상태 관리생성자에서 만들고 BeginPlay에서 런타임 참조를 묶는다
USceneComponentTransform과 부착 계층이 필요한 기능SetupAttachment와 Root 연결이 의미를 가진다
Specialized ComponentMesh, Camera, SpringArm 같은 엔진 제공 기능전용 옵션과 충돌/렌더/카메라 설정을 쓴다
Tick매 프레임 값이 바뀌는 기능만 켠다필요 없으면 꺼서 비용과 순서를 줄인다

플레이어 컨트롤러에서 회전 설정 (C++)

카메라가 컨트롤러 시점 회전을 따르도록 하려면 APlayerControllerbUseControllerRotationYaw, bUseControllerRotationPitch, bUseControllerRotationRoll을 활성화해야 합니다.

기본 APlayerController를 쓰는 게임 모드에서는 이미 설정돼 있는 경우가 많지만, 커스텀 APlayerController를 사용한다면 직접 지정해야 합니다.

AMyPlayerController.h (새로 만들거나 기존 PlayerController 클래스)
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "MyPlayerController.generated.h"

UCLASS()
class MYFIRSTCPPPROJECT_API AMyPlayerController : public APlayerController
{
    GENERATED_BODY()

public:
    AMyPlayerController();

protected:
    virtual void BeginPlay() override;
};
AMyPlayerController.cpp
#include "MyPlayerController.h"

AMyPlayerController::AMyPlayerController()
{
    // 컨트롤러가 Pawn의 Yaw, Pitch, Roll 회전을 제어하도록 설정
    bUseControllerRotationPitch = true;
    bUseControllerRotationYaw = true;
    bUseControllerRotationRoll = true; // 일반적으로 Roll은 사용하지 않지만 필요에 따라
}

void AMyPlayerController::BeginPlay()
{
    Super::BeginPlay();
    // ... 추가 초기화 로직 ...
}

그리고 GameMode 블루프린트 (BP_MyGameMode)에서 Player Controller Class를 방금 만든 BP_MyPlayerController (또는 여러분의 C++ AMyPlayerController)로 설정해야 합니다.

카메라가 기대한 방향으로 돌지 않을 때는 Controller, CharacterMovement, SpringArm, Camera가 어느 회전을 따르는지부터 나눠서 점검합니다.

카메라 회전은 Controller, Movement, SpringArm, Camera 책임을 분리한다

회전이 두 번 적용되는 느낌이 들면 카메라보다 회전 소유자부터 확인한다.

대상맡는 회전점검 기준
PlayerControllerLook 입력으로 ControlRotation을 갱신한다입력 기준 시점이 여기서 만들어진다
CharacterMovement캐릭터가 이동 방향을 따를지 컨트롤러 방향을 따를지 정한다DesiredRotation과 OrientToMovement가 충돌하지 않는다
SpringArmbUsePawnControlRotation으로 컨트롤러 회전을 상속한다벽 충돌과 카메라 거리도 함께 본다
Camera보통 SpringArm 끝에 붙어 최종 시점만 제공한다Camera 자체 회전 추적은 대개 꺼 둔다

고정 카메라 컴포넌트처럼 생성자에서 만드는 기본 구조와, 플레이 중 붙었다 떨어지는 런타임 컴포넌트는 생성·등록·정리 책임이 다릅니다.

기본 컴포넌트와 런타임 컴포넌트는 생성 위치가 다르다

카메라처럼 클래스의 고정 구조라면 생성자에서 만들고, 파워업이나 임시 장비처럼 플레이 중 붙었다 떨어지는 기능은 런타임 등록 절차를 따로 관리합니다.

  1. CreateDefaultSubobject
    두 생성 경로의 차이

    CreateDefaultSubobject 생성자에서 호출하고 SetupAttachment() 로 계층을 고정합니다. Blueprint의 Components 패널에서 기본 구성으로 보입니다. NewObject 런타임에 만들고 소유자, 부착, 등록, 해제를 직접 챙깁니다. 임시 효과나 장비 시스템에 어울립니다.

  2. CreateDefaultSubobject
    클래스 기본 구조

    CreateDefaultSubobject 생성자에서 호출하고 SetupAttachment() 로 계층을 고정합니다. Blueprint의 Components 패널에서 기본 구성으로 보입니다.

  3. NewObject
    플레이 중 추가 기능

    NewObject 런타임에 만들고 소유자, 부착, 등록, 해제를 직접 챙깁니다. 임시 효과나 장비 시스템에 어울립니다.

  4. 런타임 컴포넌트 점검표

    Outer와 소유자 액터 수명과 함께 정리되도록 소유 관계 지정 임시 객체가 월드에 남지 않게 관리 부착과 등록 SceneComponent라면 부모와 transform 규칙 설정 생성 후 월드에 쓰려면 등록 단계 확인 해제 시점 파워업 종료, 장비 교체, EndPlay에서 정리 남은 참조와 delegate 바인딩을 함께 끊기

컴파일 및 테스트

코드를 컴파일(Ctrl + Shift + B 또는 언리얼 에디터의 컴파일 버튼)하고, 언리얼 에디터에서 Play 버튼을 누릅니다.

이제 캐릭터 뒤에 카메라가 따라오며, 마우스를 움직이면 캐릭터의 시점(그리고 카메라의 시점)이 함께 움직이는 것을 확인할 수 있을 겁니다!


컴포넌트 시스템의 장점

  • 재사용성: 한 번 만든 컴포넌트는 여러 액터에 부착하여 재사용할 수 있습니다. 예를 들어, UHealthComponent는 플레이어, 적, 심지어 파괴 가능한 환경 오브젝트에도 붙일 수 있습니다.
  • 모듈성: 각 컴포넌트는 독립적인 기능을 수행하므로, 특정 기능을 수정하거나 추가할 때 해당 컴포넌트만 수정하면 됩니다. 이는 코드의 유지보수를 쉽게 만듭니다.
  • 유연성: 런타임에 액터에 컴포넌트를 동적으로 추가하거나 제거할 수 있습니다. 예를 들어, 파워업 아이템을 획득하면 UWeaponComponent를 추가하고, 특정 상태가 되면 제거하는 식으로 활용할 수 있습니다.
  • 협업 용이: 프로그래머는 컴포넌트를 C++로 구현하고, 디자이너는 블루프린트에서 이 컴포넌트를 조립하고 속성을 설정하여 게임플레이를 빠르게 만들 수 있습니다.

컴포넌트 시스템을 사용하면 액터 기능을 역할별로 분리하고 재사용할 수 있습니다.

기본 컴포넌트의 책임을 먼저 파악한 뒤, 반복되는 기능은 커스텀 컴포넌트로 분리합니다.

생성자에서 고정하는 기본 컴포넌트와 플레이 중 붙이는 런타임 컴포넌트의 책임 차이를 정리합니다.

Scene Component 조립은 선언, 생성, 부착, 조정 범위로 닫는다

상속을 늘리기보다 Actor에 필요한 부품을 선언하고 생성자에서 안정적으로 붙이는 방식이 기본이다.

단계핵심 작업실수 방지
DeclareUPROPERTY로 컴포넌트 포인터를 선언한다GC와 에디터 노출 기준을 함께 잡는다
CreateCreateDefaultSubobject로 이름 있는 기본 컴포넌트를 만든다생성 순서와 이름을 안정적으로 유지한다
AttachSetupAttachment로 Root, SpringArm, Camera 계층을 만든다Scene 계열 이상에서만 부착이 의미 있다
Tune회전, 충돌, 카메라 거리처럼 반복 조정할 값만 노출한다노출 범위를 줄여 실수 가능성을 낮춘다