敵キャラをランダムに出現させるのではなく、自分が出現させたい順番・種類・間隔で敵キャラを出現させる方法を考える。
いろいろな考え方があるが、今回は一番シンプル(だと思われる)方法を紹介する。
敵キャラを自分の意図した方法で出現させるには、次のデータを持てばよい。
- 前の敵キャラが出てから、次に表示するまでの時間
- 表示する敵キャラのタイプ
例)、1秒ごとに敵キャラを順番に出現させるためのデータ
出現させるまでの時間(ミリ秒) 出現させるタイプ 1000 0 1000 1 1000 2 1000 3 1000 4 1000 0 1000 1 1000 2
- ゲームが開始されてから1,000ミリ秒経過したら、タイプ0の敵キャラを出現
- 前のキャラクタが出現してから1,000ミリ秒経過したら、タイプ1の敵キャラを出現
このようにデータを持つことにより、
ステージ別に出現させる敵キャラを自由に制御できる。このデータを配列に格納し、次のようにプログラムを作成すれば、配列のデータに従って敵キャラが出現する。![]()
敵キャラ出現データは「出現までの時間」と「出現させるキャラの種類」の2つである。このデータを出現させたい数だけ用意する必要がある。
今後、データの項目が増えることを考え、common.hに構造体を作成する。
//----------------------------------------------------------------------------- // 構造体・列挙型 //----------------------------------------------------------------------------- ・ ・ ・ /* 敵キャラ出現データ */ typedef struct tarENEMYDATA { DWORD time; // 出現までの時間 int type; // 出現させるキャラクタの種別 } ENEMYDATA;次に、以下のマクロをcommon.hに宣言する。
#define ENEMY_DATAMAX 100 // 敵キャラデータ件数の最大
#define ENEMY_DATAEND 99999 // 敵キャラデータ終了フラグ※ステージによって出現させる敵キャラの個数が違うため、データの終わりを示すマクロを宣言している
次に、出現データを格納するENEMYDATA構造体の形を持つ配列と、配列の指標に使う変数をGame.cppにグローバル変数として宣言する。
//============================================================================= // Game処理関係の自作関数群 //============================================================================= #include "common.h" //----------------------------------------------------------------------------- // マクロ(ソース内でしか使わないもの) //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // プロトタイプ宣言(ソース内でしか使わないもの) //----------------------------------------------------------------------------- ・ ・ ・ //----------------------------------------------------------------------------- // ゲーム処理用グローバル変数 //----------------------------------------------------------------------------- ・ ・ ・ static ENEMYSTATUS Enemy[ENEMY_MAX]; // 敵キャラのステータス static ENEMYBLTSTATUS EnemyBlt[ENEMYTYPE_MAX]; // 敵キャラ画像データstatic ENEMYDATA EnemyData[ENEMY_DATAMAX]; // 敵キャラ出現データstatic int EnemyCnt; // 敵キャラ出現データのカウント・ ・ ・※EnemyData配列に出現データを格納する
※EnemyData参照用にEnemyCntを利用する(例、EnemyData[EnemyCnt].time)
ゲーム開始時の初期化は次のように行う。
//----------------------------------------------------------------------------- // 関数名 : GameInit() // 機能概要: ゲーム開始時初期化処理 //----------------------------------------------------------------------------- void GameInit(HWND hWnd) { //------------------------------------------------------- キャラクタ用サーフェイスの生成 CreateGameCharaSurface(); //------------------------------------------------------- 各変数の初期化 StargeNumber = 1; // ステージ番号(1面から開始) MyCharaStatusInit(&MyChara, 0, 0, 40, 40, 1, 0); // 自キャラ EnemyBltStatusInit(&EnemyBlt[0], 0, 394, 32, 426, 10, 5, 22, 27, 100); // 敵キャラ0 EnemyBltStatusInit(&EnemyBlt[1], 0, 218, 32, 250, 8, 2, 24, 30, 100); // 敵キャラ1 EnemyBltStatusInit(&EnemyBlt[2], 0, 282, 32, 314, 8, 2, 24, 30, 100); // 敵キャラ2 EnemyBltStatusInit(&EnemyBlt[3], 0, 346, 48, 394, 5, 5, 28, 44, 100); // 敵キャラ3 EnemyBltStatusInit(&EnemyBlt[4], 0, 346, 48, 394, 5, 5, 28, 44, 100); // 敵キャラ4 //------------------------------------------------------- フレームナンバーセット g_FrameNo = GAME_STARGE_INIT; }※敵キャラ出現データはステージ別に作成するため、ここでは何もしない
ステージ別初期化処理は次のようになる。
//----------------------------------------------------------------------------- // 関数名 : GameStargeInit() // 機能概要: ステージ毎初期化処理 //----------------------------------------------------------------------------- void GameStargeInit(HWND hWnd) { int i; /* ゲーム背景用サーフェイスの生成 */ CreateGameBackImgSurface(StargeNumber); // ステージ番号によって、ロードする背景を変える /* 各ステージ毎に初期化する */ MyCharaStatusStargeInit(&MyChara, 300, 400, 2, 2); // 自キャラ for (i=0 ; i<ENEMY_MAX ; i++) // 敵キャラ { Enemy[i].life = DEAD; }EnemyTime = nowTickCount; // 敵キャラ出現時間制御EnemyCnt = 0; // 敵キャラ出現データのカウント/* ステージ別に初期化する */ switch (StargeNumber) { case 1: // 1面での初期化EnemyData[0].time = 500; EnemyData[0].type = 0;EnemyData[1].time = 500; EnemyData[1].type = 0;EnemyData[2].time = 500; EnemyData[2].type = 0;EnemyData[3].time = 500; EnemyData[3].type = 0;EnemyData[4].time = 500; EnemyData[4].type = 0;EnemyData[5].time = 500; EnemyData[5].type = 0;EnemyData[6].time = 500; EnemyData[6].type = 0;EnemyData[7].time = 500; EnemyData[7].type = 0;EnemyData[8].time = 500; EnemyData[8].type = 0;EnemyData[9].time = 500; EnemyData[9].type = 0;EnemyData[10].time = 2000; EnemyData[10].type = 1;EnemyData[11].time = 500; EnemyData[11].type = 1;EnemyData[12].time = 500; EnemyData[12].type = 1;EnemyData[13].time = 500; EnemyData[13].type = 1;EnemyData[14].time = 500; EnemyData[14].type = 1;EnemyData[15].time = ENEMY_DATAEND;break; case 2: // 2面での初期化 break; case 3: // 3面での初期化 break; } //------------------------------------------------------- フレームナンバーセット g_FrameNo = GAME_FRAME; }※1面のみ、出現データをセットしている
※データの終わりを示すため、最後のデータのtimeにENEMY_DATAENDをセットしている
※配列の個数を超えたデータをセットするとバグが発生する
敵キャラは一定時間ごとにランダムに出現させるのではなく、設定したデータにしたがって出現させる。敵キャラ出現処理を次のように作成する。
//----------------------------------------------------------------------------- // 関数名 : SetEnemy() // 機能概要: 出現する敵キャラをセットする //----------------------------------------------------------------------------- static void SetEnemy(void) { int i; /* EnemyDataに従って敵キャラを出現させる */ if (EnemyData[EnemyCnt].time != ENEMY_DATAEND && nowTickCount - EnemyTime >= EnemyData[EnemyCnt].time) { for (i=0 ; i<ENEMY_MAX ; i++) { if (Enemy[i].life == DEAD) { EnemyTime = nowTickCount; EnemyStatusInit(&Enemy[i], EnemyData[EnemyCnt].type); EnemyCnt++; break; } } } }
- 配列の先頭から調べる
- データが残っていて、かつ、出現時間になったかどうかを調べる
- 出現時間になったら、出現させる敵キャラをセットする
- 出現させる敵キャラをセットしたらカウントを+1し、for文を抜ける
ここまでの修正を行い、プログラムを実行すると、セットしたデータの通りに敵キャラが出現する。
2面、3面用のデータをそれぞれセットすれば、ステージが切り替わるとそれに対応してデータにしたがって敵キャラが出現する。
14−3で作成したプログラムに上記修正を行い、1面のデータどおりに敵キャラが出現するかどうかを確認しなさい。
《実行結果サンプル》
2面、3面のデータを自分の好きなように作成し、ステージが切り替わってもステージ別のデータで敵キャラが出現するかどうかを確認しなさい。
敵キャラ出現データをプログラム内に持つのははっきりいって美しくない。よって、データをファイルで管理し、ステージ別にデータファイルを読み込むようプログラムを改造する。
《File.cppの作成》
//============================================================================= // ファイル処理関係の自作関数群 //============================================================================= #include "common.h" //----------------------------------------------------------------------------- // プロトタイプ宣言(ソース内でしか使わないもの) //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // グローバル変数 //----------------------------------------------------------------------------- static char FileName[][20] = { // データファイル名 "", "EnemyData1.dat", "EnemyData2.dat", "EnemyData3.dat" }; //----------------------------------------------------------------------------- // 関数名 : EnemyDataRead() // 機能概要: ステージ別にデータファイルを読み込み、配列にセットする //----------------------------------------------------------------------------- void EnemyDataRead(ENEMYDATA *data, int Number) { FILE *fp; int i; DWORD time; int type; if ((fp = fopen(FileName[Number], "r")) == NULL) { OutputDebugString("敵キャラデータファイル読み込みエラー\n"); return; } i = 0; while(fscanf(fp, "%d,%d", &time, &type) != EOF) { data[i].time = time; data[i].type = type; i++; } data[i].time = ENEMY_DATAEND; fclose(fp); }※ステージ番号は1から始まるため、ファイル名格納配列の0番目には空データを入れる
※格納する配列のアドレスとステージ番号をもらい、該当するデータファイルを読み込んで配列に格納する
※データをすべて読み込んだら、データ終了判定用の値をセットする《File.hの作成》
//----------------------------------------------------------------------------- // File.cppの関数のうち、他のソースで利用される関数のプロトタイプ宣言 //----------------------------------------------------------------------------- void EnemyDataRead(ENEMYDATA *, int);《ステージ別初期化処理の修正》
//----------------------------------------------------------------------------- // 関数名 : GameStargeInit() // 機能概要: ステージ毎初期化処理 //----------------------------------------------------------------------------- void GameStargeInit(HWND hWnd) { int i; /* ゲーム背景用サーフェイスの生成 */ CreateGameBackImgSurface(StargeNumber); // ステージ番号によって、ロードする背景を変える /* 各ステージ毎に初期化する */ MyCharaStatusStargeInit(&MyChara, 300, 400, 2, 2); // 自キャラ for (i=0 ; i<ENEMY_MAX ; i++) // 敵キャラ { Enemy[i].life = DEAD; } EnemyTime = nowTickCount; // 敵キャラ出現時間制御 EnemyCnt = 0; // 敵キャラ出現データのカウントEnemyDataRead(EnemyData, StargeNumber); // 敵キャラ出現データを読み込む/* ステージ別に初期化する */ switch (StargeNumber) { case 1: // 1面での初期化 break; case 2: // 2面での初期化 break; case 3: // 3面での初期化 break; } //------------------------------------------------------- フレームナンバーセット g_FrameNo = GAME_FRAME; }※配列のアドレスを渡すことにより、呼び出す関数に配列データをセットしてもらう
後は、ステージ別のデータファイルを作成すれば、そのデータに従って敵キャラが出現する。
1〜4で作成したプログラムに上記修正を行い、作成したテキストデータどおりに敵キャラが出現するかどうかを確認しなさい。データは自分で用意すること。
データファイルを読み込んで配列に格納する処理は、もっと簡単に書くことができる。
//----------------------------------------------------------------------------- // 関数名 : EnemyDataRead() // 機能概要: ステージ別にデータファイルを読み込み、配列にセットする //----------------------------------------------------------------------------- void EnemyDataRead(ENEMYDATA *data, int Number) { FILE *fp; int i; if ((fp = fopen(FileName[Number], "r")) == NULL) { OutputDebugString("敵キャラデータファイル読み込みエラー\n"); return; }for (i=0 ; fscanf(fp, "%d,%d", &data[i].time, &data[i].type) != EOF; i++);data[i].time = ENEMY_DATAEND; fclose(fp); }なぜこれでOKなのか、考えてみよう。