首页 > 其他分享 >Unity进阶开发-FSM有限状态机

Unity进阶开发-FSM有限状态机

时间:2023-10-28 21:12:45浏览次数:33  
标签:状态 进阶 stateMachine void FSM 状态机 PlayerState 进入状态 public

# Unity进阶开发-FSM有限状态机

前言

我们在进行开发时,到了一定程度上,会遇到数十种状态,继续使用Unity的Animator控制器会出现大量的bool,float类型的变量,而这些错综复杂的变量与Animatator控制器如同迷宫版连线相结合会变得极其的复杂且无法良好维护扩展,出现一个BUG会导致开发过程中开发者承受极大的精神力,而这时候,使用有限状态机或者AI行为树便成为了一个极佳的选择,本文只记录了有限状态机的开发

使用有限状态机进行状态管理与切换可以大幅度的减少开发时候的难度,在开发过程中只需要关注各个状态间的切换即可

图示FSM工作过程:

我们可以看到,FSM的脚本一共分为两大块儿,一块儿继承自IState,这里面保存着状态内执行的行为,例如进入状态,离开状态,逻辑切换(用于判断是否切换到其他状态),PlayerState实现这个接口,并继承ScriptableObject类,看到ScriptableObject我们就知道了PlayerState的作用了,ScriptableObject类的对象不依赖场景中的对象可,是独立存在的数据文件。而在这里,PlayState保存的是状态的行为逻辑函数,这些行为逻辑函数的本质作用就是满足条件就去执行相关行为。当然,在FSM中用一个例子来说明,当玩家处于跑步状态时,PlayState文件发现满足执行跑步行为的逻辑,就需要去播放跑步动画。我们知道,ScriptableObject的数据是不会自己变化的,那么所有判断逻辑只能使用一遍,我们该如何解决这个问题,这就需要FSM第二的板块儿的类来实现了

我们不难发现,StateMachine是继承MonoBehaviour的,这就需要挂载到场景中了,并且也可以使用Update函数来时刻改变检测到的信息,那么,上文已经说了,PlayerState的数据不能主动变化,而StateMachine的数据可以进行变化,所以我们就可以通过一个函数来讲StateMachine中检测到数据来发给PlayerState,以此来实现PlayerState创建的数据文件可以连续进行逻辑判断

好了,现在开始上代码,用代码继续说

No.1 IState

public interface IState
{
    //进入状态
    void Enter() { }
    //退出状态
    void Exit() { }
    //状态逻辑更新
    void LogicUpdate() { }
}

在这里就是状态内部需要执行的行为函数

No.2 StateMachine

// <summary>
/// 持有所有状态类,并对他们进行管理和切换
/// 负责当前状态的更新
/// </summary>
public class StateMachine : MonoBehaviour
{
    IState currentState;                                           //当前状态
    public Dictionary<System.Type, IState> stateTable;             //字典,用于保存状态以及查询状态,方便状态切换时                                                                    //进行查找
    public void Update()
    {
        currentState.LogicUpdate();                                //执行每个状态的逻辑切换函数,可以使状态实现检测                                                                    //变换,类似于Update内的函数实时接收信息
    }
    //切换状态
    protected void SwitchOn(IState newState)
    {
        //当前状态变为新状态
        currentState = newState;
        //进入新状态
        currentState.Enter();
    }
    //
    public void SwitchState(IState newState)
    {
        //退出状态
        currentState.Exit();
        //进入新状态
        SwitchOn(newState);
    }
    //切换状态函数重载
    public void SwitchState(System.Type newStateType)
    {
        SwitchState(stateTable[newStateType]);                    //将字典内的状态传入
    }

}

No.3 PlayetState

