探秘Unity游戏开发中的状态设计模式

寻技术 C#编程 / 工具使用 2023年07月11日 81

一、状态模式定义

状态模式(state)在GoF中的解释:

让一个对象的行为随着内部状态的改变而改变,而该对象也想换了类一样。

二、实现范例

结构图:

Context(状态拥有者)

  /// <summary>
    /// Describe:状态拥有者
    /// Note:是一个具有“状态”属性的类,可以制定相关的接口,让外界能够得知状态的改变或通过操作让状态改变。
    /// 比如:游戏角色有潜行,攻击、施法等状态;好友上线,脱机、忙碌等状态;
    /// GoF使用TCP联网为例,有已连接、等待连接、短线等状态;
    /// </summary>
    public class Context
    {
        State state = null;
        public void Request(int Value)
        {
            state.Handle(Value);
        }
        public void SetState(State state)
        {
            this.state = state;
        }
    }

State(状态接口类)

 /// <summary>
    /// Describe:状态接口类
    /// Note:指定状态的接口,负责规范Context(状态拥有者)在特定状态下要表现的行为。
    /// </summary>
    public abstract class State
    {
        protected Context m_Context = null;
        public State(Context context)
        {
            m_Context = context;
        }
        public abstract void Handle(int Value);
    }

ConcreteState(具体状态类)

   /*
     Describe:具体状态类
     Note:
     1、继承自State(状态接口类)
     2、实现Context(状态拥有者)在特定状态下该有的行为。
 */
    //状态A
    public class ConcreteStateA : State
    {
        public ConcreteStateA(Context context) : base(context) 
        { }
        public override void Handle(int Value)
        {
            if (Value>10)
            {
                m_Context.SetState(new ConcreteStateB(m_Context));
            }
        }
    }
    //状态A
    public class ConcreteStateB : State
    {
        public ConcreteStateB(Context context) : base(context)
        { }
        public override void Handle(int Value)
        {
            if (Value > 10)
            {
                m_Context.SetState(new ConcreteStateC(m_Context));
            }
        }
    }
    //状态A
    public class ConcreteStateC : State
    {
        public ConcreteStateC(Context context) : base(context)
        { }
        public override void Handle(int Value)
        {
            if (Value > 10)
            {
                m_Context.SetState(new ConcreteStateA(m_Context));
            }
        }
    }

三、使用状态模式(state)实现游戏场景的转换

场景结构示意图:

ISceneState 场景类接口

  /// <summary>
    /// Describe:场景类接口
    /// Note:定义场景转换和执行时需要调用的方法。
    /// </summary>
    public class ISceneState
    {
        //状态名称
        private string m_StateName = "ISceneState";
        public string StateName
        {
            get => m_StateName;
            set => m_StateName = value;
        }
        //控制者
        protected SceneStateController m_Controller = null;
        //建造者
        public ISceneState(SceneStateController controller)
        {
            m_Controller = controller;
        }
        //开始
        public virtual void StateBegin() { }
        //结束
        public virtual void StateEnd() { }
        //更新
        public virtual void StateUpdate() { }
        //结束
        public override string ToString() 
        {
            return $"[I_SceneState: StateName={StateName}]";
        }
    }

三个可切换场景(主画面场景,主画面场景,战斗场景)

主画面场景

  /// <summary>
    /// Describe:开始场景
    /// </summary>
    public class StartState : ISceneState
    {
        public StartState(SceneStateController controller) : base(controller)
        {
            this.StateName="StartState";
        }
        //开始
        public override void StateBegin()
        {
            //可在此进行游戏数据加载和初始化
        }
        //更新
        public override void StateUpdate() 
        {
            //更换为
            m_Controller.SetState(new MainMenuState(m_Controller), "MainMenuScene");
        }
    }

