본문 바로가기

UE4

[UE4] Actor::RootComponent

0. 서문

이 글의 주제인 RootComponent가 Actor의 멤버 변수이기에, 짧게 Actor와 연관된 부분에 대해 설명하는 것으로 시작한다.
 
UE 문서 중 "UE4 용어" 문서를 보면 Actor에 대해 다음과 같이 작성되어 있다.
"Actor, 액터란 레벨에 배치할 수 있는 오브젝트이다.
위치(Location), 회전(Rotation), 스케일(Scale)과 같은 3D 트랜스폼(Transform)을 지원하는 범용 클래스이다.
액터는 게임플레이 코드(C++ 또는 블루프린트)를 통해 스폰 및 소멸 가능하다.
C++에서의 Actor base class는 "AActor" 클래스이다"
 
또한, Actor는 여러 기능의 조합으로 그 성격과 역할이 규정되는데, 언리얼 엔진에서는 이 기능들을 "Component"라는 개념으로 모듈화시켜 놓았다. 여기에서 혼동하지 말아야 할 것이 Actor는 월드에 배치될 수 있지만, Component는 그렇지 않다는 것이다. 즉, Component는 Actor에 부착되거나 Actor의 성격, 역할을 규명할 수 있지만, 스스로 배치되거나 스폰될 수 없기에 Component들은 Actor가 아닌 UObject 들이다. (모든 Actor Component는 class name prefix가 "U"인 것을 확인할 수 있다)

 

위 설명에서 Actor를 레벨에 배치할 수 있다라는 했는데, 이는 3D 공간상에 구현할 수 있어야 한다는 의미이고, 이를 위해 (위치, 회전, 크기) 정보의 집합인 Transform 정보가 필요하다. UE4 에디터의 월드 아웃라이너에서 어느 액터를 선택하든 가장 먼저 보이는 정보가 Transform 정보인 것을 확인할 수 있다. 따라서, 거칠게 요약했을 때 Actor는 Transform을 가지는 언리얼 오브젝트라고도 표현할 수 있을 듯 하다.
 
하지만, Actor는 Transform 정보를 직접 저장하지 않고, RootComponent에 Transform 정보가 있을 경우 그 정보를 사용한다. 즉, Actor는 RootComponent의 Transform 정보에 의존하므로 올바른 RootComponent 설정이 필요하다.
 

1. UActor::RootComponent

RootComponent는 UActor의 protected 멤버 변수이며, 다음과 같이 "Runtime/Engine/Classes/GameFramework/Actor.h"에 정의되어 있다.
UCLASS(
    BlueprintType,  /* 블루프린트에서 이 C++ 클래스를 변수로 선언할 수 있음 */
    Blueprintable,  /* 블루프린트에서 이 C++ 클래스를 상속받을 수 있음 */
    config=Engine,  /* Engine.ini */
    meta=(ShortTooltip="An Actor is an object that can be placed or spawned in the world.")
)
class ENGINE_API AActor : public UObject
{
    // ...

protected:
    // Actor의 Transform(위치, 회전, 스케일)을 정의하는 충돌 요소(primitive)
    UPROPERTY(
        BlueprintGetter=K2_GetRootComponent,
        Category="Utilities|Transformation"
    )
    USceneComponent* RootComponent;

    // ...
}
 
주석 내용대로 RootComonent는 Actor의 Transform 정보를 대표하게 되며, Transform을 근거로 충돌을 처리하게 된다.
따라서, RootComponent로 지정된 Component의 정보에 움직임과 충돌 처리가 결정된다.
 
USceneComponent는 Transform 정보를 가지며, Actor 또는 USceneComponent간 Hierarchical attachment를 지원한다. 반면 rendering과 자체의 collision 기능은 가지지 않는다.
 
USceneComponent의 자식 클래스인 UPrimitiveComponent는 collision 데이터로 사용되거나 rendering geometry 유형을 생성하거나 담는 Component이다. Actor의 RootComponent로 설정되는 대부분은 UPrimitiveComponent이며, 가장 많이 쓰이는 종류로는 다음의 것들이 있다.
  • UCapsuleComponent : 주로 Pawn의 RootComponent로 사용되며, 충돌 감지에는 사용되나 rendering 정보는 가지지 않는다.
  • UStaticMeshComponent/USkeletalMeshComponent : 충돌 감지 + rendering geometry
 
RootComponent 이후의 hierarchy에 존재하는 Component들은 생성자에서 subobject 생성 이후 RootComponent에 붙이는 과정을 거쳐야 한다.
우선, RootComponent를 지정하는 예제를 살펴보고 이후 RootComponent에 부착시키는 과정에 대한 예제를 살펴보도록 하자.
 

2. RootComponent 지정 예제

