Prefab
프리팹 생성 (CreatePrefabFromGameObject)
void GUIManager::CreatePrefabFromGameObject(const string& objectName)
{
// Scene XML 로드
wstring scenePath = L"Resource/Scene/" + SCENE.GetActiveScene()->GetSceneName() + L".xml";
tinyxml2::XMLDocument sceneDoc;
if (sceneDoc.LoadFile(Utils::ToString(scenePath).c_str()) != tinyxml2::XML_SUCCESS)
return;
// Prefab XML 문서 생성
tinyxml2::XMLDocument prefabDoc;
tinyxml2::XMLElement* prefabRoot = prefabDoc.NewElement("Prefab");
prefabDoc.InsertFirstChild(prefabRoot);
// 재귀적으로 게임 오브젝트 처리하는 savePrefab 함수 정의...
이 함수는 다음 단계로 동작합니다:
현재 씬의 XML 문서 로드:
현재 활성화된 씬의 XML 파일을 로드합니다.
씬 파일에는 게임 오브젝트의 모든 정보(변환, 컴포넌트, 계층 구조 등)가 포함되어 있습니다.
새로운 프리팹 XML 문서 생성:
<Prefab> 루트 요소를 가진 새 XML 문서를 만듭니다.
대상 게임 오브젝트 찾기:
주어진 objectName으로 현재 씬에서 게임 오브젝트를 검색합니다.
재귀적 저장 함수 구현:
function<void(shared_ptr<GameObject>)> savePrefab =
[&](shared_ptr<GameObject> gameObj)
{
// Scene XML에서 현재 GameObject 찾아서 저장
tinyxml2::XMLElement* sceneGameObj = sceneDoc.FirstChildElement("Scene")->FirstChildElement("GameObject");
while (sceneGameObj)
{
if (Utils::ToWString(sceneGameObj->Attribute("name")) == gameObj->GetName())
{
// GameObject 노드 생성 및 복사
tinyxml2::XMLElement* newObj = prefabDoc.NewElement("GameObject");
prefabRoot->InsertEndChild(newObj);
CopyXMLElement(sceneGameObj, newObj, prefabDoc);
break;
}
sceneGameObj = sceneGameObj->NextSiblingElement("GameObject");
}
for (const auto& child : gameObj->GetChildren())
{
savePrefab(child);
}
};
이 람다 함수는 다음과 같이 작동합니다:
씬 XML에서 게임 오브젝트를 찾습니다.
프리팹 XML에 새 게임 오브젝트 노드를 생성합니다.
CopyXMLElement 함수를 사용하여 씬의 게임 오브젝트 XML 요소를 프리팹으로 복사합니다.
자식 게임 오브젝트에 대해 재귀적으로 이 과정을 반복합니다.
저장 함수 호출 및 파일 저장:
루트 게임 오브젝트부터 시작하여 재귀적으로 저장 함수를 호출합니다.
완성된 프리팹 XML을 파일로 저장합니다.
프리팹 로드 (LoadPrefabToScene)
shared_ptr<GameObject> SceneManager::LoadPrefabToScene(wstring prefab)
{
// XML 파일 로드
tinyxml2::XMLDocument doc;
string pathStr = "Resource/Prefab/" + Utils::ToString(prefab) + ".xml";
doc.LoadFile(pathStr.c_str());
// ... 게임 오브젝트 생성 및 컴포넌트 설정 코드 ...
이 함수는 다음과 같은 과정을 수행합니다:
프리팹 XML 로드:
프리팹 파일경로를 구성하고 XML 문서를 로드합니다.
이름 중복 방지를 위한 매핑 생성:
map<wstring, wstring> nameMapping;
기존 씬에 이미 존재하는 오브젝트와 이름 충돌을 방지하기 위한 매핑을 생성합니다.
원본 이름과 새 이름 간의 매핑을 저장합니다.
모든 게임 오브젝트 생성:
for (tinyxml2::XMLElement* gameObjElem = root->FirstChildElement("GameObject");
gameObjElem; gameObjElem = gameObjElem->NextSiblingElement("GameObject"))
{
shared_ptr<GameObject> gameObj = make_shared<GameObject>();
// ... 오브젝트 설정 및 컴포넌트 추가 ...
}
이 루프에서는:
새 게임 오브젝트를 생성합니다.
이름 중복 확인 후 고유한 이름을 설정합니다.
이름 매핑을 업데이트합니다.
다양한 컴포넌트(Transform, Camera, Light, MeshRenderer, Collider 등)를 처리합니다.
계층 구조 복원
for (tinyxml2::XMLElement* gameObjElem = root->FirstChildElement("GameObject");
gameObjElem; gameObjElem = gameObjElem->NextSiblingElement("GameObject"))
{
if (const char* parentAttr = gameObjElem->Attribute("parent"))
{
// ... 부모-자식 관계 복원 ...
}
}
부모-자식 관계를 읽고, 새 이름 매핑을 사용하여 관계를 복원합니다
프리팹 시스템의 핵심 기술적 요소
XML을 이용한 데이터 저장과 불러오기
이 프리팹 시스템은 TinyXML2 라이브러리를 사용하여 게임 오브젝트 계층 구조를 XML로 저장
합니다:
<Prefab>
<GameObject name="cube">
<Transform posX="8.4340391" posY="1.5573728" posZ="3.6571183" rotX="0" rotY="0" rotZ="0" scaleX="1" scaleY="1" scaleZ="1"/>
<MeshRenderer useEnvironmentMap="false" fillMode="3" cullMode="3" frontCounterClockwise="false" material="SolidWhiteMaterial" mesh="Cube">
<RenderPass pass="0" depthStencilState="0"/>
</MeshRenderer>
<BoxCollider scaleX="1" scaleY="1" scaleZ="1" centerX="0" centerY="0" centerZ="0"/>
</GameObject>
<GameObject name="sphere" parent="cube">
<Transform posX="1.9596643" posY="1.1400769" posZ="-2.4572649" rotX="0" rotY="0" rotZ="0" scaleX="1" scaleY="1" scaleZ="1"/>
<MeshRenderer useEnvironmentMap="false" fillMode="3" cullMode="3" frontCounterClockwise="false" material="SolidWhiteMaterial" mesh="Sphere">
<RenderPass pass="0" depthStencilState="0"/>
</MeshRenderer>
<SphereCollider radius="0.5" centerX="0" centerY="0" centerZ="0"/>
<Script type="TestEvent"/>
</GameObject>
</Prefab>
이름 충돌 관리
프리팹 인스턴스 생성 시 기존 씬의 오브젝트와 이름 충돌을 방지합니다:
int count = 1;
wstring baseName = name;
wstring newName = baseName;
// 이미 존재하는 오브젝트 이름인지 확인
while (_activeScene->Find(newName) != nullptr)
{
newName = baseName + to_wstring(count);
count++;
}
gameObj->SetName(newName);
nameMapping[name] = newName;
참조 무결성 유지
이름 매핑을 사용하여 부모-자식 관계와 같은 참조 무결성을 유지합니다:
if (const char* parentAttr = gameObjElem->Attribute("parent"))
{
wstring oldChildName = Utils::ToWString(gameObjElem->Attribute("name"));
wstring oldParentName = Utils::ToWString(parentAttr);
// nameMapping에서 새로운 이름 가져오기
wstring newChildName = nameMapping[oldChildName];
wstring newParentName = nameMapping[oldParentName];
shared_ptr<GameObject> childObj = SCENE.GetActiveScene()->Find(newChildName);
shared_ptr<GameObject> parentObj = SCENE.GetActiveScene()->Find(newParentName);
if (childObj && parentObj)
{
childObj->SetParent(parentObj);
}
}
Last updated