有时候在程序开发中,我们会经常碰到一些流程逻辑相关的问题,这个时候我们如果没有一个好的框架和方案的话,那么流程管理就很麻烦,这时候状态机的出现就使问题简单化啦,下面让我们来实现一个简单的状态机吧。
状态节点接口:
public interface IStateNode
{
void OnCreate(StateMachine stateMachine);
void OnEnter();
void OnUpdate();
void OnExit();
}
接下来就是主角,我们的状态机管理类,也可以称之为操作手吧
using System;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEditor.Experimental.GraphView;
public class StateMachine
{
private readonly Dictionary<string,IStateNode> _nodes=new Dictionary<string,IStateNode>(100);
private IStateNode _curNode;
private IStateNode _preNode;
/// <summary>
/// 状态机持有者
/// </summary>
public System.Object Owner { private set; get; }
/// <summary>
/// 当前节点的名称
/// </summary>
public string CurrentNode
{
get
{
return _curNode!=null?_curNode.GetType().FullName:string.Empty;
}
}
/// <summary>
/// 之前运行的节点名称
/// </summary>
public string PreviousNode
{
get
{
return _preNode != null ? _preNode.GetType().FullName : string.Empty;
}
}
private StateMachine() { }
public StateMachine(System.Object owner)
{
Owner = owner;
}
/// <summary>
/// 更新状态机
/// </summary>
public void Update()
{
if (_curNode != null)
{
_curNode.OnUpdate();
}
}
public void Run<TNode>() where TNode : IStateNode
{
var nodeType = typeof(TNode);
var nodeName = nodeType.FullName;
Run(nodeName);
}
public void Run(Type entryNode)
{
var nodeName = entryNode.FullName;
Run(nodeName);
}
public void Run(string entryNode)
{
_curNode = TryGetNode(entryNode);
_preNode = _curNode;
if (_curNode == null)
throw new Exception($"Not found entry node:{entryNode}");
_curNode.OnEnter();
}
public void AddNode<TNode>() where TNode: IStateNode, new()
{
TNode stateNode = new TNode();
AddNode(stateNode);
}
/// <summary>
/// 加入一个节点
/// </summary>
/// <param name="stateNode"></param>
/// <exception cref="ArgumentException"></exception>
public void AddNode(IStateNode stateNode)
{
if (stateNode == null)
throw new ArgumentException();
var nodeType = stateNode.GetType();
var nodeName = nodeType.FullName;
if (_nodes.ContainsKey(nodeName) == false)
{
stateNode.OnCreate(this);
_nodes.Add(nodeName, stateNode);
}
else
{
UnityEngine.Debug.LogError($"State node already existed: {nodeName}");
}
}
public void ChangeState<TNode>() where TNode : IStateNode
{
var nodeType = typeof(TNode);
var nodeName = nodeType.FullName;
ChangeState(nodeName);
}
public void ChangeState(Type nodeType)
{
var nodeName = nodeType.FullName;
ChangeState(nodeName);
}
public void ChangeState(string nodeName)
{
if (string.IsNullOrEmpty(nodeName))
throw new ArgumentNullException();
IStateNode node = TryGetNode(nodeName);
if (node == null)
{
UnityEngine.Debug.LogError($"Can not found state node:{nodeName}");
return;
}
UnityEngine.Debug.Log($"{_curNode.GetType().FullName}-->{node.GetType().FullName}");
_preNode = _curNode;
_curNode.OnExit();
_curNode = node;
_curNode.OnEnter();
}
private IStateNode TryGetNode(string nodeName)
{
_nodes.TryGetValue(nodeName, out IStateNode result);
return result;
}
}
因为每一个节点都要在Create的时候记录状态机对象,这时候我实现了一个简单的节点基类,在Create的时候实现统一记录自己的状态机对象,其他状态机节点都继承这个节点基类,简化了每一个状态节点都需要在创建的时候保存状态机管理类的问题。
public class StateNodeBase : IStateNode
{
protected StateMachine _machine;
public virtual void OnCreate(StateMachine stateMachine)
{
_machine = stateMachine;
}
public virtual void OnEnter()
{
}
public virtual void OnUpdate()
{
}
public virtual void OnExit()
{
}
}
最后让我们来看看几个简答的状态机节点吧,
public class FirstStep : StateNodeBase
{
public override void OnCreate(StateMachine stateMachine)
{
base.OnCreate(stateMachine);
}
public override void OnExit()
{
base.OnExit();
_machine.ChangeState<SecondStep>();
}
}
public class SecondStep : StateNodeBase
{
public override void OnCreate(StateMachine stateMachine)
{
base.OnCreate(stateMachine);
}
public override void OnExit()
{
base.OnExit();
_machine.ChangeState<ThirdStep>();
}
}
public class ThirdStep : StateNodeBase
{
public override void OnCreate(StateMachine stateMachine)
{
base.OnCreate(stateMachine);
}
public override void OnExit()
{
base.OnExit();
_machine.ChangeState<FourthStep>();
}
}
public class FourthStep : StateNodeBase
{
public override void OnCreate(StateMachine stateMachine)
{
base.OnCreate(stateMachine);
}
public override void OnExit()
{
base.OnExit();
_machine.ChangeState<FirstStep>();
}
}
好了,最基本的状态机框架我们已经搭建完成了,这时候就是最关键的如何去开启这个状态机和使用他了,因为我们是在Unity中实现,所以需要一个MonoBehaviour来启动他。
public class GameProcessCtrl:MonoBehaviour
{
private StateMachine _machine;
private void Start()
{
InitStateMachine();
}
private void InitStateMachine()
{
_machine = new StateMachine(this);
_machine.AddNode<FirstStep>();
_machine.AddNode<SecondStep>();
_machine.AddNode<ThirdStep>();
_machine.AddNode<FourthStep>();
_machine.Run<FirstStep>();
}
private void Update()
{
if (_machine != null)
{
_machine.Update();
}
}
}
好了,到这里一个简单的状态机就实现完成啦,在这个实例中我们默认启动了第一阶段的阶段,之后在退出的时候分别切换到下一节点,当然我们也可以在每一个节点运行的时候进行判断,满足某种条件的时候直接跳转其他节点,这些都是要根据具体内容自己来设计规划啦。