第08回 画像リソースの管理
4 / 4 画像管理の動作確認と差し替え実験
このステップの目的
読み込みとエラー処理を確認し、画像を差し替えて、素材とゲームの処理が独立していることを確かめます。
動作確認と素材差し替え実験
画像管理システムの動作を確かめるために、以下の2つの実験と確認を行います。
実験1:画像素材の差し替え実験
- プロジェクト直下の
Assets/Images/Player.pngを別名でコピーして残し、サイズが異なる画像に一時的に差し替えます。 - ビルドして実行し、機体の大きさ(
DrawRect)と当たり判定(Hitbox)が変わらず、正常にプレイできることを確認してください。 - 実験を終えたら、元の
Player.pngに戻します。
実験2:存在しないファイルのエラーハンドリング確認
ImageManager.cppの対応表で、一時的にファイルパスをL"Assets/Images/NotFound.png"などの存在しないファイル名に書き換えます。- ビルドして実行し、初期化を中断する「リソース読み込みエラー」のメッセージに、失敗した対象
Assets/Images/NotFound.pngが表示されることを確認します。 - 確認後、正しいパスに戻してビルド・再実行し、ゲームが開始できることまで確かめます。
チェックリスト
- すべての画像が起動時に一括して正しく読み込まれることを確認できる
- 画像素材を差し替えても、表示矩形と当たり判定が維持されることを確認できる
- 存在しない画像パスを指定した際、クラッシュせずに安全なエラーメッセージが表示されることを確認できる
- ゲーム終了時にデストラクタによってすべてのグラフィックハンドルが自動解放されていることを説明できる
まとめと次回への引き継ぎ
第08回では、画像の読み込みから解放までを管理するImageManagerを題材にしました。所有権とRAII、素材の寸法とゲーム処理を分ける設計を学びました。
第09回ではRAIIを音声にも使い、BGMとSEの管理・再生を実装します。SoundManagerを作り、射撃、被弾、撃破などの出来事に合わせて、迫力のある音を鳴らします。
参考実装
今回追加・変更するファイルの一例です。宣言や補助処理を確認したいときに開いてください。自分のコードへ必要な変更を反映し、既存の調整値や素材をそのまま上書きしないようにします。
Source/Shooting/ImageManager.cpp
Source/Shooting/ImageManager.cppcpp
#include <array>
#include <cstddef>
#include <DxLib.h>
#include "Shooting/ImageManager.h"
namespace dxgame::shooting
{
namespace
{
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" }
};
std::size_t ToIndex(ImageId id)
{
return static_cast<std::size_t>(id);
}
}
ImageManager::ImageManager()
{
handles.fill(-1);
}
ImageManager::~ImageManager()
{
for (int handle : handles)
{
if (handle != -1)
DeleteGraph(handle);
}
}
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, {} };
}
void ImageManager::Draw(Id id, const Rect& rect) const
{
int handle = handles[ToIndex(id)];
DrawExtendGraph(
static_cast<int>(rect.x),
static_cast<int>(rect.y),
static_cast<int>(rect.x + rect.width),
static_cast<int>(rect.y + rect.height),
handle,
TRUE);
}
}Source/Shooting/ImageManager.h
Source/Shooting/ImageManager.hcpp
#pragma once
#include <array>
#include <cstddef>
#include <string>
#include "DxGame/Rect.h"
namespace dxgame::shooting
{
// 画像の読み込み、ハンドルの所有、拡大縮小描画、解放を管理する
class ImageManager
{
public:
// ゲーム内で使う画像を種類ごとに識別する
enum class Id
{
Player,
PlayerShot,
Enemy,
EnemyShot,
Background,
Count
};
// 全ハンドルを未取得の状態で初期化する
ImageManager();
// 取得済みの画像ハンドルをすべて解放する
~ImageManager();
// ハンドルを二重に解放しないようコピー構築を禁止する
ImageManager(const ImageManager&) = delete;
// ハンドルを二重に解放しないようコピー代入を禁止する
ImageManager& operator=(const ImageManager&) = delete;
// 画像の読み込み結果と、失敗した場合の相対パスを表す
struct LoadResult
{
bool succeeded = true;
std::wstring failedPath;
};
// 対応表にある画像をすべて読み込む
// 1つでも読み込めない場合は、その画像の相対パスとともに失敗を返す
LoadResult LoadAll();
// idの画像をrectの大きさへ拡大縮小して描く
void Draw(Id id, const Rect& rect) const;
private:
std::array<int, static_cast<std::size_t>(Id::Count)> handles = {};
};
}