#はじめに
ゲームを作っていてポーズを実装したいときに例えば
void Main()
{
bool isPause=false;
while (System::Update())
{
if (Input::MouseL.clicked)
{
isPause = !isPause;
}
if (!isPause)
{
//更新
//Update();
}
//描画
//Draw();
}
}
このようにしたり、
HamFrameworkのSceneManager
を使っている場合は
void update()override
{
if (Input::MouseL.clicked)
{
m_isPause = !m_isPause;
}
if (m_isPause)
return;
/*
処理
*/
}
このようにして更新の処理を呼ばない用にして対処することができます。
しかし、経過時間に依存する
Stopwatch
EasingController
Effect
などを使用して実装していた場合は、これら全てのpause
関数も呼び出さないと意図していない動作になってしまう場合があります。
数が少ないときは問題ないかもしれませんが、ゲームの規模が大きくなったり細かい演出のためにEasingを多用したりした時に全てのpause
関数を呼ぶのは大変だったりします。
自分もゲームを作りこんだ後にポーズを実装しようとした時に、この問題に苦労したことがありました。
そこで今回は、これら全てをたった1行の関数を呼ぶだけで止めたり再開できるようにしたいと思います。
#方針
では、それを実装するためにどのようにするかの方針をざっくり説明します。
- ポーズが必要なクラスは
IPause
クラスを継承する- ポーズを行う
pause
関数をオーバーライド - リジュームを行う
resume
関数をオーバーライド
- ポーズを行う
- ポーズを管理する
PauseManager
をつくる- メンバ変数にポーズが必要なインスタンスへのポインタのリスト
std::list<IPause*>
をもつ - 全てのポーズを行うメンバ関数
pauseAll
をもつ - 全てのリジュームを行うメンバ関数
resumeAll
をもつ
- メンバ変数にポーズが必要なインスタンスへのポインタのリスト
-
IPause
を継承したクラスのインスタンスの生成時に、自身のポインタをPauseManager
のリストにわたす -
IPause
を継承したクラスのインスタンスの破棄時に、PauseManager
のリストから自身へのポインタを削除する
#実装
実際に上のような方針で実装してみた一例
今回はPuaseManagerをシングルトンパターンにして実装していますが必ずしもそうである必要はありません。
#pragma once
#include<Siv3D.hpp>
class IPause;
//-------------------------------------------------------
// PauseManager
//-------------------------------------------------------
//シングルトンで実装
class PauseManager final : Uncopyable
{
friend IPause;
private:
PauseManager() = default;
//管理するリスト
std::list<IPause*> m_pauseList;
void pauseAll();
void resumeAll();
//リストへデータの追加
void add(IPause*const pause)
{
m_pauseList.emplace_back(pause);
}
//リストからデータの削除
void erase(const IPause*const pause)
{
s3d::Erase(m_pauseList, pause);
}
static PauseManager& Instance()
{
static PauseManager instance;
return instance;
}
static void Add(IPause*const pause)
{
Instance().add(pause);
}
static void Erase(const IPause*const pause)
{
Instance().erase(pause);
}
public:
//すべてポーズ
static void Pause()
{
Instance().pauseAll();
}
//すべてリジューム
static void Resume()
{
Instance().resumeAll();
}
};
//-------------------------------------------------------
// IPause
//-------------------------------------------------------
//ポーズさせたいクラスはこれを継承
class IPause
{
public:
IPause()
{
PauseManager::Add(this);
}
virtual ~IPause()
{
PauseManager::Erase(this);
}
virtual void pause() = 0;
virtual void resume() = 0;
};
//-------------------------------------------------------
//すべてポーズ
void PauseManager::pauseAll()
{
for (auto&& elm : m_pauseList)
{
elm->pause();
}
}
//すべてリジューム
void PauseManager::resumeAll()
{
for (auto&& elm : m_pauseList)
{
elm->resume();
}
}
今回の実装方法は2つのclassが絡み合うので宣言や定義順を考慮する必要があります。
##Stopwatchなどに適応させるために
ここまで実装できたら、実際にStopwatchやEasingContlloerに適応してみましょう。
class PauseableStopwatch:
public IPause,
public Stopwatch
{
public:
//コンストラクタ
PauseableStopwatch(bool startImmediately = false):
Stopwatch(startImmediately)
{}
void pause()override
{
this->Stopwatch::pause();
}
void resume()override
{
this->Stopwatch::resume();
}
};
これで、PauseableStopwatch
のインスタンスはPauseManager::Pause
関数を呼べばポーズされるようになりました。
同様にEasingControllerやEffectもやってもいいですが、このようにそれぞれ対応する新しいクラスを作っていては大変なのでtemplateを使用すると1つのクラスですみます。
/*
Stopwatch Effect EasingControllerはメソッド名が共有なのでtemplateでやんちゃする
*/
template<class Type>
class Pauseable:
public IPause,
public Type
{
public:
//コンストラクタの引継ぎ
using Type::Type;
void pause()override
{
this->Type::pause();
}
void resume()override
{
this->Type::resume();
}
};
using 基底クラス名::基底クラス名;
と書くことで基底クラスのコンストラタを引き継いで省略することできます。
#サンプル
実際に使ってみる
# include <Siv3D.hpp>
#include"PauseManager.h"
template<class Type>
class Pauseable:
public IPause,
public Type
{
public:
//コンストラクタの引継ぎ
using Type::Type;
void pause()override
{
this->Type::pause();
}
void resume()override
{
this->Type::resume();
}
};
void Main()
{
Pauseable<EasingController<int>> easing(0,1000,Easing::Quad,5000);
Pauseable<Stopwatch> stopwatch(true);
bool isPause=false;
while (System::Update())
{
if (!easing.isActive())
{
easing.start();
}
if (Input::MouseL.clicked)
{
if (isPause)
{
PauseManager::Resume();
}
else
{
PauseManager::Pause();
}
isPause = !isPause;
}
ClearPrint();
Println(easing.easeInOut());
Println(stopwatch.ms());
}
}
#まとめ
- 経過時間に依存するものはポーズしたいときに管理が大変になる
- インスタンスへのポインタのリストを作っておくことでまとめてポーズできる