基于switch case语句的Unity中用于状态切换的FiniteStateMachine(FSM)

学习Unity的过程中会发现人物操作的代码如果用常规的各种bool来进行判定角色的运动状态,会使代码结构极为混乱,如果此时再引入新的状态简直就是屎山里放屁,稍有不慎全盘都得崩溃。

比如如果要做一个动作游戏,复杂的动作在布尔判断下简直就是一坨……

我们可以从Unity的动画状态机Animator找到灵感,利用enum枚举类型,做一个代码里的有限状态机

Unity的动画状态机

# Unity的动画状态机#

Finite State Machine,简称FSM

我的状态机分为两个部分

第一部分是Update里的内容,这部分内容的主要用途是获取玩家的输入数据并转换FSM状态

第二部分在FixUpdate里,用于执行玩家的操作

分开的主要原因是FixUpdate会减少因性能问题带来的不同机器造成的差距

这里要解释一下,再Unity中,Update的调用频率取决于使用者电脑的性能水平,而FixedUpdate的调用频率是固定的,为0.02s调取一次

而二者在同时调取时会优先执行FixedUpdate,这里面水很深,我还得研究

如果把获取玩家输入放在FixedUpdate里,那么会出现严重的操作不灵敏现象(除非你输入时正好再FixedUpdate的调取帧内)

如果把玩家操作执行代码放在Update里,如果你电脑性能还不错,那么一个1f的speed可能就能让角色飞到地图外……

这里面还牵扯到分辨率什么的问题……不过我就不是很懂了。

正题开始(代码里的注释都是用渣英语写的,看得懂就行

这里用M_Studio的Robbie项目作为参考,角色有移动,下蹲,跳跃,蓄力跳跃,下蹲跳跃等比较多的状态,M_Studio为了照顾萌新,代码写的直接但乱,我将代码用FSM改进如下

(跳跃用AddForece遇到了点问题,我还是把基础的状态机弄上来好了)

状态的枚举

public enum PlayerMovementState
{
    //The function as their name.
    Idle,
    Move,
    Crouch,
}

有关的参数的获取和变量的声明与定义:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    
    [Header("Public Movement pharameters")]
    //use to define Player's moving speed
    public float playerSpeed = 8f;
    //Use to divisor Player's crouching speed
    public float crouchSpeedDivisor  =3f;

    [Header("Private Movement pharameters")]
    private float inputDirection;

    [Header("Private Gameobject")]
    private Rigidbody2D playerRigidbody2D;
    private BoxCollider2D boxCollider2D;

    [Header("FSM")]
    public PlayerMovementState CurrentState;

    [Header("BoxColliderSize , Use to Crouch")]
    Vector2 colliderStandSize;
    Vector2 colliderStandOffset;
    Vector2 colliderCrouchSize;
    Vector2 colliderCrouchOffset;

    // Start is called before the first frame update
    void Start()
    {
        //get component
        playerRigidbody2D = GetComponent<Rigidbody2D>();
        boxCollider2D = GetComponent<BoxCollider2D>();
        //get collider Size
        colliderStandSize = boxCollider2D.size;
        //get collider offset
        colliderStandOffset = boxCollider2D.offset;

        //Set crouch collider size and offset
        colliderCrouchSize = new Vector2(colliderStandSize.x, colliderStandSize.y / 2f);
        colliderCrouchOffset = new Vector2(colliderStandOffset.x , colliderStandOffset.y / 2f);

        //Default state is Idle
        CurrentState = PlayerMovementState.Idle;
    }

FSM转换部分(Update):

    void Update()
    {
        //FSM
        //Obtaining Player's Input and transforming the State in Update
        switch (CurrentState)
        {
            case PlayerMovementState.Idle:
                {
                    //Horizontal = KeyCode(WASD) , transform to Move
                    if (Input.GetButton("Horizontal"))
                    {
                        CurrentState = PlayerMovementState.Move;
                    }
                    //Crouch = KeyCode.S ,transform to Crouch
                    if (Input.GetButton("Crouch"))
                    {
                        CurrentState = PlayerMovementState.Crouch;
                    }
                    //Jump = KeyCode.Space,transform to Jump State
                    break;

                }
            case PlayerMovementState.Move:
                {
                    //If the Player'speed == 0,transform to Idle
                    if (playerRigidbody2D.velocity.x == 0)
                    {
                        CurrentState = PlayerMovementState.Idle;
                    }
                    break;
                }
            case PlayerMovementState.Crouch:
                {
                    //If release Crouch botton, transform to Idle
                    if (!Input.GetButton("Crouch"))
                    {
                        //Restore size and offset
                        boxCollider2D.size = colliderStandSize;
                        boxCollider2D.offset = colliderStandOffset;
                        CurrentState = PlayerMovementState.Idle;
                    }
                    break;
                }
   
    }

FSM执行部分(FixedUpdate)

    private void FixedUpdate()
    {
        //FSM
        //Implement the State
        switch (CurrentState)
        {
            case PlayerMovementState.Idle:
                {
                    Idle();
                    break;
                }
            case PlayerMovementState.Move:
                {
                    Move();
                    break;
                }
            case PlayerMovementState.Crouch:
                {
                    Crouch();
                    break;
                }
        }
    }

具体函数:

 public void Idle()
    {
        if (!Input.GetButton("Horizontal"))
        {
            playerRigidbody2D.velocity = Vector2.zero;
        }
        isJump = false;
    }
    public void Move()
    {
        isJump = false;
        //Get direction
        inputDirection = Input.GetAxis("Horizontal");
        //Give a velocity to let it move
        playerRigidbody2D.velocity = new Vector2(inputDirection * playerSpeed, 0);
    }

    public void Crouch()
    {
        //Cutting Collider in half to achieve a Crouch effect
        boxCollider2D.size = colliderCrouchSize;
        boxCollider2D.offset = colliderCrouchOffset;

        //When the player is crouched and Horizontal button is pressed, the Player moves at a lower speed(crouchspeed).
        //Get direction
        inputDirection = Input.GetAxis("Horizontal");
        //Give a velocity
        playerRigidbody2D.velocity = new Vector2(inputDirection * playerSpeed / crouchSpeedDivisor, 0);

        
    }

显而易见的是,用FSM可以更容易理清各个状态之间的关系,而且代码逻辑清晰,更容易添加新的状态,运用动作系统。

用多个状态机甚至可以进行状态联动。

猜你喜欢

转载自blog.csdn.net/qq_36486755/article/details/127403774