状態移行について


ゲームというものは、状況の判定やパターンが増えれば増えるほどif文とかが多くなってソースがヤバイことになりがちです。特に敵の脳となる部分を作る場合なんて、特にififします。
今回はそういったifを減らしてソースを少しだけスッキリさせようという企画です。

例について

今回は主に敵の脳である部分を作る場合を例として挙げて説明していこうと思います。そのために、敵の動作を作る上で、敵の動かし方を書いておきます。
プレイヤーが例えばAキーを押してジャンプする場合、裏でAキーが押されているとき「aKey」という変数がtrueとなり、「aKeyがtrueだからAキーが押されているな」と判定し、プレイヤーをジャンプさせる処理を行います。つまりAキーを実際に押さなくても、「aKey」にtrueを設定してやれば、プレイヤーをジャンプさせることができます。
ということで敵の脳を作る場合、基本的に「条件判定→キー入力状態の変更」の繰り返しで敵の動きを制御します。

「状態」で管理する

if文を減らす方法として、「状態」で管理する方法があります。ある条件を満たせばそれに応じた状態へ移行させてやれば、毎回判定するif文が減って少しソースが見やすくなります。
敵の脳の例で説明しますと、ある敵が

  1. 待機状態
  2. プレイヤーに近づく状態
  3. 攻撃状態

という3つの状態を持っているとします。敵はプレイヤーがある距離に近づいてくるまで状態1で待機します。ここで、もしプレイヤーがある距離まで近づいてきたなら状態1から状態2へ移行します。状態2で敵はプレイヤーのいる方向へ移動を始めます。そして、もしプレイヤーが敵の攻撃射程範囲内に入ったなら、状態3へ移行します。状態3で攻撃が終了するまで待ちます。攻撃が終了すれば、プレイヤーとの距離によって状態1か状態2へ移行させます。
状態ごとに処理を区切ることができるので、ソースだけじゃなく頭もスッキリします。

実装

では前述の例を実際に書いてみます。

メイン

まずは敵の脳の状態を示す変数と、脳の処理の中心となるcontrolメソッドを定義します。

stat = 1;    // 状態を示す変数
function control()
{
    switch (stat)
    {
        case 1:        // 状態1
            standby();
            break;
        case 2:        // 状態2
            moveToPlayer();
            break;
        case 3:        // 状態3
            attack();
            break;
    }
}

最初に定義している「stat」という変数は、設定されている数値が状態の番号となります。敵の脳が状態1ならstatには1が設定されているといった感じです。
そしてcontrolメソッド内には、switch文によって状態を判定し、処理が分岐していることが分かると思います。それぞれのcase内には状態1であるstandby(待機)メソッド、状態2であるmoveToPlayer(プレイヤーの方へ移動)メソッド、状態3であるattack(攻撃)メソッドが書かれています。もうお分かりかと思いますが、あとはstandby()、moveToPlayer()、attack()のそれぞれのメソッドにその状態の処理を書けば敵の脳は完成となります。
あ、ちなみに、controlメソッドは定義するだけじゃなく、ループ実行しなければいけませんよ。

onClipEvent(enterFrame)
{
    control();
}

例えばこんな感じですね。

各状態

では簡単にそれぞれの状態メソッドを書いてみます。
まずはstandbyメソッドです。

function standby()
{
    // プレイヤーと敵との距離が200より近いなら
    if (Math.abs(player._x - this._x) < 200)
    {
        stat = 2;    // 状態2へ移行
    }
}

これだけです。
続いてmoveToPlayerメソッドです。

function moveToPlayer()
{
    // プレイヤーが敵より左にいるなら
    if (player._x < this._x)
    {
        // 左へ移動
        rightKey = false;    // 右キー放す
        leftKey = true;      // 左キーを押す
    }
    // プレイヤーが敵より右にいるなら
    else
    {
        // 右へ移動
        rightKey = true;    // 右キーを押す
        leftKey = false;    // 左キーを放す
    }
    
    // プレイヤーが攻撃射程内にいるなら(距離が50より近いなら
    if (Math.abs(player._x - this._x) < 50)
    {
        // 移動をやめる
        rightKey = false;    // 右キーを放す
        leftKey = false;     // 左キーを放す

        // 攻撃開始
        attackKey = true;    // 攻撃キーを押す
        stat = 3;            // 状態3へ移行
    }
}

少々長い感じがしますが、やっていることはシンプルです。
そしてattackメソッドです。

function attack()
{
    // (攻撃動作のフレーム数がが10として)攻撃動作が終了したなら
    if (this._currentFrame == 10)
    {
        attackKey = false;    // 攻撃キーを放す
        
        // プレイヤーと敵との距離が200より近いなら
        if (Math.abs(player._x - this._x) < 200)
        {
            stat = 2;    // 状態2へ移行
        }
        // 200より遠いなら
        else
        {
            stat = 1;    // 状態1へ移行
        }
    }
}

攻撃の終了を待ち、攻撃終了時のプレイヤーと敵との距離によって、状態1か状態2かへ状態移行を分岐しています。
大雑把ながら、「状態」を管理することによって敵の脳の作り方が分かったと思いますがどうでしょう。

まとめ

これでそのままifのみで敵の脳を作った場合よりソースがスッキリしたと思います。この「状態」で処理の分岐を管理するという考え方というのは、いろんなところで応用が利くので、実際に弄って経験を積むのがいいと思います。
実はこの「状態管理」をもっと賢く扱いやすくしたのがタスクシステムだったりします。


以上ぅえ。