Actor의 컴포넌트들은 CDO(Class Default Object)에 생성되는 것이 바람직하다.
이후 실제 레벨에서 스폰될 때 매 Actor 객체마다 컴포넌트를 셋업하는 비용을 줄일 수 있기 때문이다.
따라서, Component를 subobject로 생성하고 설정하는 과정은 Actor의 생성자에 작성하는 것이 바람직하다.
 
아래 예제에서 대표적인 RootComponent 후보인 UCapsuleComponent가 Actor의 RootComponent로 지정되는 흐름을 살펴볼 수 있다.
// header file
class TESTGAME_API ATestPawn : public APawn
{
    GENERATED_BODY()

public:    
    UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "ABPawnComponent")
    class UCapsuleComponent* Body = nullptr;
    // ...
};


// cpp file
ATestPawn::ATestPawn()
{
    // ...

    // Collision - CapsuleComponent
    Body = CreateDefaultSubobject<UCapsuleComponent>(TEXT("ATestPawnCapsuleComponent"));
    check(Body);
    Body->SetCapsuleSize(34.f, 88.f);

    // set as RootComponent
    RootComponent = Body;

    // ...
}
 

3. RootComponent에 부착하는 예제

USceneComponent는 attachment를 지원하며, Actor의 RootComponent 외 생성되는 모든 USceneComponent들은 모두 RootComponent에 attach 시켜야 한다. Pawn의 기준에서 흔히 RootComponent에 attach 되는 USceneComponent들은 다음과 같다.
  • USkeletalMeshComponent : Animated SkeletalMesh 애셋 인스턴스 생성
  • UArrowComponent : Actor가 어느 방향을 바라보는지 참조하기 위한 시각정보 제공
  • USpringArmComponent : 주로 카메라 부착시 사용되며, 카메라가 이동하면서 유연한 느낌을 내기 위해 사용
  • UCameraComponent : Actor에 부착할 카메라
 
아래 코드는 Pawn의 대표적인 Component들을 RootComponent에 attach하는 코드 흐름을 보여준다.
// header file
class TESTPAWN_API ATestPawn : public APawn
{
    GENERATED_BODY()

public:    
    UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "ABPawnComponent")
    class UCapsuleComponent* Body = nullptr;

    UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "ABPawnComponent")
    class USkeletalMeshComponent* SkelMesh = nullptr;
   
    UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "ABPawnComponent")
    class UArrowComponent* Arrow = nullptr;
 
    UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "ABPawnComponent")
    class USpringArmComponent* SpringArm = nullptr;
 
    UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "ABPawnComponent")
    class UCameraComponent* Camera = nullptr;

    // ...
};


// cpp file
ATestPawn::ATestPawn()
{
    // ...

    // Collision - CapsuleComponent
    Body = CreateDefaultSubobject<UCapsuleComponent>(TEXT("ATestPawnCapsuleComponent"));
    check(Body);
    Body->SetCapsuleSize(34.f, 88.f);

    // set as RootComponent
    RootComponent = Body;

    // Rendering - SkeletalMeshComponent
    SkelMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("ATestPawnSkeletalMeshComponent"));
    check(SkelMesh);

    SkelMesh->SetRelativeLocationAndRotation(
        FVector(0.f, 0.f, -88.f),
        FRotator(0.f, -90.f, 0.f)    // Roll
    );
    static ConstructorHelpers::FObjectFinder<USkeletalMesh> SK_Warrior(        TEXT("SkeletalMesh'/Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Cardboard.SK_CharM_Cardboard'")
    );
    SkelMesh->SetSkeletalMesh(SK_Warrior.Object);    
    // Attacth to RootComponent
    SkelMesh->SetupAttachment(RootComponent);

    // ArrowComponent
    Arrow = CreateDefaultSubobject<UArrowComponent>(TEXT("ATestPawnArrowComponent"));
    check(Arrow);
    // Attacth to RootComponent
    Arrow->SetupAttachment(RootComponent);

    // SpringArmComponent
    SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("ATestPawnSpringArmComponent"));
    check(SpringArm);

    SpringArm->TargetArmLength = 650.f;
    // Pitch
    SpringArm->SetRelativeRotation(FRotator(-45.f, 0.f, 0.f));
    // Disable inherit
    SpringArm->bInheritPitch = false;
    SpringArm->bInheritYaw = false;
    SpringArm->bInheritRoll = false;

    // Attacth to RootComponent
    SpringArm->SetupAttachment(RootComponent);

    // CameraComponent
    Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("ATestPawnCameraComponent"));
    check(Camera);
    // Attacth to SpringArmComponent
    Camera->SetupAttachment(SpringArm);
}
 
위 예제의 78라인을 보면, CameraComponent를 SpringArmCompoent에 attach 시키는 것을 볼 수 있는데, USceneComponent는 UsceneComponent간 Hierarchical attachment를 지원하기에 가능한 구조이다.
 
47라인의 ConstructorHelper::FObjectFinder에 대한 내용은 [UE4] ConstructerHelper::FObjectFinder/FClassFinder 를 참고하기 바란다.