シーン管理とは?
ゲームの開発をしていると必ずシーンの管理をする必要が出てきます。
- タイトル画面のシーン
- ゲーム本編のシーン
- ゲームクリアのシーン
簡単なゲームでもこれぐらいのシーンはあるんじゃないでしょうか?
シーン管理とは簡単に言えば画面遷移の管理みたいなものですね。
実際にはどの単位でシーン遷移するのかゲームによって異なりますが、上記のような範囲でシーンを別ける事が多いです。
このシーン管理はシーン数が多くなってくるとソースコードが増えてどんどん大変になってきます。
シーン管理の実装例
最初にプログラムを始めた人がやるシーン管理は大体こんな感じになるかと思います。
// シーン enum SCENE { SCENE_TITLE, SCENE_GAME, SCENE_CLEAR, SCENE_NONE = -1, }; // シーンの切り替え void SetScene(SCENE scene); // 現在のシーン int GetScene(void);
ゲームのシーン管理処理としてシーンの設定関数
そしてゲームループでswitch-case文を使用して
void MainLoop(void) { SetScene(SCENE_TITLE); while( ~メインループ~ ) { // 現在のシーンを取得 int CurrentScene = GetScene(); switch( CurrentScene ) { case SCENE_TITLE: ~タイトル更新処理~ break; case SCENE_GAME: ~ゲーム更新処理~ break; case SCENE_CLEAR: ~クリア画面更新処理~ break; } // シーンが切り替わっていた場合に if( CurrentScene != GetScene() ) { ~シーンの解放と次のシーンの初期化~ } } }
これだとシーンが増えるたびにメインループが増えていって大変ですよね。
でもこうやってシーン管理する人が多いと思います。
自分もC言語でプログラムをしていた学生の頃はこんなプログラムを書いていました。
でもこれだとcaseがどんどん長くなってきて読みにくくなりますよね。
なので今回はもう少し見やすいシーン管理システムをC言語で作ってみましょう!
switch-case文にしていた理由は主に4つの関数(初期化・解放・更新・描画)がそれぞれ異なるからです。
それでは異なる関数を処理を分けずに呼び出すにはどうしたらいいでしょうか?
方法の一つとして関数ポインタがあります。
今回はこの関数ポインタを使ってシーン管理システムを実装してみたいと思います。
関数ポインタとは?
関数ポインタとは関数のアドレスを保持する事の出来る型のことです。
触ったことのない人は?が頭の中に出たかと思います。
変数ポインタは変数のアドレスを保持するものでしたが、関数にもアドレスが割り当てられています。
その関数のアドレスを保持する型のポインタを作成することで関数を別の場所から呼び出すことができます。
具体例としては下記のようになります。
// テスト関数 void Print(const char* str) { printf(str); } // エントリーポイント void main(void) { // 戻り値無し。(const char*)を引数に持つ関数ポインタのFuncを作成 void (*Func)(const char*); // FuncにPrint関数のアドレスを設定 Func = Print; // Funcに設定されているPrint関数の呼び出し Func("test"); }
Funcとして定義した関数ポインタにPrint関数のアドレスを渡すことでFunc(“test”)でPrint関数を呼び出しています。
これをコンソールで実行すると結果に[test]と表示されるはずです。
このように関数ポインタを使うことで間接的に関数を呼び出すことが出来ます。
そして関数ポインタを応用することで様々な実装方法が可能になります。
// PrintFuncとしてvoid (*)(const char*)の型を定義 using PrintFunc = void(*)(const char*); void main(void) { // PrintFunc型の関数ポインタのFuncを作成 PrintFunc Func = Print; Func("test"); }
usingで定義することにより関数ポインタの型だけを定義することもできます。
これでも結果は同じです。
関数ポインタは実際の開発の現場でも多用されていることが多いです。
他の言語でも似た仕様が色々とあるので汎用性の高さが伺えます。