ホームへ戻る

11章. 画像等のリソース管理をする

 先ほどプレイヤー画像を表示するために画像のロードをプレイヤークラスのコンストラクタで行いました。
しかし、画像をクラスのインスタンスごとにロードしていると非効率な場面があります。
例えば弾等は何万個とオブジェクトを生成することになりますが、生成するごとにリソースをロードするわけにいきません。
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(音楽)があります。
似たようでありまた別の作り方をするので別の章で説明します。

→分からないことがあれば掲示板で質問して下さい


HPトップへ 質問掲示板へ

- Remical Soft -