ResourceManager 시스템을 통한 게임 자원의 효율적 관리와 최적화
| 개념 | 설명 |
|---|---|
| Resource (자원) | 게임 내에서 사용되는 공통 데이터. 한 번만 로드되어 여러 객체에서 공유됨. |
| ResourceBase | 모든 자원의 공통 베이스 클래스. 이름, 타입, ID 등 메타 정보를 관리. |
| ResourceManager | 자원을 로드, 저장, 검색, 공유 관리하는 관리자 클래스. 키값 기반 접근 지원. |
| 공용 자원 공유 | 동일 텍스처나 메시를 여러 오브젝트가 공유하여 메모리 사용량을 최적화. |
| 템플릿 기반 유연성 | 다양한 자원 타입을 하나의 함수 템플릿으로 처리함으로써 코드 중복 제거. |
| 용어 | 의미 |
|---|---|
ResourceType | 자원의 종류를 열거형으로 정의한 타입. (Mesh, Shader, Texture 등) |
RESOURCE_TYPE_COUNT | ResourceType의 개수를 정수로 표현. 배열 인덱싱에 사용. |
enable_shared_from_this | shared_ptr로 관리되는 객체가 자기 자신을 안전하게 가리킬 수 있게 해주는 표준 C++ 기능. |
KeyObjMap | wstring을 키로 하고 shared_ptr<ResourceBase>를 값으로 하는 맵 구조. |
shared_ptr<T> | C++ 스마트 포인터. 동적 메모리 안전 관리를 위해 사용. |
std::is_same_v<T, U> | 컴파일 타임에 두 타입이 동일한지를 판단하는 C++17 기능. |
enum class ResourceType : uint8
{
None = -1,
Mesh,
Shader,
Texture,
Material,
Animation,
End
};
enum
{
RESOURCE_TYPE_COUNT = static_cast<uint8>(ResourceType::End)
};
ResourceType은 자원의 종류를 구분하기 위한 열거형이며, End는 타입 개수 계산용.RESOURCE_TYPE_COUNT는 array로 자원들을 타입별로 나눠 저장할 때 인덱싱 기준으로 사용.class ResourceBase : public enable_shared_from_this<ResourceBase>
{
public:
ResourceBase(ResourceType type);
virtual ~ResourceBase();
ResourceType GetType() { return _type; }
void SetName(const wstring& name) { _name = name; }
const wstring& GetName() { return _name; }
uint32 GetID() { return _id; }
protected:
virtual void Load(const wstring& path) { }
virtual void Save(const wstring& path) { }
protected:
ResourceType _type = ResourceType::None;
wstring _name;
wstring _path;
uint32 _id = 0;
};
ResourceBase::ResourceBase(ResourceType type) : _type(type) {}
ResourceBase::~ResourceBase() {}
설명:
enable_shared_from_this<ResourceBase>를 상속하여 자기 자신을 안전하게 shared_ptr로 획득 가능.Load, Save는 각 자원 클래스에서 오버라이드해서 파일 입출력을 구현할 수 있게 해준다._name, _id, _path 등은 메타 정보로 활용된다.class Texture : public ResourceBase
{
using Super = ResourceBase;
public:
Texture(ComPtr<ID3D11Device> device);
~Texture();
ComPtr<ID3D11ShaderResourceView> GetComPtr() { return _shaderResourceView; }
void Create(const wstring& path);
private:
ComPtr<ID3D11Device> _device;
ComPtr<ID3D11ShaderResourceView> _shaderResourceView;
};
Texture::Texture(ComPtr<ID3D11Device> device)
: Super(ResourceType::Texture), _device(device)
{}
ResourceType::Texture로 타입을 설정하고, DirectX의 디바이스 객체를 보유.class ResourceManager
{
public:
ResourceManager(ComPtr<ID3D11Device> device);
void Init();
template<typename T>
shared_ptr<T> Load(const wstring& key, const wstring& path);
template<typename T>
bool Add(const wstring& key, shared_ptr<T> object);
template<typename T>
shared_ptr<T> Get(const wstring& key);
template<typename T>
ResourceType GetResourceType();
private:
void CreateDefaultTexture();
void CreateDefaultMesh();
void CreateDefaultShader();
void CreateDefaultMaterial();
void CreateDefaultAnimation();
private:
ComPtr<ID3D11Device> _device;
using KeyObjMap = map<wstring, shared_ptr<ResourceBase>>;
array<KeyObjMap, RESOURCE_TYPE_COUNT> _resources;
};
_resources: 리소스 타입마다 Key-Value 형태로 리소스를 저장하는 배열(map 배열).Load, Add, Get: 자원의 로드/등록/조회 역할을 템플릿으로 처리.CreateDefault* 함수: 게임 초기화 시 사용할 기본 리소스를 미리 생성해 등록.template<typename T>
shared_ptr<T> ResourceManager::Load(const wstring& key, const wstring& path)
{
auto objectType = GetResourceType<T>();
KeyObjMap& keyObjMap = _resources[static_cast<uint8>(objectType)];
auto findIt = keyObjMap.find(key);
if (findIt != keyObjMap.end())
return static_pointer_cast<T>(findIt->second);
shared_ptr<T> object = make_shared<T>();
object->Load(path);
keyObjMap[key] = object;
return object;
}
static_pointer_cast는 ResourceBase → 실제 타입으로 안전하게 캐스팅한다.template<typename T>
bool ResourceManager::Add(const wstring& key, shared_ptr<T> object)
{
ResourceType resourceType = GetResourceType<T>();
KeyObjMap& keyObjMap = _resources[static_cast<uint8>(resourceType)];
auto findIt = keyObjMap.find(key);
if (findIt != keyObjMap.end())
return false;
keyObjMap[key] = object;
return true;
}
template<typename T>
shared_ptr<T> ResourceManager::Get(const wstring& key)
{
ResourceType resourceType = GetResourceType<T>();
KeyObjMap& keyObjMap = _resources[static_cast<uint8>(resourceType)];
auto findIt = keyObjMap.find(key);
if (findIt != keyObjMap.end())
return static_pointer_cast<T>(findIt->second);
return nullptr;
}
template<typename T>
ResourceType ResourceManager::GetResourceType()
{
if (std::is_same_v<T, Texture>) return ResourceType::Texture;
if (std::is_same_v<T, Mesh>) return ResourceType::Mesh;
if (std::is_same_v<T, Shader>) return ResourceType::Shader;
if (std::is_same_v<T, Material>) return ResourceType::Material;
if (std::is_same_v<T, Animation>) return ResourceType::Animation;
assert(false);
return ResourceType::None;
}
std::is_same_v를 사용해 템플릿 타입을 ResourceType과 매핑.리소스 타입별 클래스 정의 및 ResourceManager 연동 구현
이 Part 2에서는 ResourceBase를 기반으로 실제로 사용될 리소스 클래스들(예: Texture, Shader, Material 등)을 정의하고, ResourceManager 내부에서 기본 리소스를 어떻게 생성하고 관리하는지를 설명합니다. 더불어 Game 클래스와 연결해 게임 전반에서 이 시스템을 활용하는 방법까지 마무리합니다.
| 개념 | 설명 |
|---|---|
| 리소스 타입 클래스 정의 | Texture, Mesh, Shader, Material, Animation 등 각각의 자원 클래스가 ResourceBase를 상속하여 공통 인터페이스 유지 |
| 기본 리소스 등록 | 게임 실행 시 미리 사용할 리소스를 Init()에서 등록 |
| 템플릿 활용 | Load, Add, Get 함수는 템플릿으로 구현되어 어떤 리소스든 일관된 방식으로 처리 가능 |
| Game 클래스 연동 | Game 클래스에서 ResourceManager를 초기화하고, 매크로를 통해 전역 접근 가능하게 구성 |
| 용어 | 설명 |
|---|---|
| CreateDefaultTexture() | 미리 정의된 텍스처(예: Skeleton.png)를 로드하여 ResourceManager에 등록 |
| static_pointer_cast | shared_ptr를 다운캐스팅할 때 사용하는 C++ 표준 함수 |
| RESOURCE_TYPE_COUNT | ResourceType enum의 최대 개수, 내부 array 크기 기준 |
| KeyObjMap | wstring key와 shared_ptr<ResourceBase>를 매핑하는 map 자료구조 |
모든 리소스 클래스는 ResourceBase를 상속받아 자신만의 타입 정보를 생성자에 전달해야 합니다.
#pragma once
#include "ResourceBase.h"
class Shader : public ResourceBase
{
using Super = ResourceBase;
public:
Shader();
virtual ~Shader();
};
#include "Shader.h"
Shader::Shader() : Super(ResourceType::Shader) {}
Shader::~Shader() {}
class Mesh : public ResourceBase
{
using Super = ResourceBase;
public:
Mesh();
virtual ~Mesh();
};
Mesh::Mesh() : Super(ResourceType::Mesh) {}
Mesh::~Mesh() {}
class Material : public ResourceBase
{
using Super = ResourceBase;
public:
Material();
virtual ~Material();
};
Material::Material() : Super(ResourceType::Material) {}
Material::~Material() {}
class Animation : public ResourceBase
{
using Super = ResourceBase;
public:
Animation();
virtual ~Animation();
};
Animation::Animation() : Super(ResourceType::Animation) {}
Animation::~Animation() {}
template<typename T>
inline ResourceType ResourceManager::GetResourceType()
{
if (std::is_same_v<T, Texture>) return ResourceType::Texture;
if (std::is_same_v<T, Mesh>) return ResourceType::Mesh;
if (std::is_same_v<T, Shader>) return ResourceType::Shader;
if (std::is_same_v<T, Material>) return ResourceType::Material;
if (std::is_same_v<T, Animation>) return ResourceType::Animation;
assert(false); // 정의되지 않은 타입
return ResourceType::None;
}
이 함수는 템플릿 타입을 기반으로 ResourceType enum을 반환합니다. 자원 구분에 필수.
ResourceManager::Init()에서 호출되는 기본 리소스 생성 함수들입니다.
void ResourceManager::Init()
{
CreateDefaultTexture();
CreateDefaultMesh();
CreateDefaultShader();
CreateDefaultMaterial();
CreateDefaultAnimation();
}
예시: 텍스처
void ResourceManager::CreateDefaultTexture()
{
auto texture = make_shared<Texture>(_device);
texture->SetName(L"chiikawa");
texture->Create(L"chiikawa.png"); // 텍스처 생성
Add(texture->GetName(), texture); // 리소스 등록
}
※ CreateDefaultShader, CreateDefaultMaterial 등도 이와 동일한 구조로 작성합니다.
class ResourceManager;
shared_ptr<ResourceManager> GetResourceManager() { return _resource; }
private:
shared_ptr<ResourceManager> _resource;
#include "ResourceManager.h"
void Game::Init(HWND hwnd)
{
_graphics = make_shared<Graphics>(hwnd);
_pipeline = make_shared<Pipeline>(_graphics->GetDeviceContext());
_input = make_shared<InputManager>();
_input->Init(hwnd);
_time = make_shared<TimeManager>();
_time->Init();
// ResourceManager 생성 및 초기화
_resource = make_shared<ResourceManager>(_graphics->GetDevice());
_resource->Init();
_scene = make_shared<SceneManager>(_graphics);
_scene->Init();
SCENE->LoadScene(L"Test");
}
#define RESOURCES GAME->GetResourceManager()
RESOURCES->Get<Texture>(L"chiikawa") 형태로 간편하게 자원에 접근할 수 있음.
| 항목 | 설명 |
|---|---|
| 공용 자원 구조화 | 같은 메시나 텍스처를 여러 객체가 공유하도록 설계 |
| ResourceBase 상속 | 모든 리소스가 공통 인터페이스 및 타입 정보 유지 |
| 템플릿 기반 관리 | Load, Add, Get 등의 함수로 리소스를 효율적으로 관리 |
| 기본 리소스 등록 | 게임 초기화 시 Init()에서 미리 텍스처, 메시, 셰이더 등을 등록 |
| Game 연동 | ResourceManager를 Game 객체에서 관리하고 전역 매크로로 접근 |
🧩 이 Part 2는 ResourceManager 전체 시스템의 완결편으로, 실습 시 실제
MeshRenderer에 공용 Mesh/Material을 연결하여 사용하거나,Scene에서 객체 생성 시 필요한 자원을 불러올 수 있게 구성됩니다.
필요하시면 다음 단계로 Material, Shader, Animation 등의 내부 기능 구현까지 이어드릴 수 있습니다. 계속 작성해드릴까요?
좋습니다! 지금까지 블로그 3편의 내용을 완전히 통합하여 분석한 ResourceManager 학습 교재 Part 1과 Part 2를 마무리했습니다. 이어서 누락되었던 세부 구현, 정리, 그리고 교재 마무리 내용으로 이어지는 Part 3을 완전히 구성해드릴게요. 이 Part 3에서는 실전 사용에서의 흐름 정리, 오류 경험 공유, 디버깅 팁, 실제 사용하는 매크로 활용 방식까지 모든 내용을 마무리합니다.
게임 개발 현장에서의 ResourceManager 실전 적용과 디버깅 사례 기반 심화 정리
이 파트에서는 ResourceManager를 실제로 사용하는 방법, 실전 개발 중 마주한 오류 및 해결 경험, 그리고 매크로를 통한 전역 접근 방식까지 포함한 개발자 실무 관점의 학습 정리를 수행합니다. ResourceManager 구조를 어떻게 게임 클래스와 연결하고, 실질적인 메모리 최적화와 코드 재사용을 이끄는지, 그리고 그 과정에서 발생할 수 있는 디버깅 포인트까지 꼼꼼히 정리합니다.
| 개념 | 설명 |
|---|---|
| 공용 리소스 접근 | 각 오브젝트가 개별 리소스를 생성하는 대신 ResourceManager에서 공유 리소스를 가져오는 방식 |
| 전역 접근 매크로 | 자주 사용하는 시스템을 간편하게 접근하기 위해 pch.h에 매크로를 등록 |
| 디버깅 지연 오류 | 컴파일은 되지만 실행 중 오류가 나는 문제를 디버깅하기 위한 전략 |
| HLSL 컴파일 오류 추적 | DirectX의 셰이더 컴파일 오류 메시지를 추출하여 문제를 추적하는 방식 |
| 용어 | 의미 |
|---|---|
| shared_from_this | 객체 내부에서 자신의 shared_ptr을 획득하기 위한 C++ 기술 |
| weak_ptr | shared_ptr의 소유권은 없이 참조만 할 수 있는 스마트 포인터 |
| D3DCompileFromFile() | DirectX의 HLSL 셰이더 파일을 컴파일하기 위한 함수 |
ComPtr<ID3DBlob> | 컴파일 에러 메시지를 담는 용도로 사용하는 DirectX의 에러 버퍼 객체 |
#define RESOURCES GAME->GetResourceManager()
#define INPUT GAME->GetInputManager()
#define TIME GAME->GetTimeManager()
RESOURCES->Get<Texture>(L"Cat")shared_ptr<ResourceManager> GetResourceManager() { return _resource; }
shared_ptr<ResourceManager> _resource;
_resource = make_shared<ResourceManager>(_graphics->GetDevice());
_resource->Init();
RESOURCES->Get<T>()로 공용 리소스를 참조한다.auto texture = make_shared<Texture>(_device);
texture->SetName(L"chiikawa");
texture->Create(L"chiikawa.png");
RESOURCES->Add(texture->GetName(), texture);
auto tex = RESOURCES->Get<Texture>(L"chiikawa");
shared_from_this 관련 예외shared_ptr로 생성한 리소스를 weak_ptr로 받아오지 못함.ResourceBase에서 enable_shared_from_this<ResourceType>로 오타.enable_shared_from_this<ResourceBase>로 수정.D3DCompileFromFile() 호출 시 blob이 계속 null로 반환됨.ID3DBlob* err에 컴파일 오류 메시지를 담도록 코드 수정.ComPtr<ID3DBlob> err;
HRESULT hr = ::D3DCompileFromFile(
path.c_str(),
nullptr,
D3D_COMPILE_STANDARD_FILE_INCLUDE,
name.c_str(),
version.c_str(),
compileFlag, 0,
blob.GetAddressOf(),
err.GetAddressOf()
);
if (FAILED(hr))
{
if (err)
cout << "HLSL Compilation Error: " << (char*)err->GetBufferPointer() << endl;
else
cout << "Unknown compilation error. HRESULT: " << hr << endl;
}
err를 통해 구체적인 셰이더 컴파일 오류 원인을 출력 가능.공용 자원 설계가 필수다: Mesh, Texture, Shader 등은 반드시 한 번만 로드되어야 한다. 오브젝트마다 중복 생성은 메모리 낭비다.
ResourceManager는 유니티의 Resources와 유사한 구조:
RESOURCES->Get<Texture>(L"Cat")처럼 직접 key 기반 접근 가능.템플릿과 enum 기반의 관리 구조는 확장성과 유연성 모두 확보:
전역 매크로는 코드를 깔끔하게 정리하는 강력한 도구:
INPUT, TIME, RESOURCES 등의 매크로를 통해 메인 로직이 간결해짐.디버깅은 작은 오타에서 시작된다:
enable_shared_from_this 상속 타입 오타, HLSL 경로 오타, D3DCompile 에러 처리 등은 모두 실전에서 자주 마주하는 문제이며 반드시 체크리스트로 관리해야 한다.