TinyXML2를 이용한 Animation 리소스의 XML 직렬화 및 역직렬화 시스템 구현
이 강의의 핵심은, 메모리 상에만 존재하던 Animation 데이터를 외부 XML 파일로 저장(Save)하고, 이를 다시 불러와서(Load) 메모리 객체로 복원하는 직렬화 시스템을 직접 구현하는 것이다.
이를 통해 추후 툴에서 만든 애니메이션 리소스를 에디터가 자동으로 로드하거나, 기획자가 만든 데이터 파일을 게임이 불러와서 사용할 수 있게 된다.
| 용어 | 설명 |
|---|---|
| 직렬화 | 메모리 객체를 파일로 저장 가능한 형태로 변환 |
| 역직렬화 | 파일에서 데이터를 읽어와 객체 형태로 복원 |
| XML | 트리 구조 기반의 태그 데이터 표현 포맷 |
| TinyXML2 | C++용 경량 XML 처리 라이브러리 |
| XMLElement | XML 노드를 나타내는 객체 |
| Attribute | XML 노드에 포함된 key-value 형태의 속성 |
| Keyframe | offset, size, time 정보를 가진 애니메이션 프레임 |
tinyxml2.h, tinyxml2.cpp 다운로드 후 프로젝트에 복사 Utils 필터에 추가 tinyxml2.cpp → 속성 → "미리 컴파일된 헤더 사용 안 함" 설정 #include "pch.h" 직접 작성 pch.h에 아래 추가:#include "tinyxml2.h"
using namespace tinyxml2;
Save)void Animation::Save(const wstring& path)
{
tinyxml2::XMLDocument doc;
// 루트 노드 생성: <Animation>
XMLElement* root = doc.NewElement("Animation");
doc.LinkEndChild(root);
// 이름(string으로 변환 필요) 및 속성 설정
string nameStr(GetName().begin(), GetName().end());
root->SetAttribute("Name", nameStr.c_str());
root->SetAttribute("Loop", _loop);
root->SetAttribute("TexturePath", "TODO"); // 경로 설정은 추후
// Keyframe들을 XML 노드로 저장
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);
}
doc.NewElement(...): XML 태그를 생성SetAttribute(...): 노드에 key-value 속성 설정LinkEndChild(...): 자식 노드를 붙임SaveFile(...): XML 파일로 저장TODO: Texture 경로는 추후 지정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({ Vec2{0.f,100.f}, Vec2{100.f,100.f}, 0.1f });
animation->AddKeyframe({ Vec2{100.f,100.f}, Vec2{100.f,100.f}, 0.1f });
animation->AddKeyframe({ Vec2{200.f,100.f}, Vec2{100.f,100.f}, 0.1f });
animation->AddKeyframe({ Vec2{300.f,100.f}, Vec2{100.f,100.f}, 0.1f });
Add(animation->GetName(), animation);
// 저장 실행
animation->Save(L"TestAnimation.xml");
}
TestAnimation.xml 예시:<Animation Name="SnakeAnim" Loop="true" TexturePath="TODO">
<Keyframe OffsetX="0" OffsetY="100" SizeX="100" SizeY="100" Time="0.1"/>
<Keyframe OffsetX="100" OffsetY="100" SizeX="100" SizeY="100" Time="0.1"/>
...
</Animation>
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;
// Keyframe 정보 읽기
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);
}
}
LoadFile(...): XML 로드FirstChildElement(): 루트 <Animation> 접근Attribute(...): 속성 값 가져오기NextSiblingElement(): 다음 <Keyframe> 순회AddKeyframe(...): 메모리에 복원shared_ptr<Animation> anim2 = make_shared<Animation>();
anim2->Load(L"TestAnimation.xml");
anim2 객체에 복원_keyframes에 정상적으로 데이터가 로드됨