笔记42 FSM有限状态机

传统编码

拖放模型。设定动画。

在这里插入图片描述
在这里插入图片描述

代码:Player1Controller+出现的小bug

 //拿到动画控制器
    private Animator ani;

    void Start()
    {
        ani = GetComponent<Animator>();
    }

    void Update()
    {
        //拿到轴
        float vertical = Input.GetAxis("Vertical");
        float horizontal = Input.GetAxis("Horizontal");
        //做成向量
        Vector3 dir = new Vector3(horizontal,0, vertical);
        //移动
        if (dir != Vector3.zero)
        {
            //旋转
            transform.rotation = Quaternion.LookRotation(dir);
            //前进
            transform.Translate(Vector3.forward * 3 * Time.deltaTime);
            //播放跑步动画
            ani.SetBool("isRun", true);
        }
        else
        {
            ani.SetBool("isRun", false);
        }

        //攻击
        if (Input.GetKeyDown(KeyCode.Space))
        {
            //SetTrigger触发
            ani.SetTrigger("attack");
        }
        //再有类似的,就继续添加if(){}
    }

在这里插入图片描述

缺点多于优点

再有类似的,就继续添加if(){}。
整个Update里写了一堆if。
一个if就代表一个状态。
如果写了二十多个if,一是,修改起来麻烦;
二是,它是按照顺序来的,有时修改了中间的代码,下面的代码可能出现不知解的bug。
三是,用这种方式写连击,很麻烦。
四是,比如攻击动画没做完,按跳跃是可以跳跃的,这是不允许的。
有个优点,就是与玩家相关的代码全写在这一个文件里了。

有限状态机有两种简单方式:方法、类

法一:方法+代码:Player2Controller

在这里插入图片描述

//枚举。玩家的状态
public enum PlayerState
{
    idle,
    run,
    attack
}

public class Player2Controller : MonoBehaviour
{
    //动画
    private Animator ani;
    //当前的状态。默认是站立
    private PlayerState state = PlayerState.idle;

    void Start()
    {
        //获取ani
        ani = GetComponent<Animator>();
    }

    void Update()
    {
        //switch:切换小括号里内容的取值。点击“补齐代码”。
        switch (state)
        {
            case PlayerState.idle:
                //补充:如果是站立,就调用站立的方法。
                idle();
                break;
            case PlayerState.run:
                run();
                break;
            case PlayerState.attack:
                attack();
                break;
        }
    }

    //有几个状态,就写几个方法。此处有三个。
    //每个里面,写每个要做的事。
    void idle()
    {
        //站立动画
        ani.SetBool("isRun", false);
        //得到轴,形成向量
        float vertical = Input.GetAxis("Vertical");
        float horizontal = Input.GetAxis("Horizontal");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        //判断向量不为零,说明按了AWSD某个按键了,跑步。
        if (dir != Vector3.zero)
        {
            //切换到跑步状态
            state = PlayerState.run;
        }
        //攻击
        if (Input.GetKeyDown(KeyCode.Space))
        {
            //切换到攻击状态
            state = PlayerState.attack;
        }
    }
    void run()
    {
        //跑步动画
        ani.SetBool("isRun", true);
        //得到轴,形成向量
        float vertical = Input.GetAxis("Vertical");
        float horizontal = Input.GetAxis("Horizontal");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        //判断向量不为零,说明按了AWSD某个按键了,跑步。
        if (dir != Vector3.zero)
        {
            //朝向向量
            transform.rotation = Quaternion.LookRotation(dir);
            //前进
            transform.Translate(Vector3.forward * 3 * Time.deltaTime);
        }
        else
        {
            //切换动画状态
            state = PlayerState.idle;
        }
    }
    void attack()
    {
        //触发攻击
        ani.SetTrigger("attack");
        //如果当前状态名称(方块上的名称)不是攻击,说明攻击的动画播放完了。
        //if如果 (!不是ani动画.GetCurrentAnimatorStateInfo状态(0).IsName名称("attack1"攻击))
        if (!ani.GetCurrentAnimatorStateInfo(0).IsName("attack1"))
        {
            //切换站立状态
            state = PlayerState.idle;
        }
    }
}

法二:类+代码

需要写几个类

在这里插入图片描述
在这里插入图片描述

代码:FSMState

//abstract抽象类(不能被实例化。必须有子类继承,对应关键词override)
public abstract class FSMState 
{
    //当前状态ID。它是int类型,因为玩家的状态我们是写在枚举里的。
    public int stateID;
    //状态拥有者。需要把角色控制类传过来
    public MonoBehaviour mono;
    //状态所属管理器
    public FSMManager fsmManager;

    //初始化方法
    public FSMState(int stateID,MonoBehaviour mono,FSMManager manager)
    {
        //赋值
        this.stateID = stateID;
        this.mono = mono;
        this.fsmManager = manager;
    }

    //抽象方法(子类必须实现的方法)只能包含在抽象类当中。
    //用虚方法(允许子类重写方法)也可以,类就不用写成抽象类了。
    public abstract void OnEnter();
    public abstract void OnUpdate();
}

