敵キャラは、種類によって様々な動きをする。ここでは、様々な動きをする敵キャラを作成し、効率よく管理する方法を紹介する。
敵キャラの移動パターンを効率よく管理するには、敵キャラをタイプ(種類)別に分ければよい。キャラクタ構造体にタイプを管理するメンバ(変数)を持ち、タイプの値によって移動処理を制御すれば、簡単かつ汎用性の高いプログラムを作成できる。
/* 敵キャラ用構造体 */
typedef struct _ENEMYSTATUS {
TLVERTX Vertex[4]; // 頂点情報配列
RECT HitRect; // 当たり判定矩形情報
float MoveX, MoveY; // 移動量
int type; // 敵のタイプ(種類)
int life; // 敵のライフ
} ENEMYSTATUS, *LPENEMYSTATUS;
switch (EnemyStatus[i].type) { case 0: // 敵0用の移動処理(上から下へ) EnemyMove0(&EnemyStatus[i]); break; case 1: // 敵1用の移動処理(左上から右下へ) EnemyMove1(&EnemyStatus[i]); break; case 2: // 敵2用の移動処理(右上から左下へ) EnemyMove2(&EnemyStatus[i]); break; ・ ・ ・ }
/* 敵0用移動処理 */
void EnemyMove0(LPENEMYSTATUS lpstatus)
{
Move(lpstatus->Vertex, 0, lpstatus->move_y);
}
/* 敵1用移動処理 */
void EnemyMove1(LPENEMYSTATUS lpstatus)
{
Move(lpstatus->Vertex, lpstatus->move_x, lpstatus->move_y);
}
/* 敵2用移動処理 */
void EnemyMove2(LPENEMYSTATUS lpstatus)
{
Move(lpstatus->Vertex, -lpstatus->move_x, lpstatus->move_y);
}
・
・
・
この方法ではソース自身は大きくなるが、1つの移動処理を1つの関数で作っているため、メンテナンスしやすくなる。また、グループでプログラムを作る場合、分業がしやすくなる。
実際に、複数の移動処理関数を作成し、タイプの値によって移動処理関数を切り替えるようプログラムを修正する。
敵キャラをタイプ別に制御するため、キャラクタ構造体にタイプを格納するメンバを追加する。
/* キャラクタ情報構造体 */
typedef struct _STATUS {
TLVERTX Vertex[4]; // 頂点情報配列
RECT HitRect; // 当たり判定矩形情報
float MoveX, MoveY; // 移動量
int type; // タイプ
int life; // ライフ(表示:1以上 非表示:0)
} STATUS, *LPSTATUS;
とりあえず、シンプルな動きをする移動処理関数を5つ用意する。移動処理関数 EnemyMove0 はEnemy.cppに作成済みであるため、他の4つの関数もEnemy.cppに作成する。これらの関数はGame.cpp内の関数から呼び出すため、Enemy.hにプロトタイプ宣言を行うこと。
//=============================================================================
// 敵キャラ移動関係の自作関数群
// Copyright NKC Game Staff(←自分の名前)
//-----------------------------------------------------------------------------
#include "NKC_Common.h"
//-----------------------------------------------------------------------------
// 関数名 : EnemyMove0()
// 機能概要: 敵キャラ移動(上から下へまっすぐ落ちる)
//-----------------------------------------------------------------------------
void EnemyMove0(LPSTATUS pEnemy)
{
// 移動
Move(pEnemy->Vertex, 0, pEnemy->MoveY);
// 画面をはみ出したときの処理
if ( pEnemy->Vertex[0].y > gl_rcScreen.bottom ) pEnemy->life = DEAD;
}
//-----------------------------------------------------------------------------
// 関数名 : EnemyMove1()
// 機能概要: 敵キャラ移動(まっすぐ落ち、途中から右下へ)
//-----------------------------------------------------------------------------
void EnemyMove1(LPSTATUS pEnemy)
{
// 移動
if ( pEnemy->Vertex[0].y < 200 )
Move(pEnemy->Vertex, 0, pEnemy->MoveY);
else
Move(pEnemy->Vertex, pEnemy->MoveX, pEnemy->MoveY);
// 画面をはみ出したときの処理
if ( pEnemy->Vertex[0].y > gl_rcScreen.bottom ) pEnemy->life = DEAD;
}
//-----------------------------------------------------------------------------
// 関数名 : EnemyMove2()
// 機能概要: 敵キャラ移動(まっすぐ落ち、途中から左下へ)
//-----------------------------------------------------------------------------
void EnemyMove2(LPSTATUS pEnemy)
{
// 移動
if ( pEnemy->Vertex[0].y < 200 )
Move(pEnemy->Vertex, 0, pEnemy->MoveY);
else
Move(pEnemy->Vertex, -pEnemy->MoveX, pEnemy->MoveY);
// 画面をはみ出したときの処理
if ( pEnemy->Vertex[0].y > gl_rcScreen.bottom ) pEnemy->life = DEAD;
}
//-----------------------------------------------------------------------------
// 関数名 : EnemyMove3()
// 機能概要: 敵キャラ移動(右下へ移動し、途中からまっすぐ落ちる)
//-----------------------------------------------------------------------------
void EnemyMove3(LPSTATUS pEnemy)
{
// 移動
if ( pEnemy->Vertex[0].y < 200 )
Move(pEnemy->Vertex, pEnemy->MoveX, pEnemy->MoveY);
else
Move(pEnemy->Vertex, 0, pEnemy->MoveY);
// 画面をはみ出したときの処理
if ( pEnemy->Vertex[0].y > gl_rcScreen.bottom ) pEnemy->life = DEAD;
}
//-----------------------------------------------------------------------------
// 関数名 : EnemyMove4()
// 機能概要: 敵キャラ移動(左下へ移動し、途中からまっすぐ落ちる)
//-----------------------------------------------------------------------------
void EnemyMove4(LPSTATUS pEnemy)
{
// 移動
if ( pEnemy->Vertex[0].y < 200 )
Move(pEnemy->Vertex, -pEnemy->MoveX, pEnemy->MoveY);
else
Move(pEnemy->Vertex, 0, pEnemy->MoveY);
// 画面をはみ出したときの処理
if ( pEnemy->Vertex[0].y > gl_rcScreen.bottom ) pEnemy->life = DEAD;
}
キャラクタ構造体変数のタイプ(type)の値を調べ、呼び出す関数を変える処理を追加する。敵キャラ移動処理(EnemyMove関数)を次のように修正する。
//-----------------------------------------------------------------------------
// 関数名 : EnemyMove()
// 機能概要: 敵キャラ移動
//-----------------------------------------------------------------------------
static void EnemyMove(void)
{
int i;
for ( i=0 ; i<ENEMYMAX ; i++ ) {
if ( Enemy[i].life > DEAD ) {
switch ( Enemy[i].type ) {
case 0: EnemyMove0(&Enemy[i]); break;
case 1: EnemyMove1(&Enemy[i]); break;
case 2: EnemyMove2(&Enemy[i]); break;
case 3: EnemyMove3(&Enemy[i]); break;
case 4: EnemyMove4(&Enemy[i]); break;
default: OutputDebugString("typeエラー\n"); break;
}
}
}
}
キャラクタ構造体にtypeが増えたため、タイプをセットできるよう、敵キャラ初期化処理を修正する。今回はタイプの値によって移動処理が変わるかどうかを確かめるため、敵キャラのタイプ別発生方法は考えないものとする(次項で行う)。SetEnemyInit関数の引数が増えるため、プロトタイプ宣言も修正すること。
//-----------------------------------------------------------------------------
// 関数名 : GameStageInit()
// 機能概要: ステージ開始時初期化処理
//-----------------------------------------------------------------------------
void GameStageInit(void)
{
・
・
・
/* 敵キャラ出現時データ */
SetEnemyInit(&EnemyInit,
288.0, -64.0, 352.0, 0.0, // 表示位置
10, 15, 10, 15, // 当たり判定矩形
2.0, 2.0, // 移動量
0, 1 // タイプ・ライフ
);
・
・
・
}
・
・
・
//-----------------------------------------------------------------------------
// 関数名 : SetEnemyInit()
// 機能概要: 敵キャラ発生用データ初期化
//-----------------------------------------------------------------------------
static void SetEnemyInit(LPSTATUS pEnemyInit,
float x1, float y1, float x2, float y2,
LONG left, LONG right, LONG top, LONG bottom,
float mx, float my,
int type, int life)
{
InitVertex(pEnemyInit->Vertex, x1, y1, x2, y2); // 表示位置
SetRect(&pEnemyInit->HitRect, left, top, right, bottom);// 当たり判定矩形
pEnemyInit->MoveX = mx; // 移動量(X方向)
pEnemyInit->MoveY = my; // 移動量(Y方向)
pEnemyInit->type = type; // タイプ
pEnemyInit->flg = life; // フラグ
}
以上の修正を行い、敵キャラ初期化関数に与えるタイプ(type)の値を0〜4のいずれかを与えて、敵キャラの移動処理が変化するかどうかを確かめる。
| BACK(一定時間ごとに敵キャラを発生させるサンプル) | NEXT(敵キャラの出現パターンを作る) |