public class PlayerState : ScriptableObject, IState
{
    /*************物理检测*************/
    protected bool isGround;                          //是否在地面
    /*************基础信息*************/
    protected bool isRun;                             //是否跑步
    protected bool isJump;                            //是否跳跃
    protected bool isIdle;                            //是否静止
    /*************相关组件*************/
    protected Rigidbody2D my_Body2D;                  //刚体组件,用于获取物体刚体属性
    protected Animator animator;                      //动画组件,用来播放动画
    protected PlayerStateMachine stateMachine;        //PlayerStateMachine,玩家状态机类,执行状态间的切换
    public void Initiatize(Animator animator, Rigidbody2D my_Body2D, PlayerStateMachine stateMachine)
    {//获取PlayerStateMachine传递进来的 动画,刚体,状态机类
        this.animator = animator;
        this.my_Body2D = my_Body2D;
        this.stateMachine = stateMachine;
    }
    public void PhysicalDetection(bool isGround)
    {
        this.isGround = isGround;
    }
    /// <summary>
    /// 状态信息传递
    /// </summary>
    public void BasicInformation(bool isRun,bool isJump,bool isIdle)
    {
        this.isRun = isRun;
        this.isJump = isJump;
        this.isIdle = isIdle;
    }
    //进入状态
    public virtual void Enter() { }
    //离开状态
    public virtual void Exit() { }
    //逻辑切换
    public virtual void LogicUpdate() { }
}

No.4 PlayerStateMachine

/// <summary>
/// 玩家状态机类
/// </summary>
public class PlayerStateMachine : StateMachine
{
    /*************************检测信息***************************/
    PlayerPhysicalDetection playerPhysicalDetection;                      //物理检测组件,这个是继承                                                                                       //MonoBehaviour,挂载到玩家身上来检测                                                                           //玩家的物理信息,例如是否位于地面
    /*************************状态信息***************************/
    //PlayerState资源文件
    [SerializeField] PlayerState[] states;
    Animator animator;                            //获取动画组件
    Rigidbody2D my_Body2D;                        //获取刚体组件
    void Awake()
    {
        /*************************物理检测信息***************************/
        playerPhysicalDetection=GetComponent<PlayerPhysicalDetection>();            //获取物理检测组件

        /*************************状态信息组件***************************/
        stateTable = new Dictionary<System.Type, IState>(states.Length);            //初始化字典
        animator = GetComponent<Animator>();                                        //获取动画组件
        my_Body2D = GetComponent<Rigidbody2D>();                                    //获取刚体组件
        //迭代器循环获取状态
        foreach (PlayerState state in states)
        {
            state.Initiatize(animator, my_Body2D, this);//将动画组件,刚体组件以及PlayerStateMachine传入进去
            //状态存入字典
            stateTable.Add(state.GetType(), state);
        }
    }
    private void Start()
    {//在开始时执行Idle,进入Idle状态
        SwitchOn(stateTable[typeof(PlayerState_Idle)]);
    }
    private new void Update()
    {
        base.Update();//执行父类StateMachine的Update函数
        foreach (PlayerState state in states)
        {//将检测信息传入进去
            state.PhysicalDetection(playerPhysicalDetection.isGround);
            state.BasicInformation( isRun, isJump, isIdle);
        }
    }
}

No.5 PlayerState_Idle

[CreateAssetMenu(menuName = "StateMachine/PlayerState/Idle", fileName = "PlayerState_Idle")]//创建文件
public class PlayerState_Idle : PlayerState
{
    /*****************物理检测*******************/
    public override void Enter()
    {
        //执行该状态数据文件,首先执行进入状态函数,在进入状态函数执行相关的行为
        //进入状态,默认播放Idle动画
        animator.Play("PlayerIdle");
    }
    
    //逻辑切换函数,当检测到处于某种状态,立刻执行该状态的数据文件
    public override void LogicUpdate()
    {
        if(isRun)
        {
            stateMachine.SwitchState(stateMachine.stateTable[typeof(PlayerState_Run)]);
        }
        if(isJump)
        {
            stateMachine.SwitchState(stateMachine.stateTable[typeof(PlayerState_Jump)]);
        }
        if(my_Body2D.velocity.y<0&&!isGround)
        {
            stateMachine.SwitchState(stateMachine.stateTable[typeof(PlayerState_Fall)]);
        }
    }
}

No.6 PlayerState_Jump

