Unity の単純な NPC ステート マシン

今日はステートマシンの書き方をざっくり覚えましたが、それをまとめるとステートマシンとはもともとNPCスクリプトのUpdate関数に書かれていた散在するメソッドを各ステートマシンに統一的に分類したものだと思います。

ステート マシンを使用するには、まずステート マシンを使用する利点を知る必要があります。

たとえば、今日の例では、NPC のイノシシが前に進み続けますが、壁にぶつかったり、崖にぶつかったときにイノシシが向きを変える機能を追加したいと考えています。

ステート マシンを使用しない場合は、コードを Update で記述する必要があります。

おおよその実施方法は、左右と足の裏に判定ポイントを設け、判定に達したら振り向くというものです。

ここで追加された機能は、壁にぶつかるとタイマーがあり、狭い場所での連続回転を防ぐために2秒後に再び回転することです。

    private void Update()
    {
        //判定方向
        faceDir = new Vector3(-transform.localScale.x, 0, 0);
        if ((!physicsCheck.isGround||physicsCheck.touchLeftWall||             physicsCheck.touchRightWall)&&coundTurn)
        {
            transform.localScale = new Vector3(currentEnemy.faceDir.x, 1, 1);
            coundTurn = false;
            animator.SetBool("isWalk", true);
        }

        //计时器方法
        TimeCounter();
    }

その後、イノシシにクレイジーな状態を追加する必要がありました。これはおそらく、プレイヤーを発見した後、動きが速くなり、向きを変えるのに 2 秒待たなくなり、崖に遭遇しても止まらなくなることを意味します。

ステートマシンを使用しない場合、コードは次のように記述されます。

​
    private void Update()
    {
        //判定方向
        faceDir = new Vector3(-transform.localScale.x, 0, 0);
        if(普通状态)
        {
            if ((!physicsCheck.isGround||physicsCheck.touchLeftWall||             physicsCheck.touchRightWall)&&coundTurn)
            {
            transform.localScale = new Vector3(currentEnemy.faceDir.x, 1, 1);
            coundTurn = false;
            animator.SetBool("isWalk", true);
            }
        }

        if(发狂状态)
        {
            //发狂状态逻辑
        }

        //计时器方法
        TimeCounter();
    }

​

さらに多くの状態 (衰弱、めまい) が必要な場合、更新関数は非常に複雑になり、その後の変更には不便になりますが、通常はコードを変更するよりも追加することが最善であることがわかっています。

ステートマシンを使用する

ステート マシンは、状態、この状態に入るために何をするか、この状態で何をするか、この状態を終了するために何をするかを表します。これはステートマシンに書き込まれるコードです。ステート マシンを使用するには、まずステート マシンの基本クラスを定義します。

public abstract class BaseState
{
    //这个状态机是哪个怪物的状态机
    protected Enemy currentEnemy;
    public abstract void OnEnter(Enemy enemy);
    public abstract void LogicUpdate();
    public abstract void PhysicsUpdate();

    public abstract void OnExit();
}

たとえば、上記の通常状態では、イノシシの通常状態のステート マシン BoarPatrolState を定義し、基本クラスを継承します。

public class BoarPatrolState : BaseState
{
    //进入状态时把当前的怪物传给状态机
    public override void OnEnter(Enemy enemy)
    {
        currentEnemy = enemy;
        currentEnemy.currentSpeed = currentEnemy.normalSpeed;
    }

    public override void LogicUpdate()
    {
        //发现玩家切换到chase追击
        if (currentEnemy.FoundPlayer())
        {
            currentEnemy.SwitchState(NPCState.Chase);
        }
        //碰到墙壁回头
        if ((!currentEnemy.physicsCheck.isGround||currentEnemy.physicsCheck.touchLeftWall || currentEnemy.physicsCheck.touchRightWall)&& currentEnemy.coundTurn)
        {
            currentEnemy.transform.localScale = new Vector3(currentEnemy.faceDir.x, 1, 1);
            currentEnemy.coundTurn = false;
            currentEnemy.animator.SetBool("isWalk", true);
        }

        if (currentEnemy.rb.velocity==new Vector2(0,0))
        {
            currentEnemy.animator.SetBool("isWalk", false);
        }
    }

    public override void PhysicsUpdate()
    {
        
    }
    public override void OnExit()
    {
        currentEnemy.animator.SetBool("isWalk", false);
    }

}

NPC スクリプトでステート マシン変数を定義する必要がありますが、現時点では更新関数のこのコード行のみが必要です。

    //巡逻
    protected BaseState patrolState;
    //追击
    protected BaseState chaseState;
    //当前状态
    protected BaseState currentState;
    private void Update()
    {
        faceDir = new Vector3(-transform.localScale.x, 0, 0);

        //状态机逻辑更新
        currentState.LogicUpdate();

        TimeCounter();
    }

また、イノシシの狂った (追いかける) 状態のステート マシン BoarChaseState も定義します。

public class BoarChaseState : BaseState
{
    public override void OnEnter(Enemy enemy)
    {
        currentEnemy = enemy;
        Debug.Log("切换到Chase");
        currentEnemy.currentSpeed = currentEnemy.chaseSpeed;
        currentEnemy.animator.SetBool("isRun", true);
    }

    public override void LogicUpdate()
    {
        if (currentEnemy.lostTimeCounter<=0)
        {
            currentEnemy.SwitchState(NPCState.Patrol);
        }
        if (currentEnemy.physicsCheck.touchLeftWall&&currentEnemy.faceDir.x<0 || currentEnemy.physicsCheck.touchRightWall && currentEnemy.faceDir.x>0)
        {
            currentEnemy.transform.localScale = new Vector3(currentEnemy.faceDir.x, 1, 1);
        }
    }
    public override void PhysicsUpdate()
    {
        //throw new System.NotImplementedException();
    }
    public override void OnExit()
    {
        currentEnemy.animator.SetBool("isRun", false);
    }

}

両方のステート マシンに状態を切り替えるためのロジックがあることがわかります。状態を切り替えるには、この関数を呼び出します。

    public void SwitchState(NPCState state)
    {
        //退出当前状态
        currentState.OnExit();
        //根据状态切换
        switch (state)
        {
            case NPCState.Patrol:
                currentState = patrolState;
                break;
            case NPCState.Chase:
                currentState = chaseState;
                break;
            case NPCState.Skill:
                break;
        }
        //进入新状态
        currentState.OnEnter(this);
    }

public enum NPCState
{
    Patrol,
    Chase,
    Skill
}

ここでのpatrolStateとchaseStateは特定のNPCスクリプトに注入されており、例えばイノシシのこれら2つの状態はイノシシのAwakeに注入されています。他の新しい敵 (ハチ、カタツムリ) の場合は、それぞれの Awake でインスタンス化します。

public class Boar : Enemy
{

    public override void Move()
    {
        base.Move();
        animator.SetBool("isWalk", true);
    }

    protected override void Awake()
    {
        base.Awake();
        patrolState = new BoarPatrolState();
        chaseState = new BoarChaseState();
    }
}

要約する

新しいステートをイノシシに追加したい場合は、ステート マシンの基本クラスを継承する新しいステート マシン スクリプトを作成し、それに対応するロジックを記述してから、モンスター スクリプト (Boar) に移動して、その状態の変数を取得し、SwtichState 関数に進みます。もう 1 つのケースを書くだけです。

おすすめ

転載: blog.csdn.net/holens01/article/details/131832388