Skip to content

第03回 画像表示の導入とエラー確認

前回: 第02回 ゲームループの理解と実装 / 次回: 第04回 マップデータの外部化(CSV読込)とスクロール

前回の振り返り

ゲームループと状態遷移の基本を確認しました

  • 入力、更新、判定、状態更新、描画の順序を確認しました
  • 処理順の違いで挙動がどう変わるかを比較しました
  • TitlePlayingGameClearGameOver の最小状態を導入しました
  • 今回は、図形描画を画像描画へ置き換えます

今回の目的

画像を導入し、読み込み失敗を早めに検出する

  • プレイヤー画像を読み込んで描画する
  • タイル画像とゴール画像を読み込んで描画する
  • 画像が見つからない場合はエラーを表示して停止する
  • 必須素材が欠けた状態で半端に実行を進めない作りにする

今回の授業内容

画像表示の導入とエラー確認

  • 画像ファイルの配置ルール
  • Assets フォルダでの素材管理
  • ビルド後イベントによる素材コピー
  • LoadGraph と画像ハンドル
  • エラー対応の考え方
  • 読み込み失敗時のエラー処理
  • プレイヤー、タイル、ゴールの画像描画
  • 初期化失敗時にゲームを開始しない設計
  • 素材差し替えの発展課題

画像を使う

見た目を変えても ゲームの処理は変えません

素材不足を 見逃さない

アセットファイルの配置

パスを固定して確認しやすくする

  • 実行時に使うファイルは Assets フォルダにまとめます
  • 画像は Assets/Images に置きます
  • マップCSVは次回以降 Assets/Maps に置きます
  • 音声やフォントを追加する場合も、Assets の下に分類して置きます
  • 実行ファイルから見た相対パスがコードと一致していることが重要です

Assetsフォルダの構成

種類ごとにサブフォルダへ分ける

  • 今後素材が増えても、コピー対象は Assets だけで済みます
  • コード上のパスも、素材の種類ごとに整理できます
  • 今回使う画像名は固定して扱います
text
Assets/
  Images/
    Player.png
    Tile.png
    Goal.png
    Enemy.png
  Maps/
    stage01.csv
  Sounds/
  Fonts/

ビルド後イベント

Assetsごと実行フォルダへコピーする

  • Visual Studio のビルド後イベントで、Assets をまとめてコピーします
  • 画像やCSVが増えても、コピー設定を増やす必要がありません
  • コピー後、実行ファイルから Assets/Images/Player.png のように参照できます
bat
xcopy "$(ProjectDir)Assets" "$(OutDir)Assets\" /E /I /Y /D
  • /E: サブフォルダも含める
  • /I: コピー先をフォルダとして扱う
  • /Y: 上書き確認を出さない
  • /D: 新しいファイルだけコピーする

画像ハンドルとは

読み込んだ画像を使うための番号

  • LoadGraph は、画像の読み込みに成功すると画像ハンドルを返します
  • ハンドルは、ゲーム中に何度も描画する画像を管理するための値です 描画時は、読み込み済みのデータとして画像ファイル名ではなく 画像ハンドルを用います
  • ファイル名を間違えているなどの理由で読み込みに失敗した場合、 LoadGraph は ハンドルとして -1 を返します
  • 必須画像の読み込みに失敗した場合は、初期化失敗としてエラーを表示しましょう

エラーの対応

問題を検知し、原因を特定する

  • 必須のファイルの読み込みに失敗しているようなクリティカルなエラーを 防衛的に握りつぶすと、後の処理で別のエラーの原因になったり まだらに動いているように見えて真の問題を見逃すことがあります
  • エラーは見つけ次第対応するのが基本です 簡潔に通知し、情報を出力し、状況に応じてプログラムの実行を停止するなど 執拗かつ強力に対応してください
  • プログラマーのエラー対応の姿勢は、制作全体の生産性を大きく左右します 早期発見と対応に努めましょう

エラー対応の考え方

きっちり仕留める、でもやりすぎは禁物?

  • 個人作業では、エラーは見つけ次第プログラムの動作を停止させる等 強力な対応を徹底することで問題の原因を特定しやすくなります
  • ですが集団作業では、エラーチェックで止めすぎると 予期しないエラーの際に他の人の作業を妨害してしまう危険もあります
  • 現代のプログラミングには、単にコンピューターを制御するだけではなく ワークフローを意識した立ち振る舞いが求められます どうすればチーム全体の生産性が上がるかを考えながら 適切に設計と実装を行いましょう

Game.hへ画像ハンドルを追加

プレイヤー画像を保持する

  • プレイヤー画像は Game が使います
  • 初期値を -1 にしておくと、読み込み失敗を判断できます
  • LoadImages を用意して、初期化時に読み込みます
cpp
class Game
{
public:
    bool Initialize();
    void MainLoop();

private:
    bool LoadImages();
    void DrawPlayer() const;

    int playerHandle = -1;
};

プレイヤー画像を読み込む

LoadGraphの結果を検証する

  • LoadGraph は初期化後に呼び出します
  • パスは実行フォルダからの相対パスです
  • 必須画像が読み込めない場合は false を返します
cpp
bool Game::Initialize()
{
    if (!LoadImages()) return false;

    if (!stage.Initialize()) return false;
    ResetPlay();
    return true;
}