[CreateAssetMenu(menuName = "StateMachine/PlayerState/Jump", fileName = "PlayerState_Jump")]//创建文件
public class PlayerState_Jump : PlayerState
{
    /*****************物理检测*******************/
    public override void Enter()
    {
        //执行该状态数据文件,首先执行进入状态函数,在进入状态函数执行相关的行为
        //进入状态,默认播放Idle动画
        animator.Play("PlayerJump");
    }
    
    //逻辑切换函数,当检测到处于某种状态,立刻执行该状态的数据文件
    //该脚本中,跳跃后无法执行其它行为,若有需要可以添加判断
    public override void LogicUpdate()
    {
        if(my_Body2D.velocity.y<0&&!isGround)
        {
            stateMachine.SwitchState(stateMachine.stateTable[typeof(PlayerState_Fall)]);
        }
    }
}

No.7 PlayerState_Fall

[CreateAssetMenu(menuName = "StateMachine/PlayerState/Fall", fileName = "PlayerState_Fall")]//创建文件
public class PlayerState_Jump : PlayerState
{
    /*****************物理检测*******************/
    public override void Enter()
    {
        //执行该状态数据文件,首先执行进入状态函数,在进入状态函数执行相关的行为
        //进入状态,默认播放Fall动画
        animator.Play("PlayerFall");
    }
    
    //逻辑切换函数,当检测到处于某种状态,立刻执行该状态的数据文件
    //该脚本中,掉落时无法执行其它行为,若有需要可以添加判断
    public override void LogicUpdate()
    {
        if(isGround)
        {//落地进入Idle状态
            stateMachine.SwitchState(stateMachine.stateTable[typeof(PlayerState_Idle)]);
        }
    }
}

No.7 PlayerState_Run

[CreateAssetMenu(menuName = "StateMachine/PlayerState/Run", fileName = "PlayerState_Run")]//创建文件
public class PlayerState_Idle : PlayerState
{
    /*****************物理检测*******************/
    public override void Enter()
    {
        //执行该状态数据文件,首先执行进入状态函数,在进入状态函数执行相关的行为
        //进入状态,默认播放Run动画
        animator.Play("PlayerRun");
    }
    
    //逻辑切换函数,当检测到处于某种状态,立刻执行该状态的数据文件
    public override void LogicUpdate()
    {
        if(isIdle)
        {
            stateMachine.SwitchState(stateMachine.stateTable[typeof(PlayerState_Idle)]);
        }
        if(isJump)
        {
            stateMachine.SwitchState(stateMachine.stateTable[typeof(PlayerState_Jump)]);
        }
        if(my_Body2D.velocity.y<0&&!isGround)
        {
            stateMachine.SwitchState(stateMachine.stateTable[typeof(PlayerState_Fall)]);
        }
    }
}

以上便是一个简单的状态机

总结与拓展延伸

FSM可以大幅减少玩家动画间的判断和切换,只需要关注一个状态到另一个状态的切换条件,满足条件就切换,不满足就继续执行

FSM在玩家上的使用并不明显,因为在这上面,进入某种状态后,我们只播放了动画,并没有执行其他行为。当我们用在怪物AI中会更加的方便,例如怪物处于巡逻状态,播放巡逻动画,执行巡逻的脚本文件(建立一个类,专门用来存怪物AI个状态行为函数,通过传递的方式将该类对象传递给EnemyState,由EnemyState进行操作),怪物处于追击玩家状态,播放追击动画,并执行追击玩家的行为函数,在怪物AI中,FSM的应用会更加轻松的管理怪物,怪物可以放技能了,就进入技能状态,发现玩家就进入追击玩家,一个条件一个行为,更加有利于FSM进行管理

标签:状态,进阶,stateMachine,void,FSM,状态机,PlayerState,进入状态,public
From: https://www.cnblogs.com/CloudAndMist/p/17794640.html

