前項までのプログラムでは、敵キャラ発生時、どのタイプの敵キャラでも同じ初期化データをセットしている。このため、出現位置や移動量がどのタイプでも同じになってしまっている。ここでは、敵キャラの種類別に初期化データを用意する方法を紹介する。
敵キャラのタイプが変われば初期化データも変わるはずだが、同じタイプで初期化データが違うことはあまり考えられない(例外として、出現位置をランダムにする場合などはありうる)。よって、初期化データをタイプの種類分作成すれば、大体の初期化は行えることが分かる。
初期化データをタイプの数だけ配列で持てば、それぞれのデータを修正するだけで、敵キャラを思い通りに出現させることができる。
では実際に、プログラムに組み込む方法を解説する。
敵キャラ初期化用構造体変数を配列に修正する。
//============================================================================= // ゲーム処理関係の自作関数群 // Copyright NKC Game Staff(←自分の名前) //----------------------------------------------------------------------------- #include "NKC_Common.h" // マクロの定義 #define STAGEMAX 3 // ステージ数 #define ENEMYMAX 100 // 敵キャラの最大数 //#define ENEMYTIME 1000 // 敵キャラの出現タイミング #define ENEMYDATAMAX 100 // 敵キャラデータ件数の最大 #define ENEMYDATAEND 99999 // 敵キャラデータ終了フラグ #define ENEMYTYPEMAX 5 // 敵キャラの種類の最大 // グローバル変数 /* 自ソースでのみ利用するもの */ //---- ステージ別情報 static int gl_StageNo; // ステージ番号管理 //---- 背景 static TLVERTX BackVertex[4]; // 頂点情報配列 //---- 自機 static STATUS MyChara; // 自キャラステータス情報 //---- 敵 static STATUS EnemyInitData[ENEMYTYPEMAX]; // 敵キャラステータス情報初期化データ 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)
{
・
・
・
/* 敵キャラ出現時データ(タイプ別に宣言) */
SetEnemyInit(&EnemyInitData[0],
288.0f, -64.0f, 352.0f, 0.0f, // 表示位置
5, 5, 5, 5, // 当たり判定矩形
2.0f, 2.0f, // 移動量
0, 1 // タイプ・ライフ
);
SetEnemyInit(&EnemyInitData[1],
100.0f, -64.0f, 164.0f, 0.0f, // 表示位置
10, 15, 10, 15, // 当たり判定矩形
2.0f, 2.0f, // 移動量
1, 1 // タイプ・ライフ
);
SetEnemyInit(&EnemyInitData[2],
476.0f, -64.0f, 540.0f, 0.0f, // 表示位置
10, 15, 10, 15, // 当たり判定矩形
2.0f, 2.0f, // 移動量
2, 1 // タイプ・ライフ
);
SetEnemyInit(&EnemyInitData[3],
150.0f, -64.0f, 214.0f, 0.0f, // 表示位置
10, 15, 10, 15, // 当たり判定矩形
2.0f, 2.0f, // 移動量
3, 1 // タイプ・ライフ
);
SetEnemyInit(&EnemyInitData[4],
426.0f, -64.0f, 490.0f, 0.0f, // 表示位置
10, 15, 10, 15, // 当たり判定矩形
2.0f, 2.0f, // 移動量
4, 1 // タイプ・ライフ
);
・
・
・
}
敵キャラ発生時、読み込んだタイプの値によってセットする初期化データを変えるよう、次のように修正する。
//-----------------------------------------------------------------------------
// 関数名 : 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[EnemyOutData[gl_EnemyCnt].type]; // 一括代入
//Enemy[i].type = EnemyOutData[gl_EnemyCnt].type; // タイプをセット
gl_EnemyCnt++; // 敵キャラ出現データのカウントアップ
#ifdef DEBUG
wsprintf(buff, "配列の %d 番目にセット\n", i);
OutputDebugString(buff);
#endif
return; // 見つかれば、関数を抜ける
}
}
}
}
ここまでの修正を行い、初期化データどおりに敵キャラが出現するかを確認する。また、初期化データをいろいろ変化させ、正しく移動処理が行われるかを確認する。
タイプ0の敵キャラは、上から下に真っ直ぐ降りてくるだけである。このようなキャラクタは、出現位置が固定されないことが多い。よって、タイプ0の敵キャラは、出現位置のX座標がランダムになるようにプログラムを改造することを考える。
※乱数の発生について分からない場合は、C言語教科書の「第12章 標準ライブラリ関数」の「12.3 乱数」を、また、Web教材「C言語入門の第9章 標準ライブラリ関数」の「5.乱数」を参照。
敵キャラの大きさ(64×64ピクセル)を考慮すると、X座標として指定できる値の範囲は0〜576である(640 - 64)。よって、タイプ0の敵キャラ発生時に、0〜576の乱数を求め、X座標としてセットすればよい。
乱数を発生させる処理は、様々な場面で利用することが考えられる。よって、関数化しておくのが望ましい。どのソースからも利用できるよう、public.cppに作成するのがよいだろう。プロトタイプ宣言も行うこと。
//-----------------------------------------------------------------------------
// 関数名 : Random()
// 機能概要: 指定された範囲内で乱数を発生させる
//-----------------------------------------------------------------------------
int Random(int row, int high)
{
return (row + rand() % (high - row + 1));
}
乱数ジェネレーターをクリアしないと、ゲームを開始するたびに同じ乱数が発生してしまう。この処理はWinMain関数で行うのがわかりやすいだろう。
//-------------------------------------------------------------------------------------------------
// メイン関数(エントリーポイント)プログラムはここから始まる
//-------------------------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE hThisInst, HINSTANCE hPrevInst, LPSTR lpszArgs, int nWinMode)
{
MSG msg; // メッセージ構造体変数
//表示するウィンドウの定義、登録、表示
if (!InitApp(hThisInst, nWinMode)) // InitApp関数を呼び出し、
return (FALSE); // 正常に終了すれば次にメッセージループへ
// DirectX8の初期化
if (InitDX8() != S_OK) return (FALSE);
// 乱数ジェネレーターのクリア
srand(timeGetTime());
// ゲームループ
while (TRUE) {
・
・
・
タイプ0の敵キャラが発生したら、そのつどX座標を乱数によって求め、セットする処理を追加する。
//-----------------------------------------------------------------------------
// 関数名 : SetEnemy()
// 機能概要: 敵キャラ発生処理
//-----------------------------------------------------------------------------
static void SetEnemy(void)
{
int i;
float x;
#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].flg == DEAD) {
if (EnemyOutData[gl_EnemyCnt].type == 0) { // タイプ0なら、X座標の出現位置を乱数で求める
x = (float)Random(0, 576);
InitVertex(EnemyInit[0].Vertex, x, EnemyInit[0].Vertex[0].y, (x + (float)64), EnemyInit[0].Vertex[2].y);
}
Enemy[i] = EnemyInit[EnemyOutData[gl_EnemyCnt].type]; // 一括代入
//Enemy[i].type = EnemyOutData[gl_EnemyCnt].type; // タイプをセット
gl_EnemyCnt++; // 敵キャラ出現データのカウントアップ
#ifdef DEBUG
wsprintf(buff, "配列の %d 番目にセット\n", i);
OutputDebugString(buff);
#endif
return; // 見つかれば、関数を抜ける
}
}
}
}
ここまでの修正を行い、タイプ0の敵キャラが実行するたびに違う場所から出てくるかを確認する。
| BACK(敵キャラの出現パターンを作る) | NEXT(タイプ別にキャラクタ画像を変える) |