思想:
对象内部状态改变时改变自身的行为
核心:
对对象的操作进行状态划分,每个状态都关联着一组输入。(读者需要了解的是思想,而不是代码,代码只是为了让读者更方便了解这一模式。这是最重要的!!!)
实现方式:
有限状态机:
以下是状态机比较通用的结构。可以满足大多数需求
状态机简易版
public enum UserState ///状态的Enum,划分状态类型
{
None,
MoveObj,
SelectObj
}
public abstract class StateBase //状态基类
{
//目标,让状态类知道自己服务谁,方便调用目标的资源,比如User中的Animation
protected User _targetUser;
public virtual void OnStart() { } //进入状态
public virtual void OnUpdate() { } //每帧更新
public virtual bool JudgeCanExit() { return true; } //能否退出
public virtual void OnExit() { } //退出
}
/// 实际状态类,与UserState中一一对应
public class MoveState : StateBase
{
public MoveState(User _user)
{
this._targetUser = _user;
}
}
public class SelectState : StateBase
{
public SelectState(User _user)
{
this._targetUser = _user;
}
}
public class StateMgr //状态管理类
{
private Dictionary<UserState, StateBase> _stateDic; //状态字典
private UserState _currentStateEnum; //当前状态Enum
private StateBase _currentState; //当前状态
public void Init(User _target) //状态初始化
{
_stateDic = new Dictionary<UserState, StateBase>();
_stateDic.Add(UserState.MoveObj, new MoveState(_target));
_stateDic.Add(UserState.SelectObj, new SelectState(_target));
}
public void OnUpdate() //更新脚本
{
if(_currentState != null)
_currentState.OnUpdate();
}
public void ChangeState(UserState _state) //切换状态
{
if (_state == _currentStateEnum)
return;
if (OnExitState())
{
_currentStateEnum = _state;
_currentState = _stateDic[_currentStateEnum];
_currentState.OnStart();
}
}
public bool OnExitState() //退出当前
{
if (_currentStateEnum != UserState.None)
{
if (!_currentState.JudgeCanExit())
{
return false;
}
else
{
_currentState.OnExit();
_currentStateEnum = UserState.None;
_currentState = null;
return true;
}
}
return true;
}
}
public class User //状态目标类
{
private StateMgr _stateMgt; //状态管理类
public Animation _animation;
public User()
{
_stateMgt = new StateMgr();
_stateMgt.Init(this);
}
private void Update()
{
_stateMgt.OnUpdate();
}
}
再次抽象
状态机简易版代码中,存在很多可以优化的方案,可以试想一下以下情形:项目中同时存在角色状态UserState,以及角色属性状态PropertyState。此时这套程序就需要设计为UserStateMgr(用户状态管理)以及PropertyStateMgr(属性状态管理)、以及两套的Enum。这显然可以进一步优化
该部分脚本较长,读者可先查看下方知识点讲解后再阅读代码,在此之前,读者需要理解上一版的脚本,因为知识点讲解很多是对比上一版进行讲解
public enum UserState ///状态的Enum,划分状态类型
{
None,
MoveObj,
SelectObj
}
public abstract class StateBase<T> //状态基类
{
protected T _target; //目标
public virtual void OnStart() { } //进入状态
public virtual void OnUpdate() { } //每帧更新
public virtual bool JudgeCanExit() { return true; } //能否退出
public virtual void OnExit() { } //退出
}
/// 实际状态类,与UserState中一一对应
public class MoveState : StateBase<User>
{
public MoveState(User _user)
{
this._target = _user;
}
}
public class SelectState : StateBase<User>
{
public SelectState(User _user)
{
this._target = _user;
}
}
public abstract class StateMgr<T> //状态管理类
{
protected T _target;
protected Dictionary<int, StateBase<T>> _stateDic; //状态字典
private int _currentStateEnum; //当前状态Enum
private StateBase<T> _currentState; //当前状态
public void Init(T _target)
{
this._target = _target;
_stateDic = new Dictionary<int, StateBase<T>>();
}
public void OnUpdate()
{
if(_currentState != null)
_currentState.OnUpdate();
}
public void ChangeState(int _state)
{
if (_state == _currentStateEnum)
return;
if (OnExitState())
{
_currentStateEnum = _state;
if (!_stateDic.TryGetValue(_state, out _currentState))
_stateDic.Add(_state, CreateState(_state));
_currentState = _stateDic[_currentStateEnum];
_currentState.OnStart();
}
}
public bool OnExitState()
{
if (_currentStateEnum != 0)
{
if (!_currentState.JudgeCanExit())
{
return false;
}
else
{
_currentState.OnExit();
_currentStateEnum = 0;
_currentState = null;
return true;
}
}
return true;
}
protected virtual StateBase<T> CreateState(int _stateIndex)
{
return null;
}
}
public class UserStateMgr :StateMgr<User>
{
protected override StateBase<User> CreateState(int _stateIndex)
{
switch (_stateIndex)
{
case 0:
return null;
case 1:
return new MoveState(_target);
case 2:
return new SelectState(_target);
}
return null;
}
}
public class User //用户类
{
private StateMgr<User> _stateMgt;
public Animation _animation;
public User()
{
_stateMgt = new UserStateMgr();
_stateMgt.Init(this);
}
private void Update()
{
_stateMgt.OnUpdate();
}
}
知识点:
1.为何给状态基类StateBase改为泛型StateBase<T>
为了抽象出具体状态的操作对象_target,如果不这么写那么对于操作对象(脚本中的User类需要向上抽象出一层Target类放在StateBase类中作为_target。或者删除StateBase中的_target,再在具体类中命名属性,这两种办法无疑很浪费资源,所以最优解是改为泛型)。
2.为何将StateMgr(状态管理)类进行抽象
在上一版脚本中,我们初始化状态时在Init中添加了不同的状态。但那是针对只有一类状态的情况,此时我们有多个类的状态。程序不知道该添加哪些具体的状态,所以对StateMgr进行抽象,然后添加CreateState获取该管理类中具体的状态。
3.状态字典为什么使用int类型,而不是enum
对于标记类型来说,enum无疑是最正确的方法,但这么做有两个原因
(1)enum做字典的Key,使用时会对enum进行装箱处理。
(2)以免在StateMgr(状态管理类中)再添加UserState(Enum)的泛型
4.为何在StateMgr(状态管理)类中添加泛型目标_target
对于状态管理类来说,是不需要目标这一概念的,但在创建实际状态的时候,也就是CreateState中,需要对状态赋值_target,所以添加泛型目标。
优缺点:
优点:对用户进行分层
1.回使复杂逻辑变得简单
2.可以隔离特殊属性:比如移动对象类,存在移动速度属性,如果不划分对象,选择对象类是可以读取到移动速度属性。
缺点:过多的状态会使类过于分散。
没有完美的设计模式,开发者需要自行判断使用环境。
下一篇会将状态模式变体的实现思想,比如下推状态机等。这篇内容涉及到的知识比较多,读者如果又看不懂可以私信作者,有时间的话会对读者的问题一一作答。