Transformation 없음 (위치, 회전, Scale)
따라서 공간 상 존재하지도 않음
그래서 생성 후 루트나 컴포넌트에 붙여줄 필요 없음
루트 컴포넌트도 될 수 없고, 렌더링, 충돌처리도 불가능
단순 로직만 처리하는 기능
대신 아주 가벼움
예) Health, Inventory가 상속받아 사용
액터 컴포넌트를 상속받음
Transformation 가짐 (위치, 회전, 크기)
실제로 공간에 존재
다른 컴포넌트에 부착하거나(SetupAttachment), 루트로 설정 가능
대신 상대적으로 무거움
예) Camera, Audio Component가 상속받아 사용
Scene 컴포넌트와 여러 클래스를 상속받은 컴포넌트
Scene Component에 더불어 렌더링, 충돌 처리까지 가능한 컴포넌트
예) 메시, 캡슐이 상속받음
AMyCharacter::AMyCharacter()
{
// 논리컴포넌트(액터컴포넌트)는 생성만
HealthComponent = CreateDefaultSubobject<UHealthComponent>(TEXT("Health"));
// 씬컴포넌트는 생성 후 부착 필요
SpringArmComponent= CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
SpringArmComponent->SetupAttachment(RootComponent);
}
void AMyCharacter::AddAbility(TSubclassOf<UAbilityComponent> AbilityClass)
{
if (!AbilityClass) return;
// 생성
UAbilityComponent* NewAbility = NewObject<UAbilityComponent>(this, AbilityClass);
// 씬 컴포넌트면 AttachToComponent 해야함 (SetupAttachment는 생성자 전용함수)
if (NewAbility)
{
// 월드에 등록 및 초기화, Tick 활성화
NewAbility->RegisterComponent();
}
}
void AMyCharacter::DestroyAbility(UActorComponent* ComponentToDestroy)
{
if (ComponentToDestroy)
{
// 1. 등록 해제 및 월드에서 제거
ComponentToDestroy->UnregisterComponent();
// 2. 물리/렌더링 자원 해제 및 파괴 마킹
ComponentToDestroy->DestroyComponent();
}
}
ULyraPawnExtensionComponent 하나만 가짐ALyraCharacter::ALyraCharacter()
{
// 단 하나의 Component
PawnComponent = CreateDefaultSubobject<ULyraPawnExtensionComponent>("PawnExt");
}
UCLASS()
class ULyraPawnData : public UPrimaryDataAsset
{
UPROPERTY(EditDefaultsOnly)
TArray<TSubclassOf<UActorComponent>> Components; // 필요한 액터 컴포넌트들
UPROPERTY(EditDefaultsOnly)
TArray<TSubclassOf<ULyraAbilitySet>> AbilitySets; // 필요한 GAS들
};
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 능력 세트 부여
}
}
ULyraPawnData* PaladinData = LoadObject<ULyraPawnData>("Paladin");
void TransformToPaladin(ALyraCharacter* Character)
{
auto* PawnExt = Character->GetPawnExtension();
PawnExt->RemoveAllComponents();
PawnExt->SetupPawn(PaladinData);
}
데이터 에셋에서 액터 컴포넌트를 관리하기 때문에, 디자이너도 쉽게 수정할 수 있다
자동 동기화도 해줌
하나하나 생성해주었던 일이 간소화됨
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할 수 있도록 함
언리얼의 경우 이미 핵심클래스들이 가상 소멸자이므로 상관 없음
생성자의 경우 자식 객체가 생성될 때, 자동으로 부모생성자를 먼저 호출하고 자식 생성자가 호출됨
소멸자의 경우 자식 객체가 제거될 때, 자동으로 자식 소멸자 먼저 호출하고 부모 소멸자가 호출됨
Parent* p = new Child(); // 부모(Parent), 자식(Child) 생성자 순으로
delete p; // 자식(Parent), 부모(Parent의 부모) 소멸자 순으로
p가 현재 Parent 자료형이므로 소멸될 때는 Parent의 소멸자가 먼저 불리게 되는거고 그래서 가상소멸자로 선언해야했던 것
생성자의 경우는 명확하게 자신의 타입을 아니까 가상생성자 자체가 필요 없음.
그리고 생성될 때 가상함수테이블도 없기 때문에 사용도 못 함