相关文章

  • JS加密/解密之逻辑运算符加密进阶篇
    前言 前篇给大家介绍了运算符不为人知的基础知识。他们的各种表达形式,今天我们从这个基础上,继续进一步告诉大家,如何对字符串进行加密处理。还是那句话,技术人不废话,直接晒代码。示例源代码//字符串加密示例letstr="HelloWorld";//加密letencryptedString=[[]+[]+......
  • Unity进阶提升-2D游戏跳跃手感优化(跳起下落)
    在进行2D游戏开发时,跳跃是不可缺少的一个重要功能。但是我们在Unity开发时Unity本身的物理引擎并不能提供很好的的手感,下落的时候轻飘飘的,这操作起来显然非常不舒服。所以,我们需要自己对跳跃进行优化,以此来获得更好的手感。我们不难发现,在绝大多数2D游戏的跳跃中,下落的速度比上升......
  • Java提升技术,进阶为高级开发和架构师的路线
    简介Java怎样提升技术?怎样进阶为高级开发和架构师?本文介绍靠谱的成长路线。首先点明,只写业务代码是无法成长技术的。提升技术的两个方法是:有技术大佬带有技术大佬的资料本文介绍靠谱的技术进阶资料,让你比其他人更有竞争力!Java设计模式实战链接:这里用生活例子帮助理解模式的思维,用实......
  • Java提升技术,进阶为高级开发和架构师的路线
    ​ 原文网址:Java提升技术,进阶为高级开发和架构师的路线-CSDN博客简介Java怎样提升技术?怎样进阶为高级开发和架构师?本文介绍靠谱的成长路线。首先点明,只写业务代码是无法成长技术的。提升技术的两个方法是:有技术大佬带有技术大佬的资料本文介绍靠谱的技术进阶资料,让你比......
  • 类、事件与对象---Dad&Mom&Friends(进阶事件)
    接上一个笔记:https://www.cnblogs.com/StephenYoung/p/17792668.html现在增加了一个新的朋友类:Friends这个类构造如下:从上到下依次是:1、字段名称、2、要离开的事件、3、方法--离开主人家、4、Friends构造函数(方法)、5、属性---体重、6、方法--感谢、7、方法--吃席、Fri......
  • Linux wget和curl进阶使用
    wget下载单个文件从网络中通过url下载单个文件到当前目录,这是wget最基础的用法。wgeturl地址说明:使用wget下载一个文件时候,如果没有指定下载的文件名是什么,那么默认会使用URL的最后一部分作为默认的文件名,如果知道下载的这个文件叫什么名字,可以用-O(大写)参数来指定下载的......
  • python进阶知识体系md笔记14大体系200页,第2章:linux基础命令学习
    本文从14大模块展示了python高级用的应用。分别有Linux命令,多任务编程、网络编程、Http协议和静态Web编程、html+css、JavaScript、jQuery、MySql数据库的各种用法、python的闭包和装饰器、mini-web框架、正则表达式等相关文章的详细讲述。完整版笔记直接地址:请移步这里共14......
  • 【Java 进阶篇】JavaScript 正则表达式(RegExp)详解
    JavaScript正则表达式,通常简写为RegExp,是一种强大的文本匹配工具,它允许你通过一种灵活的语法来查找和替换字符串中的文本。正则表达式在编程中用途广泛,不仅限于JavaScript,在许多编程语言中也都有类似的实现。什么是正则表达式正则表达式,简称正则或RegExp,是一个用于描述字符模式......
  • 【Java 进阶篇】JavaScript Math对象详解
    在JavaScript编程中,Math对象是一个非常有用的工具,用于执行各种数学运算。它提供了许多数学函数和常数,可以用于处理数字、执行几何运算、生成随机数等。在本篇博客中,我们将深入探讨JavaScript中Math对象的各种功能和用法。什么是Math对象?Math对象是JavaScript的内置对象之一,它不需要......
  • 【Java 进阶篇】JavaScript 中的全局对象和变量
    JavaScript是一门非常强大的编程语言,它提供了许多全局对象和变量,以便于在整个应用程序中共享数据和功能。本文将详细介绍JavaScript中的全局对象和变量,包括全局对象、全局变量、全局函数以及它们的用途和示例。全局对象JavaScript中有一些全局对象,它们在整个应用程序中都可用。......