La machine à états PNJ simple d'Unity

Aujourd'hui, j'ai à peu près appris à écrire une machine à états, et après l'avoir résumée, je pense que la machine à états consiste à unifier les méthodes dispersées écrites à l'origine dans la fonction de mise à jour du script NPC dans chaque machine à états.

Pour utiliser une machine à états, nous devons d’abord savoir quels sont les avantages de l’utilisation d’une machine à états

Par exemple, dans l'exemple que j'ai fait aujourd'hui, le PNJ sanglier continuera d'avancer, et maintenant je souhaite lui ajouter une fonction : le sanglier se retournera après avoir heurté un mur ou rencontré une falaise.

Si je n'utilise pas de machine à états, mon code doit être écrit dans Update

La méthode de mise en œuvre approximative consiste à avoir un point de jugement à gauche, à droite et sur la plante du pied. Si le jugement est atteint, faites demi-tour.

Une fonctionnalité supplémentaire ajoutée ici est qu'il y aura une minuterie lorsqu'il heurtera un mur, et il se retournera à nouveau après deux secondes pour empêcher une rotation continue dans un petit endroit.

    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();
    }

Plus tard, nous avons dû ajouter un état fou au sanglier, ce qui signifie en gros qu'après avoir découvert le joueur, il se déplacera plus vite, n'attendra plus 2 secondes pour se retourner et ne s'arrêtera pas lorsqu'il rencontrera une falaise.

Si je n'utilise pas de machine à états, mon code peut être écrit comme ceci

​
    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();
    }

​

Si j'ai besoin de plus d'états (faiblesse, vertiges), alors ma fonction de mise à jour sera très compliquée et peu pratique pour les modifications ultérieures. On sait qu'il est généralement préférable d'ajouter du code plutôt que de le modifier.

Utiliser la machine à états

Une machine à états représente un état, que faire pour entrer dans cet état, que faire dans cet état et que faire pour sortir de cet état. C'est le code à écrire dans la machine à états. Pour utiliser une machine à états, nous pouvons d’abord définir une classe de base de machine à états.

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();
}

Par exemple, dans l'état normal ci-dessus, nous définissons une machine à états BoarPatrolState pour l'état normal du sanglier et héritons de la classe de base.

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);
    }

}

Nous devons définir les variables de la machine d'état dans le script NPC. Pour le moment, nous n'avons besoin que de cette ligne de code dans notre fonction de mise à jour.

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

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

        TimeCounter();
    }

Nous définissons également la machine à états BoarChaseState pour l'état fou (de poursuite) du sanglier.

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);
    }

}

Vous pouvez voir qu'il existe une logique pour changer d'état dans les deux machines à états. Changer d'état revient à appeler cette fonction.

    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
}

Le PatrolState et le ChaseState sont ici injectés dans le script spécifique du PNJ. Par exemple, ces deux états du sanglier sont injectés dans l'éveil du sanglier. S'il s'agit d'autres nouveaux ennemis (abeilles, escargots), instanciez-les dans leurs éveils respectifs.

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();
    }
}

Résumer

Si nous voulons ajouter un nouvel état au sanglier, il nous suffit d'écrire un nouveau script de machine d'état pour hériter de la classe de base de la machine d'état et d'y écrire la logique correspondante, puis d'aller dans le script monstre (Boar) pour définir le variables de cet état, puis accédez à la fonction SwtichState. Écrivez simplement un cas supplémentaire.

Je suppose que tu aimes

Origine blog.csdn.net/holens01/article/details/131832388
conseillé
Classement