DirectX

31. 엔진구조_Data

devRiripong 2024. 1. 29.
반응형

정한 파일 포맷을 이용해서 어떻게든 메모리상으로 들고 있는 애들을 표현할 수 있는 게 중요하다. 

 

shared_ptr <Animation> animation 같은 경우는

bool _loop = false; 
shared_ptr<Texture> _texture; 
vector<Keyframe> _keyframes;

Animation.h에 있는 이런 내용들이 핵심이 될 것이다.

이런 거를 납작하게 해서 직렬을 해서 파일에 적어 놓은 다음에 결국 다시 출력하는 식으로 뭔가를 만들어 줘야 한다.

 

우리만의 포맷을 정해도 되지만 읽기 쉽게 하기 위해 데이터를 작성할 때 XML이란 JSON 두 가지 방식을 많이 사용한다.

 

게임회사에서 데이터 관리 시 JSON을 가장 많이 활용 한다.

기획자들은 보통 엑셀로 한다. 원본은 엑셀로 하다가 그거를 프로그램을 만들어서 XML이나 JSON으로 변환을 하는 경우가 일반적이라고 보면 된다.

 

이 둘의 차이는 복잡해지면 장단점이 갈린다.

JSON의 경우는 가볍다. 파싱 때 속도도 빠르다. 웹에서 많이 활용한다.

XML은 계층 구조에 장점이 있다. 직관적이다.

보통은 둘 중에 하나 골라서 작업을 한다.

 

보통 직접 만들어서 사용하진 않는다. 라이브러리 하나를 가져와서 사용하면 된다.

C#은 마소에서 제공되는게 있는데 C++은 직접 구해서 써야 한다.

C++에서는 아직 리플렉션도 지원하지 않기 때문에 노가다가 필요하다.

 

XML을 어떻게 활용하는지 만들어 보도록 할 건데,

일단 TinyXML이라는 라이브러리를 많이 사용한다.

 

1. tinyxml2라이브러리 적용 하기

검색해서 다운 받거나 선생님이 올려준 프로젝트에서 코드가 있는 폴더에 있는 tinyxml2.h, tinyxml2.cpp 파일을 나의 프로젝트에 복붙 해준다.

그리고 99. Header / Utils 필터에 드래그 앤 드롭한다.

빌드를 하면 에러가 난다.

 

error C1010: unexpected end of file while looking for precompiled header. Did you forget to add '#include "pch.h"' to your source?

 

시작할 때 precompiled header를 사용한다 했기 때문이다.

 

tinyxml2.cpp 에서 우클릭을 하고 properties에 들어가서

 

이렇게 사용 안 함으로 해주면 된다.

 

아니면 코드에 #include “phc.h”를 해주면 된다.

 

인터넷에 tinyxml이라고 치면 나오는 라이브러리이다.

외부 라이브러리를 쓸 때는 두려움이 없어야 한다. 어떻게든 할 수 있다는 믿을을 가지고 하면 된다.

 

pch.h에 가서

// Utils
#include "tinyxml2.h"
using namespace tinyxml2;

이렇게 추가한다.

빌드를 하면 잘 된다.

 

이제 xml을 save, load 하는 함수 이렇게 두 개만 해본다.

Animation 클래스에 Save, Load를 만들어 본다.

XML 파일을 Save 하고 Load 해서 읽는 식으로 해본다.

일단 Save부터 만들어 본다.

일단 XML 폼을 만들어 볼 건데 Tree를 생각하면 된다.

 

 

루트 노드 만들어서 property를 만들어 주고, 루트노드 산하에 4개의 노드를 추가할 건데 각각 노드에 정보를 추가해 준다는 걸 기억하고, 코드로 옮기면 된다.

 

각 노드의 정보들은

struct Keyframe
{
	// 어디부터 어디까지 그릴 것인지
	Vec2 offset = Vec2{ 0.f, 0.f }; 
	Vec2 size = Vec2{ 0.f, 0.f }; 

	// 몇 초 통안 그릴 것인지
	float time = 0.f; 
};

이 정보들이 들어가면 된다.

 

2. Save 함수