bool Game::LoadImages()
{
    playerHandle = LoadGraph(L"Assets/Images/Player.png");
    if (playerHandle == -1)
    {
        MessageBoxW(nullptr,
            L"Assets/Images/Player.png を読み込めません。",
            L"Asset Load Error", MB_OK);
        return false;
    }

    return true;
}

プレイヤーの画像描画

読み込み済みの画像を描く

  • 画像が必ず読み込めている前提で描画します
  • -1 判定は初期化時に済ませます
  • 描画関数にエラー回避処理を混ぜないようにします
cpp
void Game::DrawPlayer() const
{
    DrawGraph(player.x, player.y, playerHandle, TRUE);
}

Stage.hへ画像ハンドルを追加

タイルとゴールはStageが管理する

  • ステージの見た目は Stage の責務です
  • タイル画像とゴール画像のハンドルを Stage に持たせます
  • 外部から直接ハンドルを触らせる必要はありません
cpp
class Stage
{
public:
    bool Initialize();
    void Draw() const;

private:
    bool LoadImages();
    void DrawTile(int screenX, int screenY) const;
    void DrawGoal() const;

    int tileHandle = -1;
    int goalHandle = -1;
};

Stageの画像読み込み

InitializeからLoadImagesを呼ぶ

  • Stage::Initialize の最初で画像を読み込みます
  • 読み込み失敗時は false を返します
  • 素材が不足している場合は、ゲーム本編を開始しません
cpp
bool Stage::Initialize()
{
    if (!LoadImages()) return false;

    // ここでマップ初期化を行う
    return true;
}

bool Stage::LoadImages()
{
    tileHandle = LoadGraph(L"Assets/Images/Tile.png");
    if (tileHandle == -1) return false;

    goalHandle = LoadGraph(L"Assets/Images/Goal.png");
    if (goalHandle == -1) return false;

    return true;
}

タイルの画像描画

画像描画を関数にまとめる

  • DrawTile にタイル画像描画をまとめます
  • Stage::Draw は、床タイルの位置を決めて DrawTile を呼ぶだけにします
  • 初期化時に読み込み確認済みなので、描画中に代替処理は行いません
cpp
void Stage::DrawTile(int screenX, int screenY) const
{
    DrawGraph(screenX, screenY, tileHandle, TRUE);
}

ゴールの画像描画

ゴールも必須画像として扱う

  • ゴール画像が読み込めない場合は初期化失敗にします
  • 判定に使う goal の位置と大きさは、描画方法と分けて考えます
  • 描画時は読み込み済みの画像ハンドルを使います
cpp
void Stage::DrawGoal() const
{
    DrawGraph(goal.x, goal.y, goalHandle, TRUE);
}

コードの解説

表示方法とゲーム判定を分ける

  1. 初期化時に必須画像を読み込む
  2. 読み込み結果を画像ハンドルに保存する
  3. -1 が返ったらエラーを表示して false を返す
  4. 初期化に失敗した場合はゲーム本編を開始しない
  5. 描画時は読み込み済み画像を DrawGraph で描く
  • 必須素材が欠けている場合は、早い段階で通告して停止します

よくあるつまずき

パスと実行フォルダを確認する

  • Assets フォルダを実行フォルダへコピーしていない
  • Assets/Images フォルダをソースコードの隣に置いたままにしている
  • 実行ファイルから見た相対パスと、想定している相対パスが違う
  • Player.pngplayer.png の大文字小文字が違う
  • 拡張子が .png ではなく .PNG.jpg になっている
  • 画像サイズがプレイヤー判定サイズと大きく違い、見た目と判定がずれている

実習1: プレイヤー画像を導入する

読み込み失敗をエラーとして確認する

  1. Assets/Images/Player.png を配置する
  2. GameplayerHandleLoadImages を追加する
  3. 読み込み失敗時に false を返す
  4. DrawPlayer は画像描画だけにする
  5. 画像が表示されることを確認する
  6. 画像名を一時的に変えて、エラー表示で停止することを確認する

実習2: タイルとゴール画像を導入する

Stage側の表示を置き換える

  1. Assets/Images/Tile.pngAssets/Images/Goal.png を配置する
  2. StagetileHandlegoalHandle を追加する
  3. DrawTileDrawGoal を作る
  4. 画像ありで描画されることを確認する
  5. 画像不足時にゲームが開始されないことを確認する

発展課題

素材差し替えに対応する

  • 画像サイズを32x32にそろえる
  • プレイヤーの見た目と判定サイズの差を調整する
  • ゴール画像を分かりやすい見た目に差し替える
  • フォルダ名やファイル名を定数としてまとめる
  • 読み込み失敗時に、どのファイルが足りないか分かるメッセージにする

今日の最低到達

見た目を変えてもゲームが動く状態

  • プレイヤー画像が表示できる
  • タイル画像が表示できる
  • ゴール画像が表示できる
  • 画像不足時にエラーを表示して停止できる
  • 移動、ジャンプ、床衝突、ゴール判定が前回と同じように動く

今回のまとめ

画像描画と読み込みエラー確認を導入しました

  • LoadGraph で画像を読み込みました
  • 読み込み失敗時の -1 判定で初期化失敗にしました
  • プレイヤー、タイル、ゴールの描画を画像に置き換えました
  • 必須画像が足りない場合は、エラーを通告してゲーム開始を止めるようにしました
  • 次回は、ステージのマップをCSVファイルから読み込みます

おつかれさまでした!

次回予告 第04回 マップ外部化

地形をコードから外へ 出していきます