主画面场景

 /// <summary>
    /// Author:maki
    /// Time:2021-12-2
    /// Describe:主画面场景
    /// </summary>
    public class MainMenuState:ISceneState
    {
        private Button tmpBtn;
        public MainMenuState(SceneStateController controller) : base(controller)
        {
            this.StateName = "MainMenuState";
        }
        //开始
        public override void StateBegin()
        {
            //获取开始按钮
            // tmpBtn=UITool.GetUIComponent<Button>("StartGameBtn");
            tmpBtn?.onClick.AddListener(()=>OnStartGameBtnClick(tmpBtn));
        }
        //开始游戏
        private void OnStartGameBtnClick(Button tmpBtn)
        {
            m_Controller.SetState(new BattleState(m_Controller), "BattleScene");
        }
    }

战斗场景

   /// <summary>
    /// Author:maki
    /// Time:2021-12-2
    /// Describe:战斗场景
    /// </summary>
    public class BattleState:ISceneState
    {
        public BattleState(SceneStateController controller) : base(controller)
        {
            this.StateName = "BattleState";
        }
        //开始
        public override void StateBegin() 
        { 
        }
        //结束
        public override void StateEnd() 
        {
        }
        //更新
        public override void StateUpdate() 
        {
            //输入
            InputProcess();
            //游戏逻辑
            //游戏是否结束
            m_Controller.SetState(new MainMenuState(m_Controller), "MainMenuState");
        }
        //输入
        private void InputProcess()
        {
          //玩家输入判断程序代码。。。
        }
    }

SceneStateController-场景状态控制者

   /// <summary>
    /// Author:maki
    /// Time:2021-12-2
    /// Describe:场景状态的拥有者(Context),保持当前游戏场景状态,并作为与GameLoop类互动的接口,
    /// 除此以外,也是U3D场景转换的地方。
    /// </summary>
    public class SceneStateController
    {
        private ISceneState m_State;
        private bool m_bRunBegin = false;
        public SceneStateController() { }
        //设置状态
        public void SetState(ISceneState state, string loadSceneName)
        {
            m_bRunBegin = false;
            //载入场景
            LoadScene(loadSceneName);
            //通知前一个State结束
            if (m_State != null)
                m_State.StateEnd();
            //设置
            m_State = state;
        }
        //载入场景
        private void LoadScene(string loadSceneName)
        {
            if (string.IsNullOrEmpty(loadSceneName))
            {
                return;
            }
            SceneManager.LoadScene(loadSceneName);
            //  Application.LoadLevel(loadSceneName); 过时
        }
        //更新
        public void StateUpdate()
        {
            //通知新的State开始
            if (m_State != null && m_bRunBegin == false)
            {
                m_State.StateBegin();
                m_bRunBegin = true;
            }
            if (m_State != null)
            {
                m_State.StateUpdate();
            }
        }
    }

GameLoop-游戏主循环类

 /// <summary>
    /// Author:maki
    /// Time:2021-12-2
    /// Describe:游戏主循环类
    /// Note:包含初始化游戏和定期调用更新操作
    /// </summary>
    public class GameLoop:MonoBehaviour
    {
        //场景状态
        SceneStateController sceneStateController = new SceneStateController();
       void Awake()
        {
            // 转换场景不会被删除
            GameObject.DontDestroyOnLoad(gameObject);
            //随机数种子
            Random.seed = (int)System.DateTime.Now.Ticks;
        }
        private void Start()
        {
            //设置起始场景
            sceneStateController.SetState(new StartState(sceneStateController), "");
        }
        private void Update()
        {
            sceneStateController.StateUpdate();
        }
    }

四、使用状态模式的优点

优点

  • 减少错误的发生并降低维护难度;
  • 状态执行环境单一化;
  • 项目之间可以共享场景;

具体来说,使用状态模式可以清楚地了解某个场景状态执行时所需要配合使用的类对象,并且减少因新增状态而需要大量修改现有程序代码的维护成本。

缺点

在应用有大量状态的系统时,会遇到“产生过多状态类”的情况,此时会伴随着类爆炸的问题。

关闭

用微信“扫一扫”