先ほどプレイヤー画像を表示するために画像のロードをプレイヤークラスのコンストラクタで行いました。
しかし、画像をクラスのインスタンスごとにロードしていると非効率な場面があります。
例えば弾等は何万個とオブジェクトを生成することになりますが、生成するごとにリソースをロードするわけにいきません。
2つしかないオブジェクトだとしても、同じ画像を2か所でロードするのは非効率ですし、
どこか1か所でも解放漏れがあるとメモリリークしてしまいます。
従って画像等のリソース管理は1か所で行うようにします。
さらにロードした画像を覚えて置き、同じ画像を2枚ロードを重ねてメモリリークしたり解放漏れによるリークが無いように設計します。
では画像のリソース管理をするImageクラスの実装をみてみましょう。
Image.h
#pragma once #include "Singleton.h" #include <vector> class Image final : public Singleton<Image> { public: Image(); ~Image() = default; void load(){} void release(); int getPlayer() const; private: int myLoadGraph(char*); int myLoadDivGraph(char *fileName, int n, int xn, int yn, int w, int h, int* buf); std::vector<int> _images; int _player; };
Image.cpp
#include "Image.h" #include <DxLib.h> Image::Image() { _player = myLoadGraph("../dat/image/player/body/seishi0.png"); } /*! @brief 今までロードした画像を解放する */ void Image::release() { const int size = _images.size(); for (int i = 0; i < size; i++) { DeleteGraph(_images[i]); } _images.clear(); } /*! @brief プレイヤーの画像を取得する */ int Image::getPlayer() const { return _player; } /*! @brief LoadGraphをして、かつそのハンドルをメンバ変数に追加する */ int Image::myLoadGraph(char *fileName) { int ret = LoadGraph(fileName); _images.push_back(ret); return ret; } /*! @brief LoadDivGraphをして、かつそのハンドルをメンバ変数に追加する */ int Image::myLoadDivGraph(char *fileName, int n, int xn, int yn, int w, int h, int* buf) { int ret = LoadDivGraph(fileName, n, xn, yn, w, h, buf); for (int i = 0; i < n; i++) { _images.push_back(buf[i]); } return ret; }
ここで着目すべきはmyLoadGraphです。
LoadGraphというDXライブラリの関数をラップしたものです。
LoadGraphされてハンドルが返ってくるとき、そのハンドルをメンバ変数のリストに追加してから返します。
画像のロードは全てmyLoadGraphを経由することで、全ての画像リソースへのハンドルを持つことが出来、全ての画像解放作業が楽にできるようになります。
ここのクラスにロードと解放処理があると、どこかにメモリリークが目を凝らしてみる必要がありますが、この仕組みならメモリリークのしようがありません。
また、load()関数が空っぽなのは、LooperのコンストラクタでImageのコンストラクタを起動したいためにあるからです。
Imageはシングルトンクラスなので、初めてアクセスした時にコンストラクタが走ります。
実際のロード処理はコンストラクタでやっているので、loadが呼ばれた時にコンストラクタが呼ばれて画像がロードされます。
ゆくゆくはこのload処理をシーンごとにやれるように変更します。
さて、使用しているPlayerクラスと、loadを呼んでいるLooperクラスを見てみましょう。
Player.cpp
#include "Player.h" #include "Pad.h" #include <DxLib.h> #include "Image.h" const static float SPEED = 9; Player::Player() : _x(100), _y(100) { } bool Player::update() { move(); return true; } void Player::draw() const { DrawRotaGraphF(_x, _y, 1.0, 0.0, Image::getIns()->getPlayer(), TRUE); } /*! @brief プレイヤーを動かす */ void Player::move() { float moveX = 0, moveY = 0; if (Pad::getIns()->get(ePad::left) > 0) { moveX -= SPEED; } if (Pad::getIns()->get(ePad::right) > 0) { moveX += SPEED; } if (Pad::getIns()->get(ePad::down) > 0) { moveY += SPEED; } if (Pad::getIns()->get(ePad::up) > 0) { moveY -= SPEED; } if (moveX && moveY) { //斜め移動 moveX /= (float)sqrt(2.0); moveY /= (float)sqrt(2.0); } if (Pad::getIns()->get(ePad::slow) > 0) {//低速移動 moveX /= 3; moveY /= 3; } _x += moveX; _y += moveY; }
Looper.cpp
#include "Looper.h" #include "TitleScene.h" #include "Error.h" #include "GameScene.h" #include "Macro.h" #include "Keyboard.h" #include "Pad.h" #include "Image.h" using namespace std; Looper::Looper() { Image::getIns()->load(); Parameter parameter; _sceneStack.push(make_shared<GameScene>(this, parameter)); //ゲーム画面シーンを作ってpush } /*! @brief スタックのトップのシーンの処理をする */ bool Looper::loop() { Keyboard::getIns()->update(); //キーボードの更新 Pad::getIns()->update(); //ジョイパッドの更新 _sceneStack.top()->update(); //スタックのトップのシーンを更新 _sceneStack.top()->draw(); //スタックのトップのシーンを描画 _fps.draw(); //FPSの表示 _fps.wait(); //設定したFPSになるように待機 return true; } /*! @brief シーン変更(各シーンからコールバックされる) @param scene 変更するシーンのenum @param parameter 前のシーンから引き継ぐパラメータ @param stackClear 現在のシーンのスタックをクリアするか */ void Looper::onSceneChanged(const eScene scene, const Parameter& parameter, const bool stackClear) { if (stackClear) {//スタッククリアなら while (!_sceneStack.empty()) {//スタックを全部ポップする(スタックを空にする) _sceneStack.pop(); } } switch (scene) { case Title: _sceneStack.push(make_shared<TitleScene>(this, parameter)); break; case Game: _sceneStack.push(make_shared<GameScene>(this, parameter)); break; default: ERR("あるはずのないシーンが呼ばれました"); break; } }
リソースにはImageのほかにSE(効果音)とBGM(音楽)があります。
似たようでありまた別の作り方をするので別の章で説明します。