본문 바로가기
공부

[C++/DX11] 리소스매니저클래스, XML파일 저장&불러오기

by MY블로그 2023. 5. 18.

리소스매니저(ResourceManager) 클래스 생성

경로 : 2DFrameWork / GameObject / System / [추가]ResourceManager.h & ResourceManager.cpp 

클래스 생성후 GameObject 필터에 framework.h 파일에 헤더및 매크로 추가하기.




ResourceManager.h
#pragma once

template<typename T>
class Resource // 리소스 클래스
{
private:
	unordered_map<string, shared_ptr<T>> list; // 맵형태의 탬플릿 리스트
	/*
	탬플릿형태로 리스트를 만드는 이유? 리소스매니저를통하여 들어오는게 mesh일지 shader일지 모르기때문.
	어떤 자료형이 들어와도 대응이 가능하도록 탬플릿사용.
	현재 System필터의 클래스들은 대부분 탬플릿!
	*/
public:
	const shared_ptr<T>& Load(string file) // 파일불러오기 했을떄
	{
		auto it = list.find(file); // 리스트에서 파일을 검색하고
		if (it != list.end()) // 찾는파일이 리스트의 끝(검색안됨)이아니라면 즉, 검색되었다면
		{
			return it->second; // 반환
		}
		shared_ptr<T> temp = make_shared<T>(); // 검색되지않았다면, 리스트에없다면 생성
		temp->LoadFile(file);
		list[file] = temp;
		return list[file]; // 생성된것으로 반환
	}

	void ReleaseList() // 리스트 해제하기 (초기화)
	{
		for (auto it = list.begin(); it != list.end(); it++) // 리스트 처음부터 끝까지 해제
		{
			SafeReset(it->second); // Safe 매크로 ? 댕글링방지를위하여 해제후 nullptr로 바꿔줌!
		}
		list.clear(); // 해제가 완료된후 초기화!
	}
};

class ResourceManager : public Singleton<ResourceManager> // 리소스매니저는 리소스들을 총괄한다.
{
public:
	Resource<Mesh>			meshes; // 매쉬
	Resource<Shader>		shaders; // 쉐이더
	/*
	리소스매니저에 Actor 리소스가 없는 이유 
	리소스의경우 스마트포인터 shared_ptr 로 만들어져있는데
	이는 즉 모든 객체들이 포인터를통하여 참조(const shared_ptr<T>&)하고있기때문이다.
	상수형태로 참조하는 탬플릿이기때문에 만일 한객체에서 정보를 변경할경우
	모든객체의 정보가 변경되기때문에 매쉬나 쉐이더는 하나의 파일을 공유해도되지만
	Actor의 경우 각각의 수행해야하는 것들이 다르기때문에 정보를 공유해서는 안된다!
	*/

	void ReleaseAll(); // 전체해제
};
ResourceManager.cpp
#include "framework.h"

void ResourceManager::ReleaseAll() // 생성, 추가, 탐색 등은 Resource에서 하기떄문에 해제만진행
{
    meshes.ReleaseList();
    shaders.ReleaseList();
}



저장&불러오기(GOSaveLoad.cpp) 

경로 : 2DFrameWork / GameObject / GOSaveLoad.cpp 

사용한 예시 XML 코드

