
もちろん、このようなやり方を実際には「状態遷移プログラミング」と呼ぶのかどうかはわかりません。
ここでは、その言い方で統一しておきます。
・画面上のどこに出現するか?と言うのをきちっと決めてやる必要があります。その後、
・どの方向に、何フレーム飛んでいくのか?
・設定された速度で移動するといった処理を毎回(アクションゲームの場合、毎フレーム)行います(もちろん、他にやるべき事はたくさんありますが、説明を簡単にするため省略します)。
・フレーム数をカウントし、時間が来れば次の状態に移行する

状態1・突進★状態遷移処理(図で言うところの、矢印の部分に当たる)
状態2・停止
状態3・弾をばらまく
状態4・逃げる
爆発(実際の処理)
状態1へ遷移・・・といったような処理が必要になってきます。で、これらを処理するための関数を用意してやりましょう。ソースは、とりあえずCの書き方で書いておきます。
状態2へ遷移
状態3へ遷移
状態4へ遷移
爆発(初期設定)
消滅
//===========================================================
// 敵その1用処理
//
// なお、下記のソースに出てくるENEMY_PARMというのは、
// 敵のパラメータを持っている構造体である。
//===========================================================
//-------------------------------------------------
// 状態遷移関数
//----[ 状態1へ遷移 ]----
void InitEnemy1State01(ENEMY_PARM *p)
{
・・・中身は省略(爆)
}
//----[ 状態2へ遷移 ]----
void InitEnemy1State02(ENEMY_PARM *p)
{
・・・中身は省略(爆)
}
//----[ 状態3へ遷移 ]----
・・・以下、同様にして「状態遷移関数」を全部用意
//-------------------------------------------------
// 状態毎の処理関数
//----[ 状態1の処理 ]----
void ProcEnemy1State01(ENEMY_PARM *p)
{
・・・中身は省略(爆)
}
//----[ 状態2の処理 ]----
void ProcEnemy1State02(ENEMY_PARM *p)
{
・・・中身は省略(爆)
}
・・・以下、同様にして「状態毎の処理関数」を全部用意
|
//<<<構造体の中身>>>
// StateCanger 状態遷移が合ったら、その状態遷移番号。
// 無ければ−1
// State 現在の状態番号
if(p->StateChange!=-1)
{
//状態遷移があったぞ!
switch(p->StateChang)
{
case CHANGE_ENEMY1_STATE_01: //状態1へ遷移
{
InitEnemy1State01(p);
break;
}
・・・略・・・
}
p->StateChange=-1;
}
switch(p->State)
{
case ENEMY_STATE_01: //状態1
{
ProcEnemy1State01(p);
break;
}
・・・略・・・
}
|
//状態遷移関数へのポインタ
typedef void ENEMY01_CHG_STATE_FUNC(ENEMY_PARM *);
//状態毎の処理関数へのポインタ
typedef void ENEMY01_PROC_STATE_FUNC(ENEMY_PARM *);
//状態遷移テーブル
ENEMY01_CHG_STATE_FUNC Enemy01ChgState[]={
InitEnemy1State01,
・・・略・・・
};
//状態毎の処理テーブル
ENEMY01_CHG_STATE_FUNC Enemy01StateProc[]={
ProcEnemy1State01,
・・・略・・・
};
//実際の処理。ここから先を呼び出す。
if(p->StateChange!=-1)
{
//状態遷移があったぞ!
Enemy01ChgState[p->StateChange](p);
p->StateChange=-1;
}
Enemy01StateProc[p->State](p);
|
・自爆命令と言うようなものがあるとします。これらにそれぞれ番号をつけておきます。
・ダメージ食らった
・壁にぶつかった
・上から降ってきた物につぶされた
・地雷を踏んだ
enum CHANGE_STATE_OUTSIDE {
CHG_OUTSIDE_KILL_MYSELF, //自爆命令
CHG_OUTSIDE_DAMAGE, //ダメージ食らった
CHG_OUTSIDE_WALL, //壁にぶつかった
CHG_OUTSIDE_DROP_OBJ, //上から降ってきた物につぶされた
CHG_OUTSIDE_BOMB, //地雷を踏んだ
MAX_OUTSIDE_CHG, //外部からの状態遷移要因の最大数
};
|
int TableChgStateOutside[MAX_STATE][MAX_OUTSIDE_CHG]={
{・・・略・・・・}, //状態0の時の状態遷移
{・・・略・・・・}, //状態1の時の状態遷移
{・・・略・・・・}, //状態2の時の状態遷移
・・・略・・・・
};
|
void ChgStateOutside(ENEMY_PARM *p,int mes)
{
p->StateChange=TableChgStateOutside[p->State][mes];
}
|
・基本的にメンバ変数をいじっていればいいので、「p->」と言った部分は書く必要がないし、状態遷移関数・状態毎の処理関数に引数を渡す必要がないといった点です。特に、3番目のヤツが重要です。
・継承を利用すればかなり便利
・自分以外のキャラのパラメータをいじる可能性が0である
・別のクラスなら、同じ名前でもいっこうに問題ないため、関数名を別々につけてやる必要がない
・CPUが、そのゲームでは絶対に出来ない「しゃがみ大>立ち小」というチェーンコンボをしてくる
・技の途中で変な動きをすることがある(別の技のモーションが混じる)
・超必投げの最中に相手がはずれ、反撃を食らってしまう(投げた側は一人で投げの動作を行っている)
・本来出来るはずのキャンセルが、時々出来なくなったりする
・ある技の出始めに特殊行動を行うことが出来るため、ほとんど隙のない連係が可能(※2)
char型(8ビット整数型。ー128〜127)に5217という値が代入できないのと同じ事。
つまり、基本的に言語として出来ない事なので、それが起こることはありえない、というわけ。
ただし、C++の場合、配列をいじる際に上限の判定をしないので(要素数8の配列の9番目にアクセスできたりする)、それを利用すれば一応可能ではある。普通はメソッド(メンバ変数)に対してそんな事しないと思うが・・・。
一見バグっぽくないが、本来あり得ない動作なので、これはれっきとしたバグである。
なお、この「ある技」とほぼ同じ操作(立ちかしゃがみかの違い)で行う別の技では同じような事が出来ないので、明らかに開発側のミス(チェック不足?)である。
なお、UeSyuの格ゲー「クレールVSサフィーユ」のプレイヤーなどは、厳しくこの考え方に基づいているため、この手のバグは発生しにくくなっています。