언리얼 c++ CDO(Class Default Object)개념, 목적 정리
CDO(Class Defualt Object)는 언리얼 Editor나 빌드된 게임을 실행한 후에 모든 UCLASS들의 생성자를 각각 호출해서 UCLASS마다 1개씩 기본으로 생성되는 기본 인스턴스이다.
.uproject파일을 더블클릭해서 언리얼 Editor를 실행하면 위와 같은 로딩화면이 나오는데, 이 과정에서 모든 UCLASS()매크로로 선언된 언리얼 클래스들의 기본 인스턴스인 CDO가 생성이 된다.
패키징이 완료된 .exe파일을 실행할때도 처음 실행할때 CDO가 생성된다.
ABasePlayableCharacter::ABasePlayableCharacter()
:
Super::ACharacter(),
Navigation(nullptr),
AbilitySystemComponent(nullptr),
CameraBoom(nullptr),
FollowCamera(nullptr),
DefaultMappingContext(nullptr),
JumpAction(nullptr),
MoveAction(nullptr),
LookAction(nullptr),
Attack1AnimMontage(nullptr),
JumpAbilitySpecHandle(),
AutoAttackAbilitySpecHandle()
{
{//mapping context 로드
static const ConstructorHelpers::FObjectFinder<UInputMappingContext> mappingContext(TEXT("InputMappingContext'/Game/Inputs/PlayerInputMappingContext.PlayerInputMappingContext'"));
if (mappingContext.Succeeded())
DefaultMappingContext = mappingContext.Object;
else
UE_LOG(LogActor, Error, TEXT("Player MappingContext Not Loaded"));
}
{//input action들 로드
static const ConstructorHelpers::FObjectFinder<UInputAction> jumpAction(TEXT("InputAction'/Game/Inputs/InputActions/IA_Jump.IA_Jump'"));
if (jumpAction.Succeeded())
JumpAction = jumpAction.Object;
else
UE_LOG(LogActor, Error, TEXT("Jump Action Not Loaded"));
static const ConstructorHelpers::FObjectFinder<UInputAction> moveAction(TEXT("InputAction'/Game/Inputs/InputActions/IA_Move.IA_Move'"));
if (moveAction.Succeeded())
MoveAction = moveAction.Object;
else
UE_LOG(LogActor, Error, TEXT("Move Action Not Loaded"));
static const ConstructorHelpers::FObjectFinder<UInputAction> lookAction(TEXT("InputAction'/Game/Inputs/InputActions/IA_Look.IA_Look'"));
if (lookAction.Succeeded())
LookAction = lookAction.Object;
else
UE_LOG(LogActor, Error, TEXT("Look Action Not Loaded"));
}
PrimaryActorTick.bCanEverTick = true;//Tick On
{//캐릭터의 회전이 Controller의 회전에 영향받지 않게 설정
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
}
{//Character Movement Component파라미터 설정
GetCharacterMovement()->bOrientRotationToMovement = true;//이동방향으로 회전하게 설정
GetCharacterMovement()->RotationRate = FRotator(0.0f, 300.0f, 0.0f); //회전 가속도 설정
GetCharacterMovement()->JumpZVelocity = 700.f;
GetCharacterMovement()->AirControl = 0.35f;
GetCharacterMovement()->GravityScale = 2.0f;
GetCharacterMovement()->MaxWalkSpeed = 500.f;
GetCharacterMovement()->MinAnalogWalkSpeed = 20.f;
GetCharacterMovement()->BrakingDecelerationWalking = 2000.f;
}
{//Ability System설정
AbilitySystemComponent = CreateDefaultSubobject<UCredereAbilitySystemComponent>(TEXT("Ability System"));
UHealthAttributeSet* healthAttribute = CreateDefaultSubobject<UHealthAttributeSet>("Health Attribute Set");
healthAttribute->InitHealth(50.f);
healthAttribute->InitMaxHealth(100.f);
}
{//CameraBoom설정
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
CameraBoom->TargetArmLength = 700.0f;
CameraBoom->bUsePawnControlRotation = true;//Controller의 Rotation값 사용
}
{//FollowCamera설정
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
FollowCamera->bUsePawnControlRotation = false;//Camera는 CameraBoom에 종속되므로 따로 회전 x
}
}
위 코드는 언리얼 c++프로젝트를 진행하면서 작성한 BaseCharacter의 생성자이다. 생성자에서는 CreateDefaultSubobject함수를 통한 CDO의 기본 서브객체생성, FObjectFinder함수를 통한 Mesh,블루프린트 로드 등등을 진행한다. 이렇게 생성된 CDO는 SpwnActor, NewObject같은 인스턴싱 함수들의 내부에서 값 복사를 위해 사용된다. CDO의 정보를 가져오는 코드는 다음과 같다.
UObject* subObject = GetClass()->GetDefaultObject();
언리얼 엔진에서 CDO를 사용하는 목적은 크게 2가지가 있는 것 같다.
첫번째 목적은 Editor에서의 편의성에 있다.
언리얼 엔진을 실행해서 내가 만든 캐릭터들의 C++클래스를 엔진내부에서 GUI로 보면 다음과 같이 시각화가 되어있다.
만약 CDO를 만들지 않는다고 가정해보면, 초기실행 때 ArcherCharacter, WarriorCharacter등등은 인스턴스가 1개도 없는 클래스이기 때문에 저렇게 로딩된 Mesh가 시각화되어 보여질 수 없을 것이다.
두번째 목적은 로딩 효율성에 있다.
언리얼 c++코드를 작성해보면 Mesh,Animation Blueprint 등등의 애샛들은 모두 생성자에서 ConstructorHelper함수를 이용해서 로딩한다.
{//SkeletalMesh로딩 예시 코드
static const ConstructorHelpers::FObjectFinder<USkeletalMesh>
skeletalMesh(TEXT("SkeletalMesh'/Game/ParagonSparrow/Characters/Heroes/Sparrow/Meshes/Sparrow.Sparrow'"));
if (!skeletalMesh.Succeeded())
UE_LOG(LogSkeletalMesh, Error, TEXT("Archer Skeletal Mesh Not Loaded"));
GetMesh()->SetSkeletalMesh(skeletalMesh.Object);
}
이렇게 코드를 작성하면 모든 클래스들이 처음으로 실행될때 CDO를 만들면서 모든 로딩도 같이 하게 되고 나중에 실제로 인게임 인스턴스를 만들때(SpawnActor, NewObject등등)는 로딩된 애셋에 대한 포인터를 CDO에서 복사해서 재사용하는 식으로 효율을 높일수 있다.