Skip to content
第13回 新作プロジェクトとゲームの土台

3 / 4 画像リソース管理と解像度情報の保持

このステップの目的

画像ハンドルとファイルパスを ImageManager に集約し、スプライトシート切り出しを見据えて元画像の寸法(幅・高さ)を合わせて保持する設計を学びます。

画像ID(ImageId)とリソース構造体

本作で使用する画像リソースは、ファイルパスを直接扱わず列挙型 ImageId で指定します。
背景画像(Background.png)は第13回から画面全体へ表示するため、最初の列挙子として登録します。

第13回から使用する背景画像

また、プラットフォーマー特有の設計として、画像ハンドルだけでなく**元画像のピクセル寸法(幅と高さ)**を保持する ImageResource 構造体を定義します。

ImageManager.hcpp
enum class ImageId
{
    Background, // 画面全体へ表示する背景
    Player,     // プレイヤーのスプライトシート
    Enemy,      // 敵キャラクターのスプライトシート
    Tile,       // 通常ブロック(レンガ)
    TileMoss,   // コケ付きブロック
    Goal,       // ゴール地点のフラッグ
    Count       // 列挙子の総数
};

struct ImageResource
{
    int handle = -1;    // DXライブラリのグラフィックハンドル
    int width = 0;      // 画像の横幅(ピクセル)
    int height = 0;     // 画像の縦幅(ピクセル)
};

元画像の幅と高さを保持する理由

アクションゲームでは、プレイヤーの歩行やジャンプを表現するために、1枚の横長画像に複数コマの絵を並べた「スプライトシート」を使用します(第20回で実装)。
このとき、画像全体の幅や高さをあらかじめ取得して保持しておけば、1コマあたりの正確な切り出し幅を計算したり、想定外のテクスチャサイズ変更を検知したりする際に便利です。

なお、トゲトラップ(Spike)の識別子は、トラップ機能を実装する第23回で追加します。素材ファイルを事前に配置することと、使用しない識別子を先行して定義することは分けて管理します。

対応表と static_assert による登録漏れ防止

画像IDと実際のファイルパスの紐付けは、ImageManager.cpp の対応表 imageDefinitions で一元管理します。

ImageManager.cppcpp
struct ImageDefinition
{
    ImageId id;
    const wchar_t* path;
};

constexpr ImageDefinition imageDefinitions[] =
{
    { ImageId::Background, L"Assets/Images/Background.png" },
    { ImageId::Player,     L"Assets/Images/Player.png" },
    { ImageId::Enemy,      L"Assets/Images/Enemy.png" },
    { ImageId::Tile,       L"Assets/Images/Tile.png" },
    { ImageId::TileMoss,   L"Assets/Images/TileMoss.png" },
    { ImageId::Goal,       L"Assets/Images/Goal.png" }
};

// 列挙型のCountと定義表の件数が一致しているかをコンパイル時に検証する
static_assert(
    sizeof(imageDefinitions) / sizeof(imageDefinitions[0]) == static_cast<std::size_t>(ImageId::Count),
    "imageDefinitions count does not match ImageId::Count");

static_assert を配置しておくことで、「ImageId に新しい画像を追加したのに、対応表へのパス登録を忘れてしまった」というミスを、コンパイル段階で検出できます。

読み込み失敗時の厳密な中断

登録内容を調べるHasCompleteImageDefinitionsと解放するReleaseは、参考実装から利用します。起動時の ImageManager::LoadAll() では、対応表を順に走査し、LoadGraph()GetGraphSize() を実行します。

ImageManager.cppcpp
bool ImageManager::LoadAll()
{
    Release();

    if (!HasCompleteImageDefinitions())
    {
        MessageBoxW(nullptr,
            L"ImageIdと画像定義の登録に重複または不足があります。",
            L"Image Definition Error", MB_OK);
        return false;
    }

    for (const ImageDefinition& definition : imageDefinitions)
    {
        const std::size_t index = static_cast<std::size_t>(definition.id);
        ImageResource& resource = resources_[index];
        resource.handle = LoadGraph(definition.path);

        if (resource.handle == -1 ||
            GetGraphSize(resource.handle, &resource.width,
                &resource.height) == -1)
        {
            std::wstring message = definition.path;
            message += L" の読み込みまたはサイズ取得に失敗しました。";
            MessageBoxW(nullptr, message.c_str(), L"Asset Load Error", MB_OK);
            Release();
            return false;
        }
    }

    return true;
}

素材が欠けている状態でゲームを継続すると、画像が表示されない原因が描画処理の不具合なのか素材の不足なのか判別が困難になります。不完全な状態での起動を許さず、エラーの対象パスを通知して処理を停止させることが重要です。

チェックリスト
  • ImageResource がハンドルとともに元画像の幅と高さを保持している
  • static_assert によって画像IDの総数と定義表の行数が一致していることが保証されている
  • 画像の読み込みに失敗した際、対象のファイルパスを表示して初期化を安全に中止できる

ゲームプログラミング実践