ゲームでは、似たような物を沢山作らなければならないことがあります。
雑魚的や弾のような物は同じモジュールを使いまわして実現したいものです。
d.5節までで、その仕組みはできあがりましたが、メイン関数に具体的なマジックナンバーを書いてしまっていました。
ゲーム制作にあたって今後、必要なモジュールはドンドン増えていきます。
雑魚的、ボス、弾、エフェクト、背景、スコアボード、プレイヤー・・・。
特に弾が最大2000個あるとすれば、2000個の初期化関数をメイン関数に書かなければならないのでしょうか。
そんなわけありませんよね。
特定の区分で分割して、「管理部」を作り、管理する必要が出てきます。
例えば「敵」が10体いるなら「敵管理部」が敵を持ち、敵管理部が敵を管理すべきです。
メイン関数は「敵管理部」に指示を出せばよく、末端の一つ一つの敵の動作は知る必要が無いようにすべきでしょう。
d.5節までにプレイヤーを2体作りました。
ここでは「プレイヤー管理部」を作り、メイン関数からは特に個別のプレイヤーを意識する必要が無いようにしてみましょう。
まず管理部を作りましょう。(Mgrは管理部つまりマネージャーManagerの略です。MnrとかMngとか他の略しも見かけますが、うちはMgrで統一します)
↓PlayerMgr.h (新規追加)
#ifndef DEF_PLAYERMGR_H //二重include防止 #define DEF_PLAYERMGR_H // 初期化をする void PlayerMgr_Initialize(); // 動きを計算する void PlayerMgr_Update(); // 描画する void PlayerMgr_Draw(); // 終了処理をする void PlayerMgr_Finalize(); #endif
今回も基本構造はあのお約束の通りです。
・初期化
・更新
・描画
・終了処理
があります。
ポイントは、Playerモジュールのインスタンスはこのプレイヤー管理部が持つことです。
d.5節でmain関数がやっていた仕事をただ、本モジュールに移しただけです。
(その代わりすぐ追加できるようにfor文で回すように変えています)
↓PlayerMgr.cpp (新規追加)
#include "DxLib.h" #include "Keyboard.h" #include "Player.h" static const int NUM = 2; //プレイヤーの数 static Player_t m_Player[NUM]; //プレイヤーの実体 static int m_ImgPlayer; //プレイヤーの画像ハンドル // 初期化をする void PlayerMgr_Initialize(){ m_ImgPlayer = LoadGraph("画像/キャラクタ01.png"); Player_Initialize( &m_Player[0], 0, m_ImgPlayer );// 初期化 Player_Initialize( &m_Player[1], 200, m_ImgPlayer );// 初期化 } // 動きを計算する void PlayerMgr_Update(){ for( int i=0; i<NUM; i++ ){ Player_Update(&m_Player[i]);//更新 } } // 描画する void PlayerMgr_Draw(){ for( int i=0; i<NUM; i++ ){ Player_Draw(m_Player[i]);//描画 } } // 終了処理をする void PlayerMgr_Finalize(){ for( int i=0; i<NUM; i++ ){ Player_Finalize(m_Player[i]);//終了処理 } DeleteGraph( m_ImgPlayer ); //画像を解放 }
※constは、値を変えることが出来ない変数に付ける演算子です。使える時は積極的に使いましょう。
Player部の変更点は、
自分で画像をロードしていたのを、引数に持たせたハンドルを格納するように変えただけです。
↓Player.h (赤字部修正)
#ifndef DEF_PLAYER_H //二重include防止 #define DEF_PLAYER_H typedef struct{ int Image; int y; } Player_t; // 初期化をする void Player_Initialize( Player_t *Player, int y, int img ); // 動きを計算する void Player_Update( Player_t *Player ); // 描画する void Player_Draw( Player_t Player ); // 終了処理をする void Player_Finalize( Player_t Player ); #endif
↓Player.cpp (赤字部修正)
#include "DxLib.h" #include "Keyboard.h" #include "Player.h" // 初期化をする void Player_Initialize( Player_t *Player, int y, int img ){ Player->Image = img; //画像ハンドルの格納 Player->y = y; //y座標格納 } // 動きを計算する void Player_Update( Player_t *Player ){ if( Keyboard_Get( KEY_INPUT_UP ) > 0 ){//上が押されていたら Player->y--; } if( Keyboard_Get( KEY_INPUT_DOWN ) > 0 ){//下が押されていたら Player->y++; } } // 描画する void Player_Draw( Player_t Player ){ DrawGraph( 0, Player.y, Player.Image, TRUE ); } // 終了処理をする void Player_Finalize( Player_t Player ){ //特になし }
そうすると、メイン関数は全く末端のPlayerを意識することが必要無くなりました。
↓main.cpp (赤字部修正)
#include "DxLib.h" #include "PlayerMgr.h" #include "Keyboard.h" int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int){ ChangeWindowMode(TRUE),DxLib_Init(),SetDrawScreen( DX_SCREEN_BACK ); PlayerMgr_Initialize(); //プレイヤー管理モジュール初期化 while( ScreenFlip()==0 && ProcessMessage()==0 && ClearDrawScreen()==0 ){ Keyboard_Update(); //キーボードの更新 PlayerMgr_Update(); //プレイヤー管理モジュールの更新 PlayerMgr_Draw(); //プレイヤー管理モジュールの描画 } PlayerMgr_Finalize(); //プレイヤー管理モジュールの終了処理 DxLib_End(); return 0; }
このように敵も、エフェクトも、弾も、似た要素を沢山もつ物は管理部を作り、管理し、
メイン部からはそのモジュールを呼び出すだけにしてやれば、呼び出し元がすっきりします。
コードを最初に見るのはメイン関数ですから、メイン関数を初め、呼び出し元をすっきり見やすくすることは非常に大事なことです。
プロジェクトのダウンロード
実行結果(さっきと一緒)
- Remical Soft -