学习Unity的过程中会发现人物操作的代码如果用常规的各种bool来进行判定角色的运动状态,会使代码结构极为混乱,如果此时再引入新的状态简直就是屎山里放屁,稍有不慎全盘都得崩溃。
比如如果要做一个动作游戏,复杂的动作在布尔判断下简直就是一坨……
我们可以从Unity的动画状态机Animator找到灵感,利用enum枚举类型,做一个代码里的有限状态机
# 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可以更容易理清各个状态之间的关系,而且代码逻辑清晰,更容易添加新的状态,运用动作系统。
用多个状态机甚至可以进行状态联动。