代码:FSMManager

public class FSMManager 
{
    //状态列表
    public List<FSMState> stateList = new List<FSMState>();
    //当前的状态(索引)。-1表示没给状态。
    public int currentIndex = -1;
    
    //改变状态
    public void ChangeState(int StateID)
    {
        //当前状态=传进来的状态。
        currentIndex = StateID;
        //stateList[currentIndex]从数组里取出对应的状态
        //.OnEnter()调用一次OnEnter方法,确保切换到新状态的第一时间执行OnEnter方法
        stateList[currentIndex].OnEnter();       
    }

    //更新方法。这个Update是不会调用的,需要玩家里的Update协助调用。
    public void Update()
    {
        //如果当前有状态
        if (currentIndex != -1)
        {
            //stateList[currentIndex]从列表里拿到当前状态
            //.OnUpdate()调这个状态里的OnUpdate方法,确保OnUpdate会被一直调用。
            stateList[currentIndex].OnUpdate();
        }
    }
}

代码:IdleState : FSMState

/继承于抽象类FSMState。点击“生成构造函数”“实现抽象类”
public class IdleState : FSMState
{
    //必须实现构造方法。为了new的时候更方便。 new IdleState(小括号里提示写三个参数)
    public IdleState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager)
    {        
    }
    //状态的初始化方法,只调用一次
    public override void OnEnter()
    {
        //mono状态拥有者,其实就是PlayerController
        mono.GetComponent<Animator>().SetBool("isRun", false);
    }
    //状态的刷新方法,循环调用
    public override void OnUpdate()
    {
        //监听跑步状态,切换。
        //得到轴,组成向量,判断
        float vertical = Input.GetAxis("Vertical");
        float horizontal = Input.GetAxis("Horizontal");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        if (dir != Vector3.zero)
        {
            //fsmManager状态所属于的管理类。(int)PlayerState.run把枚举转成int类型
            fsmManager.ChangeState((int)PlayerState.run);
        }

        //监听攻击状态,切换。
        if (Input.GetKeyDown(KeyCode.Space))
        {
            fsmManager.ChangeState((int)PlayerState.attack);
        }
    }
}

代码:RunState : FSMState

public class RunState : FSMState
{
    public RunState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager)
    {           
    }

    public override void OnEnter()
    {
        //跑步动画状态
        mono.GetComponent<Animator>().SetBool("isRun", true);
    }

    public override void OnUpdate()
    {
        //得到轴,形成向量,判断,前进
        float vertical = Input.GetAxis("Vertical");
        float horizontal = Input.GetAxis("Horizontal");
        Vector3 dir = new Vector3(horizontal, 0, vertical);
        if (dir != Vector3.zero)
        {
            //朝向向量
            mono.transform.rotation = Quaternion.LookRotation(dir);
            //前进
            mono.transform.Translate(Vector3.forward * 3 * Time.deltaTime);
        }
        else
        {
            //切换到站立状态
            fsmManager.ChangeState((int)PlayerState.idle);
        }
    }
}

代码:AttackState : FSMState

public class AttackState : FSMState
{
    public AttackState(int stateID, MonoBehaviour mono, FSMManager manager) : base(stateID, mono, manager)
    {
    }

    public override void OnEnter()
    {
        mono.GetComponent<Animator>().SetTrigger("attack");
    }

    public override void OnUpdate()
    {
        //判断当前状态如果不是攻击1,就切换到站立
        if (!mono.GetComponent<Animator>().GetCurrentAnimatorStateInfo(0).IsName("attack1"))
        {
            fsmManager.ChangeState((int)PlayerState.idle);
        }
    }
}

代码:PlayerState3

//枚举。玩家的状态。(因为写第二个恐龙时,写了枚举PlayerState,所以此处改名为PlayerState3)
//这个枚举不妨在这的话,在下面的那行代码无用。
public enum PlayerState3
{
    idle,
    run,
    attack
}

public class player3Controller : MonoBehaviour
{
    //状态管理类
    private FSMManager fsmManager;

    void Start()
    {
        //new一个
        fsmManager = new FSMManager();
        //实例化各种状态。(0索引与枚举里的一致, this传进来的所属管理类, fsmManager属于哪个管理器类,刚刚new的)
        IdleState idle = new IdleState(0, this, fsmManager);
        RunState run = new RunState(1, this, fsmManager);
        AttackState attack = new AttackState(2, this, fsmManager);
        //把三种状态添加到管理类列表
        fsmManager.stateList.Add(idle);
        fsmManager.stateList.Add(run);
        fsmManager.stateList.Add(attack);
        //管理类默认切换成站立状态
        fsmManager.ChangeState((int)PlayerState.idle);
    }

    void Update()
    {
        //调用管理类里的Update
        //因为FSMManager里的Update方法不会自己运行,此处,帮助它运行。
        //进而,它就会调用当前状态里的OnUpdate。
        fsmManager.Update();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章