タイプ別に初期化データを変える

前項までのプログラムでは、敵キャラ発生時、どのタイプの敵キャラでも同じ初期化データをセットしている。このため、出現位置や移動量がどのタイプでも同じになってしまっている。ここでは、敵キャラの種類別に初期化データを用意する方法を紹介する。

考え方

敵キャラのタイプが変われば初期化データも変わるはずだが、同じタイプで初期化データが違うことはあまり考えられない(例外として、出現位置をランダムにする場合などはありうる)。よって、初期化データをタイプの種類分作成すれば、大体の初期化は行えることが分かる。
初期化データをタイプの数だけ配列で持てば、それぞれのデータを修正するだけで、敵キャラを思い通りに出現させることができる。

プログラムへの組み込み

では実際に、プログラムに組み込む方法を解説する。

1.敵キャラ初期化用構造体変数の修正

敵キャラ初期化用構造体変数を配列に修正する。

//=============================================================================
//  ゲーム処理関係の自作関数群
//  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"}; // 敵キャラ出現データファイル名

2.初期化データをタイプ別に作成

初期化データをタイプ別に作成する。ファイルから読み込んでもいいが、とりあえずはプログラムに埋め込む。

//-----------------------------------------------------------------------------
// 関数名 : 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                            // タイプ・ライフ
    );
    ・
    ・
    ・
}

《POINT》

3.敵キャラ発生処理の修正

敵キャラ発生時、読み込んだタイプの値によってセットする初期化データを変えるよう、次のように修正する。

//-----------------------------------------------------------------------------
// 関数名 : 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; // 見つかれば、関数を抜ける
            }
        }
    }
}

《POINT》

ここまでの修正を行い、初期化データどおりに敵キャラが出現するかを確認する。また、初期化データをいろいろ変化させ、正しく移動処理が行われるかを確認する。

出現位置をランダムにする(参考)

タイプ0の敵キャラは、上から下に真っ直ぐ降りてくるだけである。このようなキャラクタは、出現位置が固定されないことが多い。よって、タイプ0の敵キャラは、出現位置のX座標がランダムになるようにプログラムを改造することを考える。

※乱数の発生について分からない場合は、C言語教科書の「第12章 標準ライブラリ関数」の「12.3 乱数」を、また、Web教材「C言語入門の第9章 標準ライブラリ関数」の「5.乱数」を参照。

1.考え方

敵キャラの大きさ(64×64ピクセル)を考慮すると、X座標として指定できる値の範囲は0〜576である(640 - 64)。よって、タイプ0の敵キャラ発生時に、0〜576の乱数を求め、X座標としてセットすればよい。

2.乱数を発生させる関数の作成

乱数を発生させる処理は、様々な場面で利用することが考えられる。よって、関数化しておくのが望ましい。どのソースからも利用できるよう、public.cppに作成するのがよいだろう。プロトタイプ宣言も行うこと。

//-----------------------------------------------------------------------------
// 関数名 : Random()
// 機能概要: 指定された範囲内で乱数を発生させる
//-----------------------------------------------------------------------------
int Random(int row, int high)
{
    return (row + rand() % (high - row + 1));
}

3.乱数ジェネレーターのクリア

乱数ジェネレーターをクリアしないと、ゲームを開始するたびに同じ乱数が発生してしまう。この処理は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) {
        ・
        ・
        ・

《POINT》

4.敵キャラ発生処理の修正

タイプ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; // 見つかれば、関数を抜ける
            }
        }
    }
}

《POINT》

ここまでの修正を行い、タイプ0の敵キャラが実行するたびに違う場所から出てくるかを確認する。


BACK(敵キャラの出現パターンを作る) NEXT(タイプ別にキャラクタ画像を変える)