📘 주제

ResourceManager 시스템을 통한 게임 자원의 효율적 관리와 최적화

  • 게임 엔진에서 사용되는 다양한 자원(Resource)—예: 텍스처(Texture), 메시(Mesh), 셰이더(Shader), 머티리얼(Material), 애니메이션(Animation)—를 효율적으로 통합 관리하기 위해 ResourceManager 시스템을 설계하고 구현한다.
  • 수많은 GameObject가 동일 자원을 각각 보유하면 중복 생성으로 인해 성능이 저하된다. 이를 해결하기 위해, 공통 자원을 외부로 분리하고, 이를 중앙에서 관리하며 공유할 수 있도록 시스템을 구성하는 것이 본 강의의 핵심이다.

📙 개념

개념설명
Resource (자원)게임 내에서 사용되는 공통 데이터. 한 번만 로드되어 여러 객체에서 공유됨.
ResourceBase모든 자원의 공통 베이스 클래스. 이름, 타입, ID 등 메타 정보를 관리.
ResourceManager자원을 로드, 저장, 검색, 공유 관리하는 관리자 클래스. 키값 기반 접근 지원.
공용 자원 공유동일 텍스처나 메시를 여러 오브젝트가 공유하여 메모리 사용량을 최적화.
템플릿 기반 유연성다양한 자원 타입을 하나의 함수 템플릿으로 처리함으로써 코드 중복 제거.

🧾 용어 정리

용어의미
ResourceType자원의 종류를 열거형으로 정의한 타입. (Mesh, Shader, Texture 등)
RESOURCE_TYPE_COUNTResourceType의 개수를 정수로 표현. 배열 인덱싱에 사용.
enable_shared_from_thisshared_ptr로 관리되는 객체가 자기 자신을 안전하게 가리킬 수 있게 해주는 표준 C++ 기능.
KeyObjMapwstring을 키로 하고 shared_ptr<ResourceBase>를 값으로 하는 맵 구조.
shared_ptr<T>C++ 스마트 포인터. 동적 메모리 안전 관리를 위해 사용.
std::is_same_v<T, U>컴파일 타임에 두 타입이 동일한지를 판단하는 C++17 기능.

💻 코드 분석


🔹 ResourceType 정의

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_COUNTarray로 자원들을 타입별로 나눠 저장할 때 인덱싱 기준으로 사용.

🔹 ResourceBase 클래스

ResourceBase.h

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.cpp

ResourceBase::ResourceBase(ResourceType type) : _type(type) {}
ResourceBase::~ResourceBase() {}

설명:

  • 모든 자원의 공통 기능을 제공하는 베이스 클래스이다.
  • enable_shared_from_this<ResourceBase>를 상속하여 자기 자신을 안전하게 shared_ptr로 획득 가능.
  • Load, Save는 각 자원 클래스에서 오버라이드해서 파일 입출력을 구현할 수 있게 해준다.
  • _name, _id, _path 등은 메타 정보로 활용된다.

🔹 Texture 클래스 (자원 예시 클래스)

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의 디바이스 객체를 보유.
  • 이후의 리소스 클래스들도 이와 유사한 방식으로 구성된다 (Mesh, Shader, Material, Animation 등).

🔹 ResourceManager 클래스 구조

ResourceManager.h

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_castResourceBase → 실제 타입으로 안전하게 캐스팅한다.

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;
}
  • 이미 해당 키로 등록된 리소스가 있는 경우 false 반환.
  • 그렇지 않으면 새 리소스를 등록.

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_castshared_ptr를 다운캐스팅할 때 사용하는 C++ 표준 함수
RESOURCE_TYPE_COUNTResourceType enum의 최대 개수, 내부 array 크기 기준
KeyObjMapwstring key와 shared_ptr<ResourceBase>를 매핑하는 map 자료구조

🔍 코드 분석

1. 리소스 타입 클래스 정의

모든 리소스 클래스는 ResourceBase를 상속받아 자신만의 타입 정보를 생성자에 전달해야 합니다.

✅ Shader

#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() {}

✅ Mesh

class Mesh : public ResourceBase
{
	using Super = ResourceBase;
public:
	Mesh();
	virtual ~Mesh();
};
Mesh::Mesh() : Super(ResourceType::Mesh) {}
Mesh::~Mesh() {}

✅ Material

class Material : public ResourceBase
{
	using Super = ResourceBase;
public:
	Material();
	virtual ~Material();
};
Material::Material() : Super(ResourceType::Material) {}
Material::~Material() {}

✅ Animation

class Animation : public ResourceBase
{
	using Super = ResourceBase;
public:
	Animation();
	virtual ~Animation();
};
Animation::Animation() : Super(ResourceType::Animation) {}
Animation::~Animation() {}

2. ResourceType 판별 함수 확장

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을 반환합니다. 자원 구분에 필수.


3. 기본 리소스 등록 함수 구현

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 등도 이와 동일한 구조로 작성합니다.


4. Game 클래스 연동

✅ Game.h

class ResourceManager;
shared_ptr<ResourceManager> GetResourceManager() { return _resource; }

private:
shared_ptr<ResourceManager> _resource;

✅ Game.cpp

#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");
}

5. 전역 매크로 정의 (pch.h)