더보기
<Root Name="Sun" Visible="true" ObType="1">
    <Mesh File="1.Sphere.mesh"/>
    <Shader File="1.Cube.hlsl"/>
    <Transform>
        <Position X="0" Y="0" Z="0"/>
        <Scale X="1" Y="1" Z="1"/>
        <Rotation X="-0.41887903" Y="-0.33161256" Z="1.2740904"/>
    </Transform>
    <Children Size="2"/>
    <Child0 Name="EarthBone" Visible="true" ObType="0">
        <Mesh File="1.Point.mesh"/>
        <Shader File="1.Cube.hlsl"/>
        <Transform>
            <Position X="3.3499999" Y="-0.2" Z="-0.85000002"/>
            <Scale X="0.5" Y="0.5" Z="0.5"/>
            <Rotation X="-5" Y="276.43201" Z="0"/>
        </Transform>
        <Children Size="1"/>
        <Child0 Name="Earth"  Visible="true" ObType="0">
            <Mesh File="1.Sphere.mesh"/>
            <Shader File="1.Cube.hlsl"/>
            <Transform>
                <Position X="8.3999996" Y="1.15" Z="-1.3"/>
                <Scale X="1" Y="1" Z="1"/>
                <Rotation X="-1.1" Y="-309.23199" Z="-0.89999998"/>
            </Transform>
            <Children Size="1"/>
            <Child0 Name="Moon"  Visible="true" ObType="0">
                <Mesh File="1.Sphere.mesh"/>
                <Shader File="1.Cube.hlsl"/>
                <Transform>
                    <Position X="2.55" Y="0" Z="0"/>
                    <Scale X="0.5" Y="0.5" Z="0.5"/>
                    <Rotation X="0" Y="0" Z="0"/>
                </Transform>
                <Children Size="0"/>
            </Child0>
        </Child0>
    </Child0>
    <Child1 Name="MarsBone"  Visible="true" ObType="0">
        <Mesh File="1.Point.mesh"/>
        <Shader File="1.Cube.hlsl"/>
        <Transform>
            <Position X="0" Y="0" Z="0"/>
            <Scale X="0.5" Y="0.5" Z="0.5"/>
            <Rotation X="0" Y="0" Z="0"/>
        </Transform>
        <Children Size="1"/>
        <Child0 Name="Mars" Visible="true" ObType="0">
            <Mesh File="1.Sphere.mesh"/>
            <Shader File="1.Cube.hlsl"/>
            <Transform>
                <Position X="12" Y="0" Z="0"/>
                <Scale X="1" Y="1" Z="1"/>
                <Rotation X="0" Y="0" Z="0"/>
            </Transform>
            <Children Size="0"/>
        </Child0>
    </Child1>
</Root>
#include "framework.h"
void Actor::SaveFile(string file) // 파일 저장하기
{
	this->file = file; // 리스트는 문자열을 키로 사용한다. string file
	Xml::XMLDocument* doc = new Xml::XMLDocument(); // doc 동적할당
	Xml::XMLElement* ob = doc->NewElement("Root"); // 생성한 doc에 Element를 추가한다 (루트)
	doc->LinkEndChild(ob); // doc의 자식노드로 ob(루트)를 붙여준다

	SaveObject(ob, doc); // 오브젝트 저장하기 void GameObject::SaveObject(Xml::XMLElement* This, Xml::XMLDocument* doc)
	string path = "../Contents/GameObject/" + file; // 경로생성
	doc->SaveFile(path.c_str()); // 문자열형태로 경로이름 파일 저장
	SafeDelete(doc); // 저장이끝나면 동적할당한 doc 해제
}

void Actor::LoadFile(string file) // 파일 불러오기
{
	this->file = file; // Gui 에서 선택된 이름의 파일 불러오기
	Xml::XMLDocument* doc = new Xml::XMLDocument(); // doc 동적할당
	string path = "../Contents/GameObject/" + file; // 경로생성

	// 불러오기 성공 = 이부분이있어야 불러올때 버벅임이 적다.
	Xml::XMLError result = doc->LoadFile(path.c_str()); 
	if (result != Xml::XML_SUCCESS) return;

	//기존멤버지우기 = 기존에 있던정보와 겹치면 안되기 때문!
	ReleaseMember();

	Xml::XMLElement* ob; // Element 객체 생성
	ob = doc->FirstChildElement(); // 동적할당한 doc에 첫번쨰 자식노드로 연결
	Xml::XMLElement* component; // component 객체 생성
	
	name = ob->Attribute("Name"); // 이름은 Name에 기제된 이름으로 설정
	type = (ObType)ob->IntAttribute("ObType"); // 타입은 ObType으로 형변환해서 타입설정
	obList[name] = this; // 리스트이름설정
	LoadObject(ob); // 오브젝트 불러오기 void GameObject::LoadObject(Xml::XMLElement* This)
	SafeDelete(doc); // 불러오기가 끝나면 동적할당 doc 해제
}


