第08回 画像リソースの管理
1 / 4 画像の識別と対応表の設計
このステップの目的
画像IDとファイルパスを対応表で結び、ゲーム側の処理とファイル名を分けて管理する設計を理解します。
画像ID(enum class Id)による抽象化
プレイヤーや敵のクラスでは、Assets/Images/Player.pngのようなファイルパスを直接扱いません。
代わりに、ImageManager::Idという列挙型のシンボルを指定して描画を依頼します。
ImageManager.hcpp
class ImageManager
{
public:
// ゲーム内で使用する画像リソースの識別子
enum class Id
{
Player,
PlayerShot,
Enemy,
EnemyShot,
Background,
Count // 画像の総数を表す特殊な列挙子
};
};ファイル名を直接指定せず、IDで画像を扱うと、次の利点があります。
画像名やフォルダ構成が変わっても、Player.cppやEnemy.cppなどのゲーム処理を修正する必要はありません。
存在しない画像IDを指定すると、コンパイルエラーになります。文字列でパスを指定する場合と違い、入力ミスをその場で検出できます。
対応表(ImageTable)による一括管理
画像IDとファイルパスの対応は、ImageManager.cppの無名名前空間にあるImageTableでまとめて管理します。
ImageManager.cppcpp
using ImageId = ImageManager::Id;
struct ImageEntry
{
ImageId id;
const wchar_t* path;
};
constexpr std::array<ImageEntry, static_cast<std::size_t>(ImageId::Count)> ImageTable =
{
ImageEntry { ImageId::Player, L"Assets/Images/Player.png" },
ImageEntry { ImageId::PlayerShot, L"Assets/Images/Bullet.png" },
ImageEntry { ImageId::Enemy, L"Assets/Images/Enemy.png" },
ImageEntry { ImageId::EnemyShot, L"Assets/Images/EnemyShot.png" },
ImageEntry { ImageId::Background, L"Assets/Images/Background.png" }
};ImageId::Countで配列の容量を決めます。ただし、初期化する行が少なくても残りの要素が初期化されるため、登録行数の不足はこれだけでは検出できません。各IDを一度ずつ登録したか、表と読み込み結果を確認します。
一括読み込みとエラーハンドリング
起動時のImageManager::LoadAll()では、対応表を先頭から順に調べます。DXライブラリのLoadGraph()で画像を読み込み、ハンドルを取得します。
ImageManager.cppcpp
ImageManager::LoadResult ImageManager::LoadAll()
{
for (const ImageEntry& entry : ImageTable)
{
std::size_t index = ToIndex(entry.id);
handles[index] = LoadGraph(entry.path);
if (handles[index] == -1)
return { false, entry.path };
}
return { true, {} };
}一つでも画像の読み込みに失敗したら、そのパス(entry.path)を結果構造体へ記録し、直ちに処理を中断します。
これにより、呼び出し元であるGame::Run()は「画像ファイルが見つかりません: Assets/Images/Bullet.png」といった分かりやすいエラーメッセージを表示して安全に終了できます。
チェックリスト
- キャラクターがファイルパスを直接持たず、
ImageManager::Idを指定して描画を依頼している - 画像IDとファイルパスが
ImageTableで一元管理されている理由を説明できる - Id::Countで容量を決め、登録漏れと重複を別に確認している
- 読み込み失敗時にエラーとなったファイルパスを特定できる仕組みを理解している