前項で、敵キャラをタイプ別に移動させることはできるようになった。ここでは、自分が出現させたい順番・種類・間隔で敵キャラを出現させる方法を紹介する。
いろいろな考え方があるが、今回は、敵キャラの出現パターンをデータで管理する方法を紹介する。出現パターンをデータで管理すれば、プログラムの修正を行うことなく、敵キャラを思い通りに出現させることができる。
敵キャラを自分の意図した方法で出現させるには、最低限、次のデータを持てばよい。
例えば、次のようなデータになる。
| 出現させるまでの時間 (ミリ秒) | 出現させる タイプ | 備考 |
|---|---|---|
| 1000 | 0 | ゲーム開始から1000ミリ秒経過したら、タイプ0の敵キャラを出現させる |
| 500 | 0 | 前の敵キャラを出現させてから500ミリ秒経過したら、タイプ0の敵キャラを出現させる |
| 500 | 0 | |
| 500 | 0 | |
| 500 | 0 | |
| 1000 | 1 | |
| 300 | 1 | |
| 300 | 2 | |
| 300 | 2 | |
| 300 | 1 | |
| 300 | 2 | |
| 1500 | 3 | |
| 100 | 4 | |
| 100 | 3 | |
| 100 | 4 | |
| 100 | 3 | |
| 100 | 4 |
このようにデータを持つことにより、ステージ別に出現させる敵キャラを自由に制御できる。このデータを配列に格納し、次のようにプログラムを作成すれば、配列のデータに従って敵キャラが出現する。

