状态模式的介绍
意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
何时使用:代码中包含大量与对象状态有关的条件语句。
如何解决:将各种具体的状态类抽象出来。
优点: 1、封装了转换规则。 2、枚举可能的状态,在枚举状态之前需要确定状态种类。 3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点:1、状态模式的使用必然会增加系统类和对象的个数。 2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 3、状态模式对”开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
使用场景: 1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者。
注意事项:在行为受状态约束的时候使用状态模式,而且状态不超过 5 个。
- 状态模式类图
状态模式在游戏中的常用场合
- 角色状态
- 动画状态机
- 场景切换管理
- AI实现
应用示例
下面就以简单的场景切换管理作为示例
一般玩家在进行游戏的时候都会经过的步骤是:登录->主城->战斗,退出游戏则顺序相反(如上图)
当然要实现这样的场景切换非常简单,Unity中也对应的API:
UnityEngine.SceneManagement.SceneManager.LoadScene("sceneName");
但是如果在每个需要跳转的场景的地方都使用这个方法进行跳转,这样的弊端是不利于我们进行管理,因为我们经常需要在进入场景的时候初始化数据和管理器等等,并且在场景退出的时候需要进行脏数据回收,销毁对象等等…如果跳转场景方法散落到项目的各个角落,那样对于我们的开发和维护是非常不利的,因为我要修改一个需求的时候要将项目中所有的相关的地方都进行修改,这样耗费的时间成本会非常多,并且非常容易遗漏掉需要修改的地方
那么…有没有什么方法能够很好的解决这个问题呢?答案就是:状态模式
- 状态基类
/// <summary>
/// 场景状态基类
/// </summary>
public abstract class BaseSceneState
{
private string m_StateName = string.Empty;
public string StateName
{
get { return m_StateName; }
set { m_StateName = value; }
}
protected SceneStateController m_Controller = null;
public BaseSceneState(SceneStateController controller)
{
this.m_Controller = controller;
m_StateName = this.GetType().Name;
}
// 进入状态
public abstract void Enter();
// 更新
public abstract void Update(float deltaTime);
// 固定更新
public abstract void FixedUpdate(float fixedDeltaTime);
// 退出状态
public abstract void Exit();
public override string ToString()
{
return string.Format("StateName:" + m_StateName);
}
}
- 具体状态类(以登录和大厅为例)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 登录场景状态
/// </summary>
public class LoginSceneState : BaseSceneState
{
public LoginSceneState(SceneStateController controller) : base(controller)
{
}
public override void Enter()
{
AudioManager.Instance.PlayMusic(AudioDefine.BGM_1);
}
public override void Update(float deltaTime)
{
// 在Update中监听按键是否按下Q,如果按下Q就切换到LobbySceneState
if (Input.GetKeyDown(KeyCode.Q))
{
m_Controller.SetSceneState(new LobbySceneState(m_Controller), "GameLobby");
}
}
public override void FixedUpdate(float fixedDeltaTime)
{
}
public override void Exit()
{
AudioManager.Instance.StopMusic();
}
}
using UnityEngine;
/// <summary>
/// 大厅场景状态
/// </summary>
public class LobbySceneState : BaseSceneState
{
public LobbySceneState(SceneStateController controller) : base(controller)
{
}
public override void Enter()
{
}
public override void Exit()
{
}
public override void FixedUpdate(float fixedDeltaTime)
{
}
public override void Update(float deltaTime)
{
// 在Update中监听按键是否按下Q,如果按下Q就切换到LoginSceneState
if (Input.GetKeyDown(KeyCode.Q))
{
m_Controller.SetSceneState(new LoginSceneState(m_Controller), "GameLogin");
}
}
}
- 场景状态控制器
using System;
using UnityEngine;
using UnityEngine.SceneManagement;
/// <summary>
/// 场景状态控制器
/// </summary>
public class SceneStateController
{
// 当前状态
private BaseSceneState m_CurrentState = null;
// 场景加载中执行的回调
private Action m_LoadingCallback = null;
// 标记场景是否加载完成
private bool m_SceneLoaded = false;
/// <summary>
/// 析构函数
/// </summary>
~SceneStateController()
{
if (m_CurrentState != null)
m_CurrentState.Exit();
m_CurrentState = null;
m_LoadingCallback = null;
}
/// <summary>
/// 设置场景状态
/// </summary>
/// <param name="toSceneState">目标状态</param>
/// <param name="loadSceneName">加载的场景名称</param>
public void SetSceneState(BaseSceneState toSceneState, string loadSceneName)
{
if (toSceneState == null)
{
throw new Exception("设置场景状态错误,目标状态为null!");
}
else
{
m_SceneLoaded = false;
LoadScene(loadSceneName);
if (m_CurrentState != null)
m_CurrentState.Exit();
m_CurrentState = toSceneState;
}
}
public void Update(float deltaTime)
{
if (m_LoadingCallback != null)
{
m_LoadingCallback();
}
else
{
if (m_CurrentState != null && !m_SceneLoaded)
{
// 场景加载完成后调用Enter方法
m_CurrentState.Enter();
m_SceneLoaded = true;
}
if (m_CurrentState != null)
{
m_CurrentState.Update(deltaTime);
}
}
}
public void FixedUpdate(float fixedDeltaTime)
{
if (m_LoadingCallback != null || !m_SceneLoaded) return;
if (m_CurrentState != null)
{
m_CurrentState.FixedUpdate(fixedDeltaTime);
}
}
// 加载场景
private void LoadScene(string sceneName)
{
if (string.IsNullOrEmpty(sceneName))
{
throw new Exception("加载场景失败,sceneName为空!");
}
else
{
// 异步加载场景
AsyncOperation async = SceneManager.LoadSceneAsync(sceneName);
m_LoadingCallback = () => OnSceneLoading(async);
}
}
// 场景加载中的回调方法
private void OnSceneLoading(AsyncOperation async)
{
// 加载进度,可以在这里加入Loading界面进度显示
float prg = async.progress;
// 加载完成(进度为1即加载完成)
if (prg == 1)
{
// 进行清理,如果打开了Loading界面在这里可以关闭
m_LoadingCallback = null;
async = null;
}
}
}
测试
namespace Blink.Game
{
/// <summary>
/// 游戏管理器
/// </summary>
public class GameManager : MonoBehaviour
{
// 场景状态控制器
private SceneStateController m_SceneStateController = null;
private void Start()
{
// 设置初始状态
m_SceneStateController = new SceneStateController();
m_SceneStateController.SetSceneState(new LoginSceneState(m_SceneStateController), "GameLogin");
}
private void Update()
{
if (m_SceneStateController != null)
{
m_SceneStateController.Update(Time.deltaTime);
}
}
private void FixedUpdate()
{
if (m_SceneStateController != null)
{
m_SceneStateController.FixedUpdate(Time.fixedDeltaTime);
}
}
}
}