#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 1Part 2를 마무리했습니다. 이어서 누락되었던 세부 구현, 정리, 그리고 교재 마무리 내용으로 이어지는 Part 3을 완전히 구성해드릴게요. 이 Part 3에서는 실전 사용에서의 흐름 정리, 오류 경험 공유, 디버깅 팁, 실제 사용하는 매크로 활용 방식까지 모든 내용을 마무리합니다.


📘 주제

게임 개발 현장에서의 ResourceManager 실전 적용과 디버깅 사례 기반 심화 정리

이 파트에서는 ResourceManager를 실제로 사용하는 방법, 실전 개발 중 마주한 오류 및 해결 경험, 그리고 매크로를 통한 전역 접근 방식까지 포함한 개발자 실무 관점의 학습 정리를 수행합니다. ResourceManager 구조를 어떻게 게임 클래스와 연결하고, 실질적인 메모리 최적화와 코드 재사용을 이끄는지, 그리고 그 과정에서 발생할 수 있는 디버깅 포인트까지 꼼꼼히 정리합니다.


📙 개념

개념설명
공용 리소스 접근각 오브젝트가 개별 리소스를 생성하는 대신 ResourceManager에서 공유 리소스를 가져오는 방식
전역 접근 매크로자주 사용하는 시스템을 간편하게 접근하기 위해 pch.h에 매크로를 등록
디버깅 지연 오류컴파일은 되지만 실행 중 오류가 나는 문제를 디버깅하기 위한 전략
HLSL 컴파일 오류 추적DirectX의 셰이더 컴파일 오류 메시지를 추출하여 문제를 추적하는 방식

🧾 용어정리

용어의미
shared_from_this객체 내부에서 자신의 shared_ptr을 획득하기 위한 C++ 기술
weak_ptrshared_ptr의 소유권은 없이 참조만 할 수 있는 스마트 포인터
D3DCompileFromFile()DirectX의 HLSL 셰이더 파일을 컴파일하기 위한 함수
ComPtr<ID3DBlob>컴파일 에러 메시지를 담는 용도로 사용하는 DirectX의 에러 버퍼 객체

💻 코드 분석 및 실전 사용 흐름

🔹 매크로 등록 (pch.h)

#define RESOURCES GAME->GetResourceManager()
#define INPUT     GAME->GetInputManager()
#define TIME      GAME->GetTimeManager()
  • 반복적으로 접근하는 시스템을 간편하게 사용하기 위한 매크로 설정.
  • 사용 예: RESOURCES->Get<Texture>(L"Cat")

🔹 Game 클래스에서 ResourceManager 연동

Game.h

shared_ptr<ResourceManager> GetResourceManager() { return _resource; }
shared_ptr<ResourceManager> _resource;

Game.cpp - Init()

_resource = make_shared<ResourceManager>(_graphics->GetDevice());
_resource->Init();
  • ResourceManager는 게임 초기화 시 생성되어, 기본 리소스들을 등록한다.
  • 이후 게임 내 어떤 오브젝트든 RESOURCES->Get<T>()로 공용 리소스를 참조한다.

🔹 실전 사용 흐름 예시

1️⃣ ResourceManager에 리소스를 등록

auto texture = make_shared<Texture>(_device);
texture->SetName(L"chiikawa");
texture->Create(L"chiikawa.png");
RESOURCES->Add(texture->GetName(), texture);

2️⃣ 어디서든 리소스를 재사용

auto tex = RESOURCES->Get<Texture>(L"chiikawa");
  • 오브젝트는 texture를 따로 생성하지 않고 공유 리소스를 참조한다.

⚠ 디버깅 경험과 해결 전략

문제 1. shared_from_this 관련 예외

  • 증상: shared_ptr로 생성한 리소스를 weak_ptr로 받아오지 못함.
  • 원인: ResourceBase에서 enable_shared_from_this<ResourceType>로 오타.
  • 해결: 올바르게 enable_shared_from_this<ResourceBase>로 수정.

문제 2. HLSL 셰이더 컴파일 오류

  • 증상: D3DCompileFromFile() 호출 시 blob이 계속 null로 반환됨.
  • 원인: HLSL 경로 오타, 혹은 컴파일 실패 원인 확인 불가.
  • 해결: 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를 통해 구체적인 셰이더 컴파일 오류 원인을 출력 가능.
  • 이 방법을 몰랐더라면 문제 추적에 수 일이 걸렸을 수 있음.

🧠 핵심

  1. 공용 자원 설계가 필수다: Mesh, Texture, Shader 등은 반드시 한 번만 로드되어야 한다. 오브젝트마다 중복 생성은 메모리 낭비다.

  2. ResourceManager는 유니티의 Resources와 유사한 구조:

    • RESOURCES->Get<Texture>(L"Cat")처럼 직접 key 기반 접근 가능.
  3. 템플릿과 enum 기반의 관리 구조는 확장성과 유연성 모두 확보:

    • 새로운 타입의 리소스가 추가되더라도 ResourceBase 상속만 하면 ResourceManager에서 자동 관리 가능.
  4. 전역 매크로는 코드를 깔끔하게 정리하는 강력한 도구:

    • INPUT, TIME, RESOURCES 등의 매크로를 통해 메인 로직이 간결해짐.
  5. 디버깅은 작은 오타에서 시작된다:

    • enable_shared_from_this 상속 타입 오타, HLSL 경로 오타, D3DCompile 에러 처리 등은 모두 실전에서 자주 마주하는 문제이며 반드시 체크리스트로 관리해야 한다.

profile
李家네_공부방

0개의 댓글