void Animation::Save(const wstring& path)
{
	tinyxml2::XMLDocument doc; 

	// node를 element라고 부른다고 생각하면 된다. 
	XMLElement* root = doc.NewElement("Animation"); // 노드 만들기
	doc.LinkEndChild(root); // 만들어준 노드를 문서에 추가 

	string nameStr(GetName().begin(), GetName().end()); 
	root->SetAttribute("Name", nameStr.c_str()); // key값, value로 정보를 넣어 주는 거 Name = "SnakeAnim"
	root->SetAttribute("Loop",_loop); 
	root->SetAttribute("TexturePath", "TODO"); // 나중에 경로를 넣어 줄 수 있다.

	// root 산하의 여러 노드를 만들어 준다
	for (const auto& keyframe : _keyframes)
	{
		XMLElement* node = doc.NewElement("Keyframe"); 
		root->LinkEndChild(node); 

		node->SetAttribute("OffsetX", keyframe.offset.x); 
		node->SetAttribute("OffsetY", keyframe.offset.y); 
		node->SetAttribute("SizeX", keyframe.size.x); 
		node->SetAttribute("SizeY", keyframe.size.y); 
		node->SetAttribute("Time", keyframe.time); 
	}

	string pathStr(path.begin(), path.end()); 
	auto result = doc.SaveFile(pathStr.c_str());
	assert(result == XMLError::XML_SUCCESS);
}

Save가 이렇게 만들어진다.

 

3. Save 함수 테스트 

ResourceManager로 가서 간단하게 테스트를 해본다.

void ResourceManager::CreateDefaultAnimation()
{
	shared_ptr<Animation> animation = make_shared<Animation>(); 
	animation->SetName(L"SnakeAnim"); 
	animation->SetTexture(Get<Texture>(L"Snake")); 
	animation->SetLoop(true); 

	animation->AddKeyframe(Keyframe{ Vec2{0.f, 0.f}, Vec2{100.f, 100.f}, 0.1f }); 
	animation->AddKeyframe(Keyframe{ Vec2{100.f, 0.f}, Vec2{100.f, 100.f}, 0.1f }); 
	animation->AddKeyframe(Keyframe{ Vec2{200.f, 0.f}, Vec2{100.f, 100.f}, 0.1f }); 
	animation->AddKeyframe(Keyframe{ Vec2{300.f, 0.f}, Vec2{100.f, 100.f}, 0.1f }); 

	Add(animation->GetName(), animation); 

	// XML + JSON
	animation->Save(L"TextAnim.xml"); 
}

실행을 해보면 코드가 있는 폴더에 TextAnim.xml 파일이 생긴 걸 볼 수 있고,

열어 보면

<Animation Name="SnakeAnim" Loop="true" TexturePath="TODO">
    <Keyframe OffsetX="0" OffsetY="0" SizeX="100" SizeY="100" Time="0.1"/>
    <Keyframe OffsetX="100" OffsetY="0" SizeX="100" SizeY="100" Time="0.1"/>
    <Keyframe OffsetX="200" OffsetY="0" SizeX="100" SizeY="100" Time="0.1"/>
    <Keyframe OffsetX="300" OffsetY="0" SizeX="100" SizeY="100" Time="0.1"/>
</Animation>

이렇게 성공적으로 저장이 되었다는 것을 볼 수 있다.

 

4. Load 함수

지금처럼 하드코딩해서 넣어주는 게 아니라 툴로 이런저런 설정을 해서 저장 버튼을 누르면 이런 식으로 파일이 저장이 되어야 의미가 있을 것이다.

이렇게 만든 파일은 거꾸로 편리하게 사용할 수 있게 된 것이다. 대칭적이니까 반대 방향으로 해주면 된다.

void Animation::Load(const wstring& path)
{
	tinyxml2::XMLDocument doc;

	string pathStr(path.begin(), path.end()); 
	XMLError error = doc.LoadFile(pathStr.c_str());
	assert(error == XMLError::XML_SUCCESS); 

	XmlElement* root = doc.FirstChildElement(); 
	string nameStr = root->Attribute("Name"); 
	_name = wstring(nameStr.begin(), nameStr.end()); 

	_loop = root->BoolAttribute("Loop"); 
	_path = path; 

	// Load Texture
	
	XmlElement* node = root->FirstChildElement();
	for (; node != nullptr; node = node->NextSiblingElement())
	{
		Keyframe keyframe; 

		keyframe.offset.x = node->FloatAttribute("OffsetX"); 
		keyframe.offset.y = node->FloatAttribute("OffsetY"); 
		keyframe.size.x = node->FloatAttribute("SizeX"); 
		keyframe.size.y = node->FloatAttribute("SizeY"); 
		keyframe.time = node->FloatAttribute("Time"); 

		AddKeyframe(keyframe); 
	}
}