では実際に、プログラムに組み込む方法を解説する。
敵キャラ出現データは「出現までの時間」と「出現させるキャラの種類」の2つである。このデータを出現させたい敵キャラ数だけ用意する必要がある。
他のソースから利用する(かもしれない)ことを考え、NKC_Common.hに構造体を作成する。
/* 敵キャラ出現データ */
typedef struct _ENEMYOUTDATA {
DWORD time; // 出現までの時間
int type; // 出現させるキャラクタの種別
} ENEMYOUTDATA, *LPENEMYOUTDATA;
敵キャラの出現はゲーム処理で行うため、それらの処理で必要なグローバル変数はGame.cppに宣言するのがいいだろう。次のようにプログラムを修正する。
//============================================================================= // ゲーム処理関係の自作関数群 // Copyright NKC Game Staff(←自分の名前) //----------------------------------------------------------------------------- #include "NKC_Common.h" // マクロ #define STAGEMAX 3 // ステージ数 #define ENEMYMAX 100 // 敵キャラの最大数 //#define ENEMYTIME 1000 // 敵キャラの出現タイミング #define ENEMYDATAMAX 100 // 敵キャラデータ件数の最大 #define ENEMYDATAEND 99999 // 敵キャラデータ終了フラグ // グローバル変数(自ソースでのみ使用するもの) /* 自ソースでのみ利用するもの */ //---- ステージ別情報 static int gl_StageNo; // ステージ番号管理 //---- 背景 static TLVERTX BackVertex[4]; // 頂点情報配列 //---- 自機 static STATUS MyChara; // 自キャラステータス情報 //---- 敵 static STATUS EnemyInitData; // 敵キャラステータス情報初期化データ static STATUS Enemy[ENEMYMAX]; // 敵キャラステータス情報 static DWORD gl_EnemyTime; // 敵キャラ出現時間 static ENEMYOUTDATA EnemyOutData[ENEMYDATAMAX];// 敵キャラ出現データ static int gl_EnemyCnt; // 敵キャラ出現データのカウント ・ ・ ・
作成した出現データ用配列に、出現データをセットする処理を追加する。本来ならファイルから読み込むべきだが、とりあえず、プログラム内にデータを記述する。
//-----------------------------------------------------------------------------
// 関数名 : GameStageInit()
// 機能概要: ステージ開始時初期化処理
//-----------------------------------------------------------------------------
void GameStageInit(void)
{
int i;
//--------------------------------------------------- 各変数の初期化
// ステージ別に使用するテクスチャの作成
CreateGameStageTexture(gl_StageNo);
// ポリゴンの初期化
/* 自キャラ */
InitVertex(MyChara.Vertex, 272.0f, 380.0f, 368.0f, 476.0f, 255); // 頂点データ
SetRect(&MyChara.HitRect, 10, 10, 10, 10); // 当たり判定矩形
MyChara.MoveX = 2.0f;
MyChara.MoveY = 2.0f;
/* 敵キャラ出現データのカウント */
gl_EnemyCnt = 0;
/* 敵キャラ出現データをセット(ステージ別に) */
switch ( gl_StageNo ) {
case 1: // 1面での初期化
EnemyOutData[0].time = 1000; EnemyOutData[0].type = 0;
EnemyOutData[1].time = 500; EnemyOutData[1].type = 0;
EnemyOutData[2].time = 500; EnemyOutData[2].type = 0;
EnemyOutData[3].time = 500; EnemyOutData[3].type = 0;
EnemyOutData[4].time = 500; EnemyOutData[4].type = 0;
EnemyOutData[5].time = 1000; EnemyOutData[5].type = 1;
EnemyOutData[6].time = 300; EnemyOutData[6].type = 1;
EnemyOutData[7].time = 300; EnemyOutData[7].type = 2;
EnemyOutData[8].time = 300; EnemyOutData[8].type = 2;
EnemyOutData[9].time = 300; EnemyOutData[9].type = 1;
EnemyOutData[10].time = 300; EnemyOutData[10].type = 2;
EnemyOutData[11].time = 1500; EnemyOutData[11].type = 3;
EnemyOutData[12].time = 100; EnemyOutData[12].type = 4;
EnemyOutData[13].time = 100; EnemyOutData[13].type = 3;
EnemyOutData[14].time = 100; EnemyOutData[14].type = 4;
EnemyOutData[15].time = 100; EnemyOutData[15].type = 3;
EnemyOutData[16].time = 100; EnemyOutData[16].type = 4;
EnemyOutData[17].time = ENEMYDATAEND;
break;
case 2: // 2面での初期化
break;
case 3: // 3面での初期化
break;
}
/* 敵キャラ初期化データ */
SetEnemyInit(&EnemyInitData,
288.0f, -64.0f, 352.0f, 0.0f, // 表示位置
5, 5, 5, 5, // 当たり判定矩形
2.0f, 2.0f, // 移動量
0, 1 // タイプ・ライフ
);
/* 敵キャラ */
for ( i=0 ; i<ENEMYMAX ; i++ ) Enemy[i].life = DEAD;
/* 敵キャラ出現時間のクリア */
gl_EnemyTime = 0;
//--------------------------------------------------- フレームナンバーセット
g_FrameNo = GAME_FRAME;
}
敵キャラは、設定したデータに従って出現させる。敵キャラ出現処理を次のように修正する。
//-----------------------------------------------------------------------------
// 関数名 : SetEnemy()
// 機能概要: 敵キャラ発生処理
//-----------------------------------------------------------------------------
static void SetEnemy(void)
{
int i;
#ifdef DEBUG
char buff[80];
#endif
/* EnemyOutDataに従って敵キャラを出現させる */
if ( EnemyOutData[gl_EnemyCnt].time != ENEMYDATAEND && gl_nowTime - gl_EnemyTime >= EnemyOutData[gl_EnemyCnt].time ) {
gl_EnemyTime = gl_nowTime;
/* flgが0のデータを検索し、初期情報をセット(敵キャラが死ぬことを考えてある) */
for ( i=0 ; i<ENEMYMAX ; i++ ) {
if ( Enemy[i].life == DEAD ) {
Enemy[i] = EnemyInitData; // 一括代入
Enemy[i].type = EnemyOutData[gl_EnemyCnt].type; // タイプをセット
gl_EnemyCnt++; // 敵キャラ出現データのカウントアップ
#ifdef DEBUG
wsprintf(buff, "配列の %d 番目にセット\n", i);
OutputDebugString(buff);
#endif
return; // 見つかれば、関数を抜ける
}
}
}
}
ここまでの修正を行い、初期化データどおりに敵キャラが出現するかを確かめる。また、2面用、3面用のデータも作成し、ステージを切り替えたら、それぞれの初期化データどおりに敵キャラが出現するかを確かめる。
敵キャラ出現データをプログラム内に持つのははっきりいって美しくない。よって、出現データをファイルで管理し、ステージ別にデータファイルを読み込むようプログラムを改造する。
※C言語のファイル操作関数を利用する。分からない人はC言語教科書の第13章ファイル操作、またはWeb教材のC言語入門の第15章を参照すること。
敵キャラ出現データファイルを、ステージ別に作成する。ファイル名は「Enemy1.dat」「Enemy2.dat」「Enemy3.dat」とし、ソースファイルと同じフォルダに配置する。例えばステージ1用のデータファイルの内容は次のようになる。
1000,0 500,0 500,0 500,0 500,0 1000,1 300,1 300,2 300,2 300,1 300,2 1500,3 100,4 100,3 100,4 100,3 100,4
同様に、ステージ2、ステージ3用のデータファイルを作成しておくこと。
Game.cppのグローバル変数の宣言に、敵キャラ出現データのファイル名を宣言する。
// グローバル変数
/* 自ソースでのみ利用するもの */
//---- ステージ別情報
static int gl_StageNo; // ステージ番号管理
//---- 背景
static TLVERTX BackVertex[4]; // 頂点情報配列
//---- 自機
static STATUS MyChara; // 自キャラステータス情報
//---- 敵
static STATUS EnemyInitData; // 敵キャラステータス情報初期化データ
static STATUS Enemy[ENEMYMAX]; // 敵キャラステータス情報
static DWORD gl_EnemyTime; // 敵キャラ出現時間
static ENEMYOUTDATA EnemyOutData[ENEMYDATAMAX];// 敵キャラ出現データ
static int gl_EnemyCnt; // 敵キャラ出現データのカウント
static char* szEnemyOutData[] = {"", "Enemy1.dat", "Enemy2.dat", "Enemy3.dat"}; // 敵キャラ出現データファイル名
ステージ別にデータファイルを読み込み、配列に格納するように、ステージ別初期化処理を次のように修正する。
//-----------------------------------------------------------------------------
// 関数名 : GameStageInit()
// 機能概要: ステージ開始時初期化処理
//-----------------------------------------------------------------------------
void GameStageInit(void)
{
FILE *fp;
char buff[80];
DWORD time;
int type;
int i;
//--------------------------------------------------- 各変数の初期化
// ステージ別に使用するテクスチャの作成
CreateGameStageTexture(gl_StageNo);
// ポリゴンの初期化
/* 自キャラ */
InitVertex(MyChara.Vertex, 272.0f, 380.0f, 368.0f, 476.0f, 255); // 頂点データ
SetRect(&MyChara.HitRect, 10, 10, 10, 10); // 当たり判定矩形
MyChara.MoveX = 2.0f;
MyChara.MoveY = 2.0f;
/* 敵キャラ出現データのカウント */
gl_EnemyCnt = 0;
/* 敵キャラ出現データをセット(ステージ別に) */
if ( (fp = fopen(szEnemyOutData[gl_StageNo], "r")) == NULL ) {
wsprintf(buff, "敵キャラ出現データファイル「%s」読み込みエラー\n", szEnemyOutData[gl_StageNo]);
MessageBox(hWnd, buff, "ERROR", MB_OK);
return;
}
for ( i=0 ; fscanf(fp, "%d,%d", &time, &type) != EOF ; i++ ) {
EnemyOutData[i].time = time;
EnemyOutData[i].type = type;
}
EnemyOutData[i].time = ENEMYDATAEND;
fclose(fp);
/* 敵キャラ初期化データ */
SetEnemyInit(&EnemyInitData,
288.0f, -64.0f, 352.0f, 0.0f, // 表示位置
5, 5, 5, 5, // 当たり判定矩形
2.0f, 2.0f, // 移動量
0, 1 // タイプ・ライフ
);
/* 敵キャラ */
for ( i=0 ; i<ENEMYMAX ; i++ ) Enemy[i].life = DEAD;
/* 敵キャラ出現時間のクリア */
gl_EnemyTime = 0;
//--------------------------------------------------- フレームナンバーセット
g_FrameNo = GAME_FRAME;
}
※ここまでの修正を行い、出現データファイルどおりに敵キャラが出現するかを確かめる。ステージを切り替えたら、それぞれの出現データどおりに敵キャラが出現するかを確かめる。
上の例では、ファイルからデータを1行読み込み、配列に格納するプログラムを次のように作成した。
for ( i=0 ; fscanf(fp, "%d,%d", &time, &type) != EOF ; i++ ) {
EnemyOutData[i].time = time;
EnemyOutData[i].type = type;
}
よーく考えると、次のように簡略化できる。
for ( i=0 ; fscanf(fp, "%d,%d", &EnemyOutData[i].time, &EnemyOutData[i].type) != EOF ; i++ );
for文の中で行う処理が無いため、for文の終わりにセミコロンを付ける(または、中身の無い括弧「{}」を付ける)必要がある。この場合、GameStageInit関数内のtimeやtype変数の宣言が不要になる。
| BACK(敵キャラの出現パターンを作る) | NEXT(タイプ別に初期化データを変える) |