월드에 존재하면서, 정해진 기능에 따라 자신의 역할을 수행하는 기본 객체
월드에서 존재한다는 이야기는 3차원 가상 공간에서 항상 자신의 위치, 방향과 크기가 지정되어 있다는 뜻입니다. 일반적으로 많은 3D 소프트웨어에서는 이를 구현하기위해 위치, 회전, 스케일 정보를 묶은 트랜스폼(Transform)이라는 정보를 사용합니다.
우리가 사용하는 언리얼 엔진도 모든 액터에 트랜스폼 정보가 들어있습니다. 월드 아웃라이너에서 어떤 액터를 선택하더라도 에디터의 디테일 뷰에 항상 트랜스폼 정보가 가장 먼저 지정되어 있음을 확인할 수 있습니다.
단순하게 이야기하자면 액터는 월드 트랜스폼이 있는 언리얼 오브젝트라고 할 수 있겠습니다.
언리얼 엔진에서 액터를 상속받는 클래스는 모두 A라는 접두사를 사용하도록 설계되어 있습니다.
게임을 프로그래밍 프레임웍의 관점에서 확장해 컨텐츠 제작의 관점으로 본다면, 게임 컨텐츠는 월드에 존재하는 물체간의 상호작용이라고 할 수 있습니다. 액터는 월드에 존재하는 물체의 기본 단위이기 때문에, 게임 컨텐츠의 설계는 액터에서부터 시작한다고 할 수 있습니다. 그래서 언리얼 엔진이 액터 클래스의 정의에 U접두사를 사용하지 않고 A접두사를 사용하는 이유는, 컨텐츠 제작에 기여하는 오브젝트들을 묶어서 관리하기가 용이하기 때문입니다.
액터가 가지는 또 다른 특징은 제작자가 액터에 부여한 기능입니다. 언리얼 엔진은 액터에 기능을 부여할 수 있도록 엔진의 기능을 잘 모듈화하여 컴포넌트란 이름으로 포장을 해서 제공을 합니다. 제작자는 액터를 설계할 때 언리얼 엔진이 제공하는 컴포넌트를 골라 부착해, 액터의 기능을 지정할 수 있습니다. 물론 제작자가 컴포넌트를 직접 제작하는 것도 가능합니다.
예를 들어 무기 액터는 우선 무기의 시각적 요소를 보여줘야하는 기능이 필요합니다. 이를 위해서 언리얼 엔진이 제공하는 SkeletalMesh 컴포넌트를 사용하면 됩니다. 그리고 SkeletalMesh 컴포넌트를 액터의 움직임이나 충돌 기능을 대표하도록 루트 컴포넌트로 설정합니다.
Weapon = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("WeaponHashValue"));
RootComponent = Weapon;
CDO를 제작하는 생성자 코드에서 애셋에 관련된 정보를 불러올 때에는 ConstructorHelpers라는 특수한 클래스를 사용합니다. 이 클래스는 애셋 내 가져올 종류에 따라 ObjectFinder와 ClassFInder라는 두 API를 제공하는데, ClassFinder는 애셋의 형(Type) 정보를 가져올 때 사용되고, ObjectFinder는 애셋의 내용물을 가져올 때 사용합니다. (이는 하나의 언리얼 오브젝트가 UClass와 CDO로 나누어진다는 것을 다시 되새기면 이해가 가실 겁니다.) 우리는 애셋의 내용물을 가져와야 하므로 ObjectFinder를 사용하겠습니다.
static ConstructorHelpers::FObjectFinder<USkeletalMesh> SK_BlackKnight(TEXT("SkeletalMesh'/Game/InfinityBladeWeapons/Blade.Blade'"));
Weapon->SetSkeletalMesh(SK_BlackKnight.Object);
먼저 ConstructorHelpers 클래스는 말 그대로 생성자(Constructor)에서만 사용이 가능합니다. 즉 CDO 제작(생성자)에만 사용된다는 이야기지요. 만일 게임플레이 런타임에서 애셋을 로딩하기 위해서는 ConstructorHelper가 아닌 StaticLoadObject와 같은 다른 API를 사용하여야 합니다. 이렇게 애셋을 로딩 시점을 엔진 초기화 런타임과 게임플레이 런타임으로 구분하는 이유는, 안전을 위해서입니다.
일반적으로 컨텐츠에서 사용하는 애셋들은 엔진 초기화 시점에서 우리가 사용할 애셋이 확실히 존재하는지 검증하고 다음 단계를 진행하는 것이 안전합니다. 만일 런타임에서 애셋을 로딩하게 된다면 예기치 않은 문제들이 발생할 수 있습니다. 예를 들어 우리가 제작한 무기를 게임 플레이 런타임에서 로딩한다면, 캐릭터가 등장하고 열심히 탐험하고 몬스터 공격을 위해 칼을 빼들 때야 비로서 로딩을 시작하겠지요. 그런데 이 때 칼 애셋이 없는 경우에는 잘못해서 콘텐츠가 크래시가 일어날 수 있습니다.
불러들이는 애셋의 경로는 특별한 이유가 없다면 로딩된 이후에 변경될 일이 없을테니, 사용할 애셋들은 ConstructorHelpers 클래스를 사용해 애셋의 유무를 미리 확인하고 로딩하는 것이 좋습니다.
ConstructorHelpers 클래스를 사용해 제작한 변수에는 static 키워드를 앞에 붙였습니다. 이 구문은 스태틱 키워드를 사용하지 않아도 동작에는 무방하지만 이렇게 스태틱 변수로 선언한 이유는 리소스는 공유해서 사용하는 자원이므로 모든 언리얼 오브젝트 인스턴스마다 애셋 정보를 로딩할 필요가 없기 때문입니다. 그래서 ConstructorHelpers 로 변수를 선언할 때에는 관련 인스턴스들이 공유해서 쓰도록 로컬 스태틱 변수로 선언하는 것이 일반적입니다.