프리팹
하나의 인스턴스를 복제해서 사용하는 개념인 프리팹은 언리얼의 블루프린트 객체와 유니티의 프리팹과 비슷합니다.
유니티를 계속 써오긴 했지만 막상 프리팹이란 개념을 엔진에 넣고자 했을 때 고민을 많이 하였습니다. 고민은 다음과 같았습니다.
- 프리팹은 어떻게 만들 것인가? 게임 오브젝트 그대로? 프리팹 클래스에 프리팹 오브젝트를 상속해서? 프리팹 클래스를 독립적으로 제작?
- 프리팹의 깊은 복사를 어떻게 이루어지게 할 것인가? 일반적인 C++ 깊은 복사와 다른 의미이다. 오브젝트와 컴포넌트 간의 레퍼런스도 모두 같은 방향으로 복사를 해야하는데 어떻게 구현할 것인가?
그렇게 저는 프리팹은 리소스의 형태, 프리팹 내부엔 게임 씬에 종속되지 않고 작동하지도 않는 게임 오브젝트가 있는 클래스로 만들었고
프리팹에서 복제한 게임 오브젝트는 깊은 복사를 통해 게임 씬에 넣는 방식으로 제작하였습니다.
씬
씬은 프리팹을 구현한 뒤로 객체화를 좀 더 세부적으로 들어간 덕분에 생각보다 큰 고민을 하지 않았습니다.
따라서 프리팹과 비슷한 개념으로 이루어져있습니다.
대신 리소스의 개념이 아니고, 모든 게임 오브젝트의 최상단 노드를 담당하며, 오브젝트의 생명주기를 판단하는 역할이 프리팹과 다른점입니다.
프리팹 복제 원리
해당 복제 방식은 프리팹 뿐만 아니라 일반적인 게임 오브젝트, 씬 등에서도 사용됩니다.
XML로 파싱하는 방식과 오브젝트를 복제하는 과정은 같으나 로직이 조금씩 달라 서로 다른 코드로 동작합니다.
여러 코드 중 오브젝트를 복제 로직은 아래와 같은 순서로 돌아갑니다.
아래의 코드는 로직을 돌 때 중요한 파트들만 적어놨습니다.
클래스의 구조는 소스코드를 참조해주시면 감사하겠습니다.
CloneFactory::Clone()
대부분의 오브젝트들은 CloneFactory::Clone() 함수를 사용합니다.
해당 함수는 여러 방식으로 오버로딩 되지만
아래의 코드는 게임 오브젝트를 복제하는 코드입니다.
- Clone 함수 호출
SGameObject* SCloneFactory::Clone(SGameObject* object, SGameObject* parent) { // 부모 오브젝트가 없으면 씬의 최상위 오브젝트 노드를 가리킵니다. if(parent == nullptr) { Scene* currentScene = CORE->GetCore<SceneMgr>()->GetCurrentScene(); if(!dynamic_cast<SScene*>(currentScene)) return nullptr; parent = static_cast<SScene*>(currentScene)->GetRoot(); } std::map<SComponent*, SComponent*> clone_component; std::map<SGameObject*, SGameObject*> clone_object; // 오브젝트 생성 CloningObjects(object, clone_component, clone_object, parent); SGameObject* cloneObject_root = clone_object[object]; for(const auto& component_pair : clone_component) { SComponent* component = component_pair.second; // 레퍼런스 바인딩 component->CopyReference(component_pair.first, clone_object, clone_component); } return cloneObject_root; }
- 오브젝트 및 컴포넌트 생성
void CloningObjects(SGameObject* object, std::map<...>& clone_comp, std::map<...>& clone_obj, SGameObject* parent) { if(object == nullptr) return; //게임 오브젝트 복사 SGameObject* cloneObject = new SGameObject(object->GetName() + ""); clone_obj[object] = cloneObject; cloneObject->SetIsEnable(object->GetIsEnable()); cloneObject->SetIsPrefab(object->isPrefab(true)); cloneObject->SetResourceID(object->GetResourceID()); //게임 컴포넌트 복사 std::vector<SComponent*> target_component = object->GetComponents(); for(const auto& component : target_component) { // 트랜스폼은 항상 생성되는 컴포넌트이므로 예외처리를 해줍니다. if(dynamic_cast<TransformComponent*>(component)) { TransformComponent* transform_copy = static_cast<TransformComponent*>(cloneObject->GetTransform()); TransformInterface* transform_src = static_cast<TransformComponent*>(component); clone_comp[component] = transform_copy; transform_copy->m_position = vec3(transform_src->m_position); transform_copy->m_rotation = Quaternion(transform_src->m_rotation); transform_copy->m_scale = vec3(transform_src->m_scale); continue; } const auto& copy_comp = component->Clone(cloneObject); clone_comp[component] = copy_comp; copy_comp->SetIsEnable(component->GetIsEnable()); } auto children = object->GetChildren(); for(const auto& child : children) { CloningObjects(child, clone_comp, clone_obj, cloneObject); } parent->AddChild(cloneObject); }
- 컴포넌트의 레퍼런스 바인딩 (예시 AnimatorComponent)
void AnimatorComponent::CopyReference(SComponent* src, std::map<...> lists_obj, std::map<...> lists_comp) { if (src == nullptr) return; AnimatorComponent* convert = static_cast<AnimatorComponent*>(src); // 컴포넌트 리스트에서 같은 형식의 컴포넌트를 찾아서 바인딩 하는 매크로입니다. FIND_COMP_REFERENCE(m_entity, convert, DrawableSkinnedMeshComponent); }