정한 파일 포맷을 이용해서 어떻게든 메모리상으로 들고 있는 애들을 표현할 수 있는 게 중요하다.
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 |
댓글