이렇게 해주면 알아서 모든 애들이 된다고 보면 된다.

 

5. Load 함수 테스트

테스를 하고 싶으면

void ResourceManager::CreateDefaultAnimation()에서

void ResourceManager::CreateDefaultAnimation()
{
	shared_ptr<Animation> animation = make_shared<Animation>(); 
	animation->SetName(L"SnakeAnim"); 
	animation->SetTexture(Get<Texture>(L"Snake")); 
	animation->SetLoop(true); 

	animation->AddKeyframe(Keyframe{ Vec2{0.f, 0.f}, Vec2{100.f, 100.f}, 0.1f }); 
	animation->AddKeyframe(Keyframe{ Vec2{100.f, 0.f}, Vec2{100.f, 100.f}, 0.1f }); 
	animation->AddKeyframe(Keyframe{ Vec2{200.f, 0.f}, Vec2{100.f, 100.f}, 0.1f }); 
	animation->AddKeyframe(Keyframe{ Vec2{300.f, 0.f}, Vec2{100.f, 100.f}, 0.1f }); 

	Add(animation->GetName(), animation); 

	// XML + JSON
	animation->Save(L"TextAnim.xml"); 

	shared_ptr<Animation> anim2 = make_shared<Animation>(); 
	anim2->Load(L"TextAnim.xml"); 
}

이렇게 하면 문제가 없다면 저장해 준 것을 그대로 꺼내 올 거라는 걸 알 수 있다.

ResourceManager::CreateDefaultAnimation() 함수의 마지막 줄에 중단점을 찍고 실행해 보면

anim2에 데이터가 잘 들어온 것을 볼 수 있다.

 

상용 툴에서도 애니메이션 파일 같은 걸 Resource로 관리하고 있다가 그 정보들을 사용할 때마다 로드해서 사용하는 것이라는 걸 알 수 있다.

그래서 Resource는 무조건 Save, Load 짝이 맞춰져 있어서 애니메이션을 대상으로 했지만 Material, Mesh, Shader, Texture 등 이런 모든 리소스는 기본적으로 Save와 Load를 제공해야 한다는 걸 알 수 있다.

 

json이나 metafile로 하건 새로 정한 포맷으로 하건 중요한 건 아니고 중요한 건 이런 식으로 Save, Load를 만들어 줘야 한다가 핵심이다.

 

 

이렇게 2D에서 중요하게 생각하는 부분을 한 바퀴 돌았다.

셰이더를 이용해서 그렸다는 게 WindAPI와 차이다.

3D로 넘어가면 GPU를 활용하냐 마냐에 따라 성능이 천배, 만배 차이가 난다.

2D로 DX로 입문하는 건 적응할 시간을 주는 거다.

엔진 구조와 그래픽스 지식이 동시에 들어오면 힘드니 1단계로 최소한의 그래픽스 지식, 게임 수학, 엔진 구조에 대해 맛보기, 렌더링 파이프라인 등을 배워봤다.

 

3D에서는 몇 가지가 추가된다.

지금은 모든 걸 하나의 프로젝트에 만들고 있는데, 엔진이랑 클라이언트(콘텐츠) 쪽이랑 구분을 할 수 있어야 한다.

 

더 나아가 3D에서 그림자나 3D 메쉬 애니메이션, 자식 부모 관계 생각한 애니메이션, 조명, 이펙트 관리, 최적화 기법, 컬링 같은 게 진행된다.

 

3D 넘어가기 전에 30%는 한 거다. 

 

포폴 만들 때 만들어 놓은 걸로 컨텐츠단 작업을 하는 거는 유니티로 만드는 거랑 별 차이가 없다.

포폴 만들어도 엔진단을 이해 못 하고 만들면 의미가 없으니 면접 디펜스를 위해서라도 핵심 부분을 복습하고 계속 공부해야 한다. 

반응형

'DirectX' 카테고리의 다른 글

33. DirectX11 3D 입문_사각형 띄우기  (0) 2024.02.01
32. DirectX11 3D 입문_프로젝트 설정  (0) 2024.01.31
30. 엔진구조_Animation  (0) 2024.01.28
29. 엔진구조_Material  (0) 2024.01.26
28. 엔진구조_ RenderManager  (0) 2024.01.23

댓글