ゲームのシーン管理の話
4×4個のパネルを音に合わせて押すゲームを作っている会計です。
会長から、音ゲー班でもブログ記事書いてって言われたので、
ソフトウェア担当の私が適当に好き勝手書きます。
といっても、今作っているのがどんなゲームかを紹介するという
記事では著作権とか色々とまずそうなので、
私の書いてるプログラムのシーン管理システムでも紹介します。
シーン、というのは、ゲーム上の各場面のことです。
例えば、格闘ゲームなら、戦闘、キャラ選択、オープニングなどの、
それぞれの場面一つ一つを、戦闘シーン、キャラ選択シーン、等と言います。
……一般的な定義になってるかどうかはさておき、
この記事ではそういう定義です。
多数のシーンを管理するプログラムでは、
シーンを管理するIDを定義し、それをグローバル関数などに格納して、
各シーンごとの処理に分岐させる、というのが一般的なようです。
C++で書くなら、こんな感じ。
// シーンIDの定義
#define PLAY_SCENE 0
#define RESULT_SCENE 1
#define START_SCENE 2
以下IDの定義が延々と続く
int sceneID; // シーン管理変数
この辺に色々な関数がある
// WinMainのメッセージループから呼ばれる
int SwitchScene() {
  switch(sceneID) {
    case PLAY_SCENE:
      return PlayScene();
    case RESULT_SCENE:
      return ResultScene();
    case START_SCENE:
      return StartScene();
    ...以下略
  }
}
私が最初にこれを見た時、「こんなのC++じゃねえ!」と思いました。
とりあえず、オブジェクト指向的ではありません。
もちろん、使っているものが高等であればいいって訳じゃありません。
問題は、保守しやすく、改変が容易であるかどうかです。
そういう意味で、この仕組みには、いくつか欠点があります。
1つ目は「シーンを追加する毎に、手を入れる箇所が3つもある」という点。
IDの定義、SwitchScene関数内、シーン自体を処理する関数、
この3つを、各シーンごとに追加しなくてはなりません。
どうせなら、一箇所にまとめたいものです。その方が、ミスも減ります。
2つ目は「使っているリソースが管理しずらい」という事。
普通、一つのシーンが終わるまでは、そこで使用する画像や音楽などは、
メモリ上に保持しておかなければなりません。
他にも、グラフィックドライバへのポインタだとか、
プログラム終了まで必要な物もあります。
では、それらの保持されたリソースは、
この仕組みでどのように管理するのでしょうか。
全部グローバル変数でポインタを保持?
もし私がそんな仕組みでプログラムを書いたなら、
間違えて、どっかで解放しちゃってて、
うっかりヌルポインタ使って強制終了とか、
そんな事態が、コンパイル一回につき6割ぐらいの確率で起こります。
出来れば、そういう敏感な部分は、
privateな場所に隔離したいものです。
そこで、こんな仕組みを考えました。
やってることは、さっきと同等です。
// シーンクラスの基底
class SceneBase {
public:
  // シーンの処理をする関数(返り値はエラーの有無)
  virtual bool Run(引数は、シーンに共通して必要なものを入れる)=0;
};
class PlayScene : public SceneBase {
private:
  PlaySceneで必要な物のみを定義する
public:
  virtual bool Run(SceneBaseと同様) {
    PlaySceneの処理
  }
};
class ResultScene : public SceneBase {
private:
  ResultSceneで必要な物のみを(ry
public:
  virtual bool Run(SceneBaseと同様) {
    ResultSceneの処理
  }
};
class StartScene : public SceneBase {
private:
  StartSceneで必要な(ry
public:
  virtual bool Run(SceneBaseと同様) {
    StartSceneの処理
  }
};
// Singletonにして、二重生成、二重解放を防ぐ
class Game {
private:
  ここにグラフィックドライバへのポインタとかを書く
  SceneBase *_nowScene, *_nextScene;
public:
  // 次に呼ばれるシーンをセット
  bool SetNextScene(SceneBase *next) {
    if (next==NULL) return true;
    _nextScene=next;
    return false;
  }
  // WinMainのメッセージループから呼ばれる
  bool Run() {
    if (_nowScene==NULL) return true;
    if (_nextScene!=_nowScene) { // シーン切り替え処理
      delete _nowScene;
      _nowScene=_nextScene;
    }
    return _nowScene->Run(シーン共通で必要なもの);
  }
};
オブジェクト指向の、仮想関数の機能を使って、
シーン追加時の書き足し箇所を、一箇所にしました。
また、リソースの管理場所は、各所に区分けしました。
コンストラクタ・デストラクタは省略していますが、
各所のリソースを確保・解放する処理が入っています。
GameクラスのSetNextSceneの引数は、
SceneBaseを継承したクラスを、newで生成したものを渡します。
一旦_nextSceneに次のシーンへのポインタを保持しているのは、
もし仮に_nowScene=next;としてしまうと、
今まで_nowSceneに入っていたオブジェクトが破棄できなくなるからです。
コードは色々と省略しましたけど、
オブジェクト指向を知ってる人なら分かる! はず!
……そう信じていますが、分かりにくかったら補足しますので、
この記事のコメントででも質問してください。
今回はこんな感じで終わります。
次回があるかどうかは未定。