TIL_093 : 언리얼 컴포넌트 (Lyra), 부모 생성자/소멸자

김펭귄·2026년 1월 9일

Today What I Learned (TIL)

목록 보기
93/111

1. Actor vs Scene vs Primitive Component

1.1. Actor Component

  • Transformation 없음 (위치, 회전, Scale)

  • 따라서 공간 상 존재하지도 않음

  • 그래서 생성 후 루트나 컴포넌트에 붙여줄 필요 없음

  • 루트 컴포넌트도 될 수 없고, 렌더링, 충돌처리도 불가능

  • 단순 로직만 처리하는 기능

  • 대신 아주 가벼움

  • 예) Health, Inventory가 상속받아 사용

1.2. Scene Component

  • 액터 컴포넌트를 상속받음

  • Transformation 가짐 (위치, 회전, 크기)

  • 실제로 공간에 존재

  • 다른 컴포넌트에 부착하거나(SetupAttachment), 루트로 설정 가능

  • 대신 상대적으로 무거움

  • 예) Camera, Audio Component가 상속받아 사용

1.3. Primitive Component

  • Scene 컴포넌트와 여러 클래스를 상속받은 컴포넌트

  • Scene Component에 더불어 렌더링, 충돌 처리까지 가능한 컴포넌트

  • 예) 메시, 캡슐이 상속받음

2. Component 생성/제거

2.1. 생성자에서 생성

AMyCharacter::AMyCharacter()
{
  // 논리컴포넌트(액터컴포넌트)는 생성만
  HealthComponent = CreateDefaultSubobject<UHealthComponent>(TEXT("Health"));

  // 씬컴포넌트는 생성 후 부착 필요
  SpringArmComponent= CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
  SpringArmComponent->SetupAttachment(RootComponent);
}

2.2. 런타임 중 생성

void AMyCharacter::AddAbility(TSubclassOf<UAbilityComponent> AbilityClass)
{
    if (!AbilityClass) return;
	
    // 생성
    UAbilityComponent* NewAbility = NewObject<UAbilityComponent>(this, AbilityClass);
    // 씬 컴포넌트면 AttachToComponent 해야함 (SetupAttachment는 생성자 전용함수)

    if (NewAbility)
    {
    	// 월드에 등록 및 초기화, Tick 활성화
        NewAbility->RegisterComponent();
    }
}

2.3. 런타임 중 제거

void AMyCharacter::DestroyAbility(UActorComponent* ComponentToDestroy)
{
    if (ComponentToDestroy)
    {
        // 1. 등록 해제 및 월드에서 제거
        ComponentToDestroy->UnregisterComponent();

        // 2. 물리/렌더링 자원 해제 및 파괴 마킹
        ComponentToDestroy->DestroyComponent();
    }
}

3. Lyra에서의 컴포넌트 설계

3.1. 하나의 컴포넌트

  • 캐릭터 생성시, 생성자에서 카메라, 메시 등을 하나하나 생성하는 것이 아니라 ULyraPawnExtensionComponent 하나만 가짐
ALyraCharacter::ALyraCharacter()
{
    // 단 하나의 Component
    PawnComponent = CreateDefaultSubobject<ULyraPawnExtensionComponent>("PawnExt");
}

3.1. Pawn Data

  • 그리고 각 캐릭터마다 가져야할 컴포넌트들을 하나의 데이터 에셋에 저장
UCLASS()
class ULyraPawnData : public UPrimaryDataAsset
{
    UPROPERTY(EditDefaultsOnly)
    TArray<TSubclassOf<UActorComponent>> Components;	// 필요한 액터 컴포넌트들

    UPROPERTY(EditDefaultsOnly)
    TArray<TSubclassOf<ULyraAbilitySet>> AbilitySets;	// 필요한 GAS들
};

3.2. 런타임에 컴포넌트 생성

  • 그리고 런타임에 설계도를 보고 생성함
void ULyraPawnExtensionComponent::SetupPawn(const ULyraPawnData* PawnData)
{
    for (TSubclassOf<UActorComponent> CompClass : PawnData->Components)
    {
        UActorComponent* NewComp = NewObject<UActorComponent>(GetOwner(), CompClass);
        NewComp->RegisterComponentWithWorld(GetWorld());
    }

    for (TSubclassOf<ULyraAbilitySet> AbilitySet : PawnData->AbilitySets)
    {
        GrantAbilitySet(AbilitySet);  // GAS 능력 세트 부여
    }
}

3.3. 캐릭터 변경 시

  • 컴포넌트 다 제거하고, 하나씩 추가해줄 필요 없이 마찬가지로 데이터에셋만 가지고 한 번에 추가가 가능함
ULyraPawnData* PaladinData = LoadObject<ULyraPawnData>("Paladin");

void TransformToPaladin(ALyraCharacter* Character)
{
    auto* PawnExt = Character->GetPawnExtension();

    PawnExt->RemoveAllComponents();
    PawnExt->SetupPawn(PaladinData);
}

3.4. 장점

  • 데이터 에셋에서 액터 컴포넌트를 관리하기 때문에, 디자이너도 쉽게 수정할 수 있다

  • 자동 동기화도 해줌

  • 하나하나 생성해주었던 일이 간소화됨

4. 가상 소멸자

  • 클래스를 설계할 때, 소멸자는 virtual키워드를 사용하는 것이 좋음
// 잘못된 사례
class Parent 
{
public:
    ~Parent() { /* ... */ }	// virtual로 사용 안 함
};

class Child : public Parent 
{
    int* data;
public:
    Child() { data = new int[100]; } // 동적 할당
    ~Child() { delete[] data; }
};

int main() 
{
    Parent* p = new Child(); // 업캐스팅
    delete p; // 문제 발생
}
  • p를 제거했을 때, p의 소멸자가 불리는 상황

  • 하지만 위 코드처럼 가상함수로 소멸자를 선언하지 않으면, override되지 않아 Parent의 소멸자가 호출됨.
    따라서 data는 사라지지 않고 메모리에 남아있음

  • 이런 상황을 방지하기 위해 가상함수로 소멸자를 선언해서 자식 클래스에서 override할 수 있도록 함

  • 언리얼의 경우 이미 핵심클래스들이 가상 소멸자이므로 상관 없음

5. 부모 생성자, 소멸자 호출 시점

  • 생성자의 경우 자식 객체가 생성될 때, 자동으로 부모생성자를 먼저 호출하고 자식 생성자가 호출됨

  • 소멸자의 경우 자식 객체가 제거될 때, 자동으로 자식 소멸자 먼저 호출하고 부모 소멸자가 호출됨

Parent* p = new Child(); // 부모(Parent), 자식(Child) 생성자 순으로 
delete p; 				 // 자식(Parent), 부모(Parent의 부모) 소멸자 순으로
  • p가 현재 Parent 자료형이므로 소멸될 때는 Parent의 소멸자가 먼저 불리게 되는거고 그래서 가상소멸자로 선언해야했던 것

  • 생성자의 경우는 명확하게 자신의 타입을 아니까 가상생성자 자체가 필요 없음.
    그리고 생성될 때 가상함수테이블도 없기 때문에 사용도 못 함

profile
반갑습니다

0개의 댓글