什么时候用状态模式?
物体有明确的状态,在指定分支需要切换。
不用switch : case的理由?
代码太庞大。
文章
UML图
解决同一类问题而总结出的代码写法。
1. 概述
当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
2. 解决的问题
主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同的一系列类当中,可以把复杂的逻辑判断简单化。
3. 模式中的角色
3.1 上下文环境(Context) :它定义了客户程序需要的接口并维护一个具体状态角色的实例,将与状态相关的操作委托给当前的Concrete State对象来处理。
3.2 抽象状态(State) :定义一个接口以封装使用上下文环境的的一个特定状态相关的行为。
3.3** 具体状态(Concrete State)**:实现抽象状态定义的接口。
应用一:状态模式控制场景切换
代码
GameLoop
csharp
using UnityEngine;
public class GameLoop : MonoBehaviour {
private SceneStateController controller = null;
void Awake()
{
//不会随场景切换而销毁
DontDestroyOnLoad(this.gameObject);
}
// Use this for initialization
void Start () {
controller = new SceneStateController();
controller.SetState(new StartState(controller),false);
}
// Update is called once per frame
void Update () {
if(controller!=null)
controller.StateUpdate();
}
}
SceneStateController
csharp
using UnityEngine.SceneManagement;
using UnityEngine;
/*
* /场景切换控制器
*/
public class SceneStateController
{
private ISceneState mState;
private AsyncOperation mAO;
//控制执行一次
private bool mIsRunStart = false;
public void SetState(ISceneState state,bool isLoadScene=true)
{
if (mState != null)
{
mState.StateEnd();//让上一个场景状态做一下清理工作
}
mState = state;
if (isLoadScene)
{
mAO = SceneManager.LoadSceneAsync(mState.SceneName);
mIsRunStart = false;
} else
{
mState.StateStart();
mIsRunStart = true;
}
}
public void StateUpdate()
{
if (mAO != null && mAO.isDone == false) return;
if (mIsRunStart==false&& mAO != null && mAO.isDone == true)
{
mState.StateStart();
mIsRunStart = true;
}
if (mState != null)
{
mState.StateUpdate();
}
}
}
ISceneState
csharp
using System;
using System.Collections.Generic;
using System.Text;
public class ISceneState
{
private string mSceneName;
protected SceneStateController mController;
public ISceneState(string sceneName,SceneStateController controller)
{
mSceneName = sceneName;
mController = controller;
}
public string SceneName
{
get { return mSceneName; }
}
//每次进入到这个状态的时候调用
/// <summary>
/// 组件获取、初始化流程
/// </summary>
public virtual void StateStart(){ }
/// <summary>
/// 按帧调用
/// </summary>
public virtual void StateUpdate(){ }
/// <summary>
/// 销毁物体等
/// </summary>
public virtual void StateEnd(){ }
}
MainMenuState
csharp
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
public class MainMenuState:ISceneState
{
public MainMenuState(SceneStateController controller) : base("02MainMenuScene", controller) { }
public override void StateStart()
{
GameObject.Find("StartButton").GetComponent<Button>().onClick.AddListener(OnStartButtonClick);
}
private void OnStartButtonClick()
{
mController.SetState(new BattleState(mController));
}
}
BattleState
csharp
using System;
using System.Collections.Generic;
using System.Text;
public class BattleState:ISceneState
{
public BattleState(SceneStateController controller):base("03BattleScene",controller){}
//兵营 关卡 角色管理 行动力 成就系统。。。
public override void StateStart()
{
GameFacade.Insance.Init();
}
public override void StateEnd()
{
GameFacade.Insance.Release();
}
public override void StateUpdate()
{
if (GameFacade.Insance.isGameOver)
{
mController.SetState(new MainMenuState(mController));
}
GameFacade.Insance.Update();
}
}
应用二:状态模式控制moba兵线
双方建造出的士兵进入默认状态(向对方水晶移动)。在【攻击范围内】捕捉到敌人时会参与战斗。战斗结束后恢复默认状态。
UML图
箭头表示控制关系
有限状态机
分析有限状态
ISoldier战士类
csharp
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
public enum SoldierType
{
Rookie,
Sergeant,
Captain,
Captive
}
public abstract class ISoldier:ICharacter
{
protected SoldierFSMSytem mFSMSystem;
public ISoldier():base()
{
MakeFSM();
}
public override void UpdateFSMAI( List<ICharacter> targets )
{
if (mIsKilled) return;
mFSMSystem.currentState.Reason(targets);
mFSMSystem.currentState.Act(targets);
}
private void MakeFSM()
{
mFSMSystem = new SoldierFSMSytem();
SoldierIdleState idleState = new SoldierIdleState(mFSMSystem, this);
idleState.AddTransition(SoldierTransition.SeeEnemy, SoldierStateID.Chase);
SoldierChaseState chaseState = new SoldierChaseState(mFSMSystem, this);
chaseState.AddTransition(SoldierTransition.NoEnmey, SoldierStateID.Idle);
chaseState.AddTransition(SoldierTransition.CanAttack, SoldierStateID.Attack);
SoldierAttackState attackState = new SoldierAttackState(mFSMSystem, this);
attackState.AddTransition(SoldierTransition.NoEnmey, SoldierStateID.Idle);
attackState.AddTransition(SoldierTransition.SeeEnemy, SoldierStateID.Chase);
mFSMSystem.AddState(idleState,chaseState,attackState);
}
public override void UnderAttack(int damage)
{
if (mIsKilled) return;
base.UnderAttack(damage);
if (mAttr.currentHP <= 0)
{
PlaySound();
PlayEffect();
Killed();
}
}
public override void Killed()
{
base.Killed();
GameFacade.Insance.NotifySubject(GameEventType.SoldierKilled);
}
protected abstract void PlaySound();
protected abstract void PlayEffect();
public override void RunVisitor(ICharacterVisitor visitor)
{
visitor.VisitSoldier(this);
}
}
SoldierFSMSytem战士的有限状态机
csharp
using System.Collections.Generic;
using UnityEngine;
//战士有限状态机管理类
public class SoldierFSMSytem
{
private List<ISoldierState> mStates = new List<ISoldierState>();
private ISoldierState mCurrentState;
public ISoldierState currentState { get { return mCurrentState; } }
public void AddState(params ISoldierState[] states)
{
foreach (ISoldierState s in states)
{
AddState(s);
}
}
public void AddState(ISoldierState state)
{
if (state == null)
{
Debug.LogError("要添加的状态为空"); return;
}
if (mStates.Count == 0)
{
mStates.Add(state);
mCurrentState = state;
return;
}
foreach (ISoldierState s in mStates)
{
if (s.stateID == state.stateID)
{
Debug.LogError("要添加的状态ID[" + s.stateID + "]已经添加"); return;
}
}
mStates.Add(state);
}
public void DeleteState(SoldierStateID stateID)
{
if (stateID == SoldierStateID.NullState)
{
Debug.LogError("要删除的状态ID为空" + stateID); return;
}
foreach (ISoldierState s in mStates)
{
if (s.stateID == stateID)
{
mStates.Remove(s); return;
}
}
Debug.LogError("要删除的StateID不存在集合中:" + stateID);
}
/// <summary>
/// 转换状态
/// </summary>
/// <param name="trans"></param>
public void PerformTransition(SoldierTransition trans)
{
if (trans == SoldierTransition.NullTansition)
{
Debug.LogError("要执行的转换条件为空 : " + trans); return;
}
//获取转换后状态的ID
SoldierStateID nextStateID = mCurrentState.GetOutPutState(trans);
if (nextStateID == SoldierStateID.NullState)
{
Debug.LogError("在转换条件 [" + trans + "] 下,没有对应的转换状态"); return;
}
foreach (ISoldierState s in mStates)
{
if (s.stateID == nextStateID)
{
//转换
mCurrentState.DoBeforeLeaving();
mCurrentState = s;
mCurrentState.DoBeforeEntering();
return;
}
}
}
}
ISoldierState战士的状态基类
csharp
using System.Collections.Generic;
using UnityEngine;
/*
* /战士状态转换的接口
*/
public enum SoldierTransition
{
NullTansition = 0,
SeeEnemy,
NoEnmey,
CanAttack
}
public enum SoldierStateID
{
NullState,
Idle,
Chase,
Attack
}
public abstract class ISoldierState
{
//保存已添加的状态
protected Dictionary<SoldierTransition, SoldierStateID> mMap = new Dictionary<SoldierTransition, SoldierStateID>();
protected SoldierStateID mStateID;
//状态所属角色
protected ICharacter mCharacter;
protected SoldierFSMSytem mFSM;
public ISoldierState(SoldierFSMSytem fsm,ICharacter character)
{
mFSM = fsm;
mCharacter = character;
}
public SoldierStateID stateID { get { return mStateID; } }
/// <summary>
/// 添加状态
/// </summary>
/// <param name="trans"></param>
/// <param name="id"></param>
public void AddTransition(SoldierTransition trans, SoldierStateID id)
{
//安全检测1
if (trans == SoldierTransition.NullTansition)
{
Debug.LogError("SoldierState Error: trans不能为空"); return;
}
//安全检测2
if (id == SoldierStateID.NullState)
{
Debug.LogError("SoldierState Error: id状态ID不能为空"); return;
}
//错误检测
if (mMap.ContainsKey(trans))
{
Debug.LogError("SoldierState Error: " + trans + " 已经添加上了"); return;
}
mMap.Add(trans, id);
}
public void DeleteTransition(SoldierTransition trans)
{
if (mMap.ContainsKey(trans) == false)
{
Debug.LogError("删除转换条件的时候, 转换条件:[" + trans + "]不存在"); return;
}
mMap.Remove(trans);
}
/// <summary>
/// 获取下一个状态ID
/// </summary>
/// <param name="trans"></param>
/// <returns></returns>
public SoldierStateID GetOutPutState(SoldierTransition trans)
{
if (mMap.ContainsKey(trans) == false)
{
return SoldierStateID.NullState;
} else
{
return mMap[trans];
}
}
public virtual void DoBeforeEntering() { }
public virtual void DoBeforeLeaving() { }
public abstract void Reason( List<ICharacter> targets );
public abstract void Act(List<ICharacter> targets);
}
战士的具体状态
csharp
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
public class SoldierIdleState:ISoldierState
{
public SoldierIdleState(SoldierFSMSytem fsm, ICharacter c) : base(fsm, c) { mStateID = SoldierStateID.Idle; }
public override void Reason(List<ICharacter> targets)
{
if (targets != null && targets.Count > 0)
{
mFSM.PerformTransition(SoldierTransition.SeeEnemy);
}
}
public override void Act(List<ICharacter> targets)
{
mCharacter.PlayAnim("stand");
}
}
csharp
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
public class SoldierChaseState:ISoldierState
{
public SoldierChaseState(SoldierFSMSytem fsm, ICharacter c) : base(fsm, c) { mStateID = SoldierStateID.Chase; }
public override void Reason(List<ICharacter> targets)
{
if (targets == null || targets.Count == 0)
{
mFSM.PerformTransition(SoldierTransition.NoEnmey); return;
}
float distance = Vector3.Distance(targets[0].position, mCharacter.position);
//在攻击距离内
if (distance <= mCharacter.atkRange)
{
mFSM.PerformTransition(SoldierTransition.CanAttack);
}
}
public override void Act(List<ICharacter> targets)
{
if (targets != null && targets.Count > 0)
{
mCharacter.MoveTo(targets[0].position);
}
}
}
csharp
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
public class SoldierAttackState:ISoldierState
{
public SoldierAttackState(SoldierFSMSytem fsm,ICharacter c) : base(fsm,c) {
mStateID = SoldierStateID.Attack;
mAttackTimer = mAttackTime;
}
private float mAttackTime = 1;
private float mAttackTimer = 1;
public override void Reason(List<ICharacter> targets)
{
if (targets == null || targets.Count == 0)
{
mFSM.PerformTransition(SoldierTransition.NoEnmey); return;
}
float distance = Vector3.Distance(mCharacter.position, targets[0].position);
if (distance > mCharacter.atkRange)
{
mFSM.PerformTransition(SoldierTransition.SeeEnemy);
}
}
public override void Act(List<ICharacter> targets)
{
if (targets == null || targets.Count == 0) return;
mAttackTimer += Time.deltaTime;
if (mAttackTimer >= mAttackTime)
{
mCharacter.Attack(targets[0]);
mAttackTimer = 0;
}
}
}