对状态机的介绍
什么是状态机?一篇文章就够了 - 知乎
说实话,目前并不能深入理解状态机的奇妙之处(当然,我觉得状态机作为教程的重要组成部分是不得不理解的,所以以下我会对游戏教程内的状态机做一些我认为的解释,如有错误,请多包涵)
基本的Player状态机组成
基本的Player状态机,我认为由4个部分组成
1.Player脚本,与Player组件绑定,能提供Unity自带的MonoBehavior来实现游戏内的更新与初始化。
2.PlayerState脚本,作为所有的状态,例如游戏角色奔跑状态,静止状态的父脚本,提供一个最基础的框架
3.PlayerStateMachine脚本,作为在一定条件下,转换一个角色的状态的机器。及从奔跑状态转移到静止状态的脚本
4.Player-XX-State脚本,继承了PlayerState脚本,但是实际上表示了真正的具体的动作的脚本。
简单实例说明
源代码
Player.cs
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class Player : MonoBehaviour
{
#region 定义States
public PlayerStateMachine stateMachine { get; private set; }
public PlayerIdleState idleState { get; private set; }
public PlayerMoveState moveState { get; private set; }
#endregion
private void Awake()
{
stateMachine = new PlayerStateMachine();
//通过构造函数,在构造时传递信息
idleState = new PlayerIdleState(this, stateMachine, "Idle");
moveState = new PlayerMoveState(this, stateMachine, "Move");
//this 就是 Player这个类本身
}//Awake初始化所以State,为所有State传入各自独有的参数,及animBool,以判断是否调用此动画(与animatoin配合完成)
private void Start()
{
stateMachine.Initialize(idleState);
}
private void Update()//在mano中update会自动刷新但其他没有mano的不会故,需要在这个updata中调用其他脚本中的函数stateMachine.currentState.update以实现 //stateMachine中的update
{
stateMachine.currentState.Update();//反复调用CurrentState的Update函数
}
}
PlayerState.cs
using System.Collections;
using System.Collections.Generic;
using System.Security.Authentication.ExtendedProtection;
using UnityEngine;
//被继承的总类,后面的所以state都需要继承它
//实际上Player并没有进入过这个状态,没有调用此脚本,所有的调用均属于此脚本的子脚本
public class PlayerState
{
protected PlayerStateMachine stateMachine;//创建PlayerStateMachine类,以对其进行控制
protected Player player;//创建Player类,以对其进行控制
private string animBoolName;//控制此时的anim的BOOL值
public PlayerState(Player _player,PlayerStateMachine _stateMachine,string _animBoolName)
{
this.player = _player;
this.stateMachine = _stateMachine;
this.animBoolName = _animBoolName;
}//构造函数,用于传递信息
public virtual void Enter()
{
Debug.Log("I enter+" + animBoolName);
}
public virtual void Update()
{
Debug.Log("I'm in+" + animBoolName);
}
public virtual void Exit()
{
Debug.Log("I exit " + animBoolName);
}
}
PlayerStateMachine.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerStateMachine
{
public PlayerState currentState { get; private set; }
public void Initialize(PlayerState _startState)
{
currentState = _startState;
currentState.Enter();
}//初始化状态函数,及通过将idleState传送进来,以便于调用idleState中的Enter函数
public void ChangeState(PlayerState _newState)
{
currentState.Exit();
currentState = _newState;
currentState.Enter();
}//更改状态函数
//1.调用当前状态的Exit函数,使动画为false
//2.传入新的状态,替换原来的状态
//3.调用新的状态的Enter函数
}
PlayerIdleState.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerIdleState : PlayerState//继承函数,获得PlayerState里的所有函数和参数
{
public PlayerIdleState(Player _player, PlayerStateMachine _stateMachine, string _animBoolName) : base(_player, _stateMachine, _animBoolName)
{
}//构造函数,用于传递信息。
//当外补New出对象时,New出的对象里传入参数
public override void Enter()
{
base.Enter();
}
public override void Exit()
{
base.Exit();
}
public override void Update()
{
base.Update();
}
}
基本代码运行逻辑
在游戏运行时,Unity引擎会自动调用与组件绑定的脚本进行初始化,如何将带有MonoBehavior的脚本的Awake和Enter和Update函数调用。
---------------------------------------------------------------------
Player.cs
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class Player : MonoBehaviour
{
#region 定义States
public PlayerStateMachine stateMachine { get; private set; }
public PlayerIdleState idleState { get; private set; }
public PlayerMoveState moveState { get; private set; }
#endregion
private void Awake()
{
stateMachine = new PlayerStateMachine();
//通过构造函数,在构造时传递信息
idleState = new PlayerIdleState(this, stateMachine, "Idle");
moveState = new PlayerMoveState(this, stateMachine, "Move");
//this 就是 Player这个类本身
}//Awake初始化所以State,为所有State传入各自独有的参数,及animBool,以判断是否调用此动画(与animatoin配合完成)
private void Start()
{
stateMachine.Initialize(idleState);
}
private void Update()//在mano中update会自动刷新但其他没有mano的不会故,需要在这个updata中调用其他脚本中的函数stateMachine.currentState.update以实现 //stateMachine中的update
{
stateMachine.currentState.Update();//反复调用CurrentState的Update函数
}
}
---------------------------------------------------------------------
在Awake函数中,我们实现了对其他三个类型的声明对象调用并传入了参数。
此时我们就相当于在Player中获得了其他3个脚本也就是PlayerStateMachine PlayerIdleState PlayerMoveState的所有参数与函数,且参数被初始化。
由于这只是状态机的框架,Idle和MoveState里并没有写入新的参数与函数,所以让我们先对PlayerState进行分析,因为这是Idle和MoveState的父脚本。这两个脚本里的参数,除了通过
//通过构造函数,在构造时传递信息,改变了他的animBoolName以外没有任何的区别。
---------------------------------------------------------------------
PlayerState.cs
using System.Collections;
using System.Collections.Generic;
using System.Security.Authentication.ExtendedProtection;
using UnityEngine;
//被继承的总类,后面的所以state都需要继承它
//实际上Player并没有进入过这个状态,没有调用此脚本,所有的调用均属于此脚本的子脚本
public class PlayerState
{
protected PlayerStateMachine stateMachine;//创建PlayerStateMachine类,以对其进行控制
protected Player player;//创建Player类,以对其进行控制
private string animBoolName;//控制此时的anim的BOOL值
public PlayerState(Player _player,PlayerStateMachine _stateMachine,string _animBoolName)
{
this.player = _player;
this.stateMachine = _stateMachine;
this.animBoolName = _animBoolName;
}//构造函数,用于传递信息
public virtual void Enter()
{
Debug.Log("I enter+" + animBoolName);
}
public virtual void Update()
{
Debug.Log("I'm in+" + animBoolName);
}
public virtual void Exit()
{
Debug.Log("I exit " + animBoolName);
}
}
---------------------------------------------------------------