void GameObject::SaveObject(Xml::XMLElement* This, Xml::XMLDocument* doc) // 오브젝트 저장하기
{
	// 예시 : <Root Name="Sun" Visible="true" ObType="1">
	This->SetAttribute("Name", name.c_str()); // 이름 설정
	This->SetAttribute("Visible", visible); // 비지블 설정
	This->SetAttribute("ObType", (int)type); // 타입설정 (형변환)

	if (mesh) // 매쉬는 매쉬로 저장     <Mesh File="1.Sphere.mesh"/>
	{
		Xml::XMLElement* Mesh = doc->NewElement("Mesh");
		This->LinkEndChild(Mesh);
		Mesh->SetAttribute("File", mesh->file.c_str());
	}
	if (shader) // 쉐이더는 쉐이더로 저장     <Shader File="1.Cube.hlsl"/>
	{
		Xml::XMLElement* Shader = doc->NewElement("Shader");
		This->LinkEndChild(Shader);
		Shader->SetAttribute("File", shader->file.c_str());
	}
	

	Transform::SaveTransform(This, doc); // 트랜스폼 저장

	if (type == ObType::Camera) // 카메라가 있다면 카메라정보도 저장
	{
		Xml::XMLElement* Cam = doc->NewElement("Camera");
		This->LinkEndChild(Cam);
		Camera* CamOb = dynamic_cast<Camera*>(this);
		Cam->SetAttribute("Ortho", CamOb->ortho);
		Cam->SetAttribute("Fov", CamOb->fov);
		Cam->SetAttribute("Width", CamOb->width);
		Cam->SetAttribute("Height", CamOb->height);
		Cam->SetAttribute("NearZ", CamOb->nearZ);
		Cam->SetAttribute("FarZ", CamOb->farZ);
		Cam->SetAttribute("viewportX", CamOb->viewport.x);
		Cam->SetAttribute("viewportY", CamOb->viewport.y);
		Cam->SetAttribute("viewportW", CamOb->viewport.width);
		Cam->SetAttribute("viewportH", CamOb->viewport.height);
	}
	
	// 자식 노드가 있다면
	Xml::XMLElement* Chidren = doc->NewElement("Children");
	This->LinkEndChild(Chidren); // 현자자신의 자식으로 저장
	Chidren->SetAttribute("Size", (int)children.size());

	int i = 0; // 자식은 0번부터 <Child0 Name="EarthBone" Visible="true" ObType="0">
	
	for (auto it = children.begin(); it != children.end(); it++)
	{
		string temp = "Child" + to_string(i++); // 자식이계속있다면 증가되면서추가됨
		Xml::XMLElement* Child = doc->NewElement(temp.c_str());
		This->LinkEndChild(Child);
		it->second->SaveObject(Child, doc); // 재귀로 자식->자식 끝까지저장
	}
}

void GameObject::LoadObject(Xml::XMLElement* This) // 오브젝트불러오기
{
	// 불러오는정보는 다 준비되어있으니 검사로 진행한다.
	Xml::XMLElement* component;
	string file;
	visible = This->BoolAttribute("Visible");

	if (component = This->FirstChildElement("Mesh")) // Mesh 라면
	{
		file = component->Attribute("File"); // 예시 <Mesh File="1.Sphere.mesh"/>
		SafeReset(mesh); // 기존에있던 mesh는 리셋 시키고
		mesh = RESOURCE->meshes.Load(file); // 새롭게 리소스를 통해서 생성(리스트에있다면 불러오기)
	}
	if (component = This->FirstChildElement("Shader")) // Shader 라면 이하 동일
	{
		file = component->Attribute("File"); // 예시 <Shader File="1.Cube.hlsl"/>
		SafeReset(shader); // 리셋후
		shader = RESOURCE->shaders.Load(file); // 새롭게 추가
	}
	
	if (type == ObType::Camera) // 카메라라면
	{
		Camera* CamOb = dynamic_cast<Camera*>(this);
		component = This->FirstChildElement("Camera");

		CamOb->ortho = component->BoolAttribute("Ortho");
		CamOb->fov = component->FloatAttribute("Fov");
		CamOb->width = component->FloatAttribute("Width");
		CamOb->height = component->FloatAttribute("Height");
		CamOb->nearZ = component->FloatAttribute("NearZ");
		CamOb->farZ = component->FloatAttribute("FarZ");
		CamOb->viewport.x = component->FloatAttribute("viewportX");
		CamOb->viewport.y = component->FloatAttribute("viewportY");
		CamOb->viewport.width = component->FloatAttribute("viewportW");
		CamOb->viewport.height = component->FloatAttribute("viewportH");

	}

	Transform::LoadTransform(This); // 트랜스폼 불러오기

	component = This->FirstChildElement("Children");
	int size = component->IntAttribute("Size"); // 자식의 수 받아오기     <Children Size="2"/>

	for (int i = 0; i != size; i++) // 자식수만큼 반복실행
	{
		string Tag = "Child" + to_string(i);
		Xml::XMLElement* ob = This->FirstChildElement(Tag.c_str());
		string childName = ob->Attribute("Name");
		ObType Type = (ObType)ob->IntAttribute("ObType");
		//자식의 각 타입에 따라 추가하고 불러오기(리스트에없으면 생성됨)
		if (Type == ObType::GameObject)
		{
			GameObject* temp = GameObject::Create(childName);
			AddChild(temp);
			temp->LoadObject(ob);
		}
		else if (Type == ObType::Actor)
		{
			Actor* temp = Actor::Create(childName);
			AddChild(temp);
			temp->LoadObject(ob);
		}
		else if (Type == ObType::Camera)
		{
			Camera* temp = Camera::Create(childName);
			AddChild(temp);
			temp->LoadObject(ob);
		}
		
	}
}

