


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