/*
트랜스폼 구간 예시 XML 코드
<Transform>
	<Position X="3.3499999" Y="-0.2" Z="-0.85000002"/>
	<Scale X="0.5" Y="0.5" Z="0.5"/>
	<Rotation X="-5" Y="276.43201" Z="0"/>
*/


void Transform::SaveTransform(Xml::XMLElement* This, Xml::XMLDocument* doc) // 트랜스폼 저장하기
{
	Xml::XMLElement* Trans = doc->NewElement("Transform");
	Xml::XMLElement* Pos = doc->NewElement("Position");
	Xml::XMLElement* Scale = doc->NewElement("Scale");
	Xml::XMLElement* Rot = doc->NewElement("Rotation");
	This->LinkEndChild(Trans); // Trans 를 자시긍로 추가하고
	Trans->LinkEndChild(Pos); // Trans에 Pos를 자식으로 추가
	Trans->LinkEndChild(Scale); // Trans에 Scale을 자식으로 추가
	Trans->LinkEndChild(Rot); // Trans에 Rot을 자식으로 추가
	// 각각의 정보에 접근 해서 저장
	Pos->SetAttribute("X", position.x);
	Pos->SetAttribute("Y", position.y);
	Pos->SetAttribute("Z", position.z);
	Scale->SetAttribute("X", scale.x);
	Scale->SetAttribute("Y", scale.y);
	Scale->SetAttribute("Z", scale.z);
	Rot->SetAttribute("X", rotation.x);
	Rot->SetAttribute("Y", rotation.y);
	Rot->SetAttribute("Z", rotation.z);
}

void Transform::LoadTransform(Xml::XMLElement* This) // 트램스폼 불러오기
{
	Xml::XMLElement* component;
	Xml::XMLElement* transform;
	component = This->FirstChildElement("Transform"); // 생성한 component Element에 Transform 넣기
	transform = component->FirstChildElement("Position"); // component(Transform)의 자식인 Position을 transform에 넣기
	position.x = transform->FloatAttribute("X"); // this의 포지션 x,y,z 에 읽어온 transform의 정보 넣기
	position.y = transform->FloatAttribute("Y");
	position.z = transform->FloatAttribute("Z");
	transform = component->FirstChildElement("Scale"); 
	scale.x = transform->FloatAttribute("X");
	scale.y = transform->FloatAttribute("Y");
	scale.z = transform->FloatAttribute("Z");
	transform = component->FirstChildElement("Rotation");
	rotation.x = transform->FloatAttribute("X");
	rotation.y = transform->FloatAttribute("Y");
	rotation.z = transform->FloatAttribute("Z");
}



정리 : 위와같이 파일단위로 Save Load 가 가능해졌기때문에

게임 오브젝트에서 mesh를 생성하거나 shader를 생성하지 않아도 된다!

 

- 맨처음 mesh, shader 만드는 방법 -

mesh = make_shared(); // 생성
shader = make_shared(); // 생성
shader->LoadFile("1.Cube.hlsl"); // 만들어진 hlsl 파일 불러와야했다.

* hlsl = high level shader langueage

 

- 그다음으로 리소스를 사용하여 불러오는 방법 -

shader = RESOURCE->shaders.Load("1.Cube.hlsl");
mesh = RESOURCE->meshes.Load("1.Sphere.mesh");

 

이제 위처럼 생성하거나 불러오는 작없이 필요없다.

댓글