游戏开发中的设计模式——1.状态模式

这篇博文我记录了状态模式在游戏开发中的简单应用,用于与切换各个游戏场景。但是在实际的游戏开发中,本人并不建议使用这种方式切换游戏场景,因为Unity本来就是基于组件的游戏引擎。但是,知识学多了有害无益,为了学习而学习嘛。
先上总图:
这里写图片描述
上图中分为两个模块,第一个是通用模式下的原型代码(主要涉及到状态模式的概念理解和原型代码实现,不做详细讲解哈),第二个是在游戏开发中的实际应用,三个场景分别对应三个状态,使用状态模式切换各个游戏场景(状态)。

1.先简单给出状态模式在通用模式下的原型代码:
在Unity中新建一个场景,把下面的脚本挂载到任意一个游戏对象上即可

using UnityEngine;
using System.Collections;

public class StateDesignMood : MonoBehaviour {

    // Use this for initialization
    void Start () {
        Context context = new Context();
        context.SetState(new ConcreteStateA(context));

        context.Handle(5);
        context.Handle(20);
        context.Handle(30);
        context.Handle(4);
        context.Handle(50);
    }
}
///1. 概述:当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
/// 2. 解决的问题:主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同的一系列类当中,可以把复杂的逻辑判断简单化。
/// <summary>
///  上下文环境类:它定义了客户程序需要的接口并维护一个具体状态角色的实例,将与状态相关的操作委托给当前的Concrete State对象来处理。
///  就好比该类是游戏中的一个主角,他需要进行各种不同的状态
/// </summary>
public class Context {
    private IState state;
    public void SetState(IState state) {
        this.state = state;
    }
    public void Handle(int arg) {
        state.Handle(arg);
    }
}

/// <summary>
/// 抽象状态接口(State):定义一个接口以封装使用上下文环境的的一个特定状态相关的行为。
/// </summary>
public interface IState {
    void Handle(int arg);
}

/// <summary>
/// 具体状态类A(Concrete StateA):实现抽象状态定义的接口
/// </summary>
public class ConcreteStateA : IState
{
    private Context context;
    public ConcreteStateA(Context context)
    {
        this.context = context;
     }
    public void Handle(int arg)
    {
        Debug.Log("我是具体状态类A," + arg);
        if (arg > 10)//如果传入的参数大于10就转向另一个具体状态B处理
        {
            this.context.SetState(new ConcreteStateB(this.context));
        }
    }
}

/// <summary>
/// 具体状态类B(Concrete StateB):实现抽象状态定义的接口
/// </summary>
public class ConcreteStateB : IState
{
    private Context context;
    public ConcreteStateB(Context context)
    {
        this.context = context;
    }

    public void Handle(int arg)
    {
        Debug.Log("我是具体状态类B," + arg);
        if (arg <= 10)//如果传入的参数大于10就转向另一个具体状态A处理
        {
            this.context.SetState(new ConcreteStateA(this.context));
        }
    }
}

2.在这个游戏开发中,状态对应的就是场景,有3个游戏场景:开始场景,菜单场景,战斗场景,这些场景只是一个简单的雏形,没有实现其具体的功能,因为我们这个模式的主题内容不在这,而在于使用状态模式实现场景的切换。
三个场景:
这里写图片描述
这里写图片描述

六个脚本(具体功能下面讲解中体现):
这里写图片描述

脚本UML图(重点理解,可参照普通模式下原型代码的UML图加深理解):
这里写图片描述

GameLoop脚本用于启动整个项目:

using UnityEngine;
using System.Collections;

/// <summary>
/// 此类用来启动状态模式下的场景管理器
/// </summary>
public class GameLoop : MonoBehaviour {
    private SceneStateController stateController = null;

    private void Awake()
    {
        DontDestroyOnLoad(this.gameObject);
    }

    // Use this for initialization
    void Start () {
        stateController = new SceneStateController();
        stateController.SetState(new StartState("Start", stateController), false);
    }

    // Update is called once per frame
    void Update () {
        stateController.StateUpdate();//启动场景管理器
    }
}

SceneStateController用于控制各个状态(场景)的切换:

using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;
/// <summary>
/// 场景管理器
/// </summary>
public class SceneStateController {
    private ISceneState sceneState;//申明一个ISceneState对象
    private AsyncOperation ao;//用于接收异步加载场景的返回值, 判断异步加载场景是否加载完毕
    private bool isRunStart = false;//是否运行过StateStart( )方法,确保StateStart( )只调用一次

    /// <summary>
    /// //设置状态方法
    /// </summary>
    /// <param name="sceneState">场景状态接口对象</param>
    /// <param name="isLoadScene">是否需要加载场景(默认第一个场景不用加载)</param>
    public void SetState(ISceneState sceneState,bool isLoadScene = true) {
        if (sceneState != null)
        {
            sceneState.StateEnd();//该场景结束后做一下清理工作啥的
        }
        this.sceneState = sceneState;//将该状态(该场景)更新为新状态(新场景)
        if (isLoadScene == true)
        {
            ao = SceneManager.LoadSceneAsync(sceneState.SceneName);
            isRunStart = false;
        }
        else
        {
            this.sceneState.StateStart();
            isRunStart = true;
        }
    }

    /// <summary>
    ///更新状态(场景)方法
    /// </summary>
    public void StateUpdate() {
        if (ao != null && ao.isDone == false) return;//如果处于正在加载的过程中,就不用更新状态了

        if (ao != null && ao.isDone == true && isRunStart == false)//如果异步加载完成,并且没有运行过StateStart()方法就执行加载资源啥的方法
        {
            sceneState.StateStart();
            isRunStart = true;
        }
        if (sceneState != null)
        {
            sceneState.StateUpDate();
        }
    }
}

接下来就是具体状态模式的代码来实现器切换功能了具体实现步骤在上面总图中有说明,总共分为7步。

2.1 创建场景状态的基础接口:

using UnityEngine;
using System.Collections;
/// <summary>
/// 场景状态接口(严格意义上来说不是一个接口,此时是一个父类,因为在其中会有子类需要继承的方法)
/// </summary>
public class ISceneState {
    private string sceneName;//需要加载的场景名
    protected SceneStateController sceneController;//表示该状态的拥有者,也就是场景管理器

    /// <summary>
    /// 获取场景名
    /// </summary>
    public string SceneName
    {
        get
        {
            return sceneName;
        }
    }

    /// <summary>
    /// 构造方法
    /// </summary>
    /// <param name="sceneName">场景名</param>
    /// <param name="sceneController">场景管理器</param>
    public ISceneState(string sceneName,SceneStateController sceneController) {
        this.sceneName = sceneName;
        this.sceneController = sceneController;
    }

    /// <summary>
    /// 场景加载时的方法(在游戏中可能需要加载资源啥的),每次进入到这个状态时调用
    /// </summary>
    public virtual void StateStart() { }

    /// <summary>
    /// 场景结束时的方法(在游戏中可能需要释放资源啥的),每次退出到这个状态时调用
    /// </summary>
    public virtual void StateEnd() { }

    /// <summary>
    /// 更新场景
    /// </summary>
    public virtual void StateUpDate() { }
}

2.2 创建场景状态的三个子类(开始界面,菜单界面,战斗界面):

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

/// <summary>
/// 开始状态(开始界面)
/// </summary>
public class StartState : ISceneState
{
    /// <summary>
    /// 开始界面(状态)构造方法
    /// </summary>
    /// <param name="sceneName">场景名</param>
    /// <param name="sceneController">场景控制器</param>
    public StartState(string sceneName, SceneStateController sceneController) : base(sceneName, sceneController) { }

    private Image logo;//开始界面Logo
    private float smoothingSpeed = 2f;//透明度过度平滑速率
    private float waitTime = 3f;//加载下一个界面的等待时间

    public override void StateStart()
    {
        logo = GameObject.Find("Logo").GetComponent<Image>();
        logo.color = new Color(1,1,1,0);
    }

    public override void StateUpDate()
    {
        logo.color = Color.Lerp(logo.color,new Color(1,1,1,1),Time.deltaTime * smoothingSpeed);//设置开始界面UI透明度渐变
        waitTime -= Time.deltaTime;
        if (waitTime <= 0)
        {
            sceneController.SetState(new MainMenuState("MainMenu", sceneController));//到达等待时间进入下一个场景
        }
    }
}


using UnityEngine;
using System.Collections;
using UnityEngine.UI;

/// <summary>
///第二状态(主场景) 
/// </summary>
public class MainMenuState : ISceneState
{
    /// <summary>
    /// 开始界面(状态)构造方法
    /// </summary>
    /// <param name="sceneName">场景名</param>
    /// <param name="sceneController">场景控制器</param>
    public MainMenuState(string sceneName, SceneStateController sceneController) : base(sceneName, sceneController){}

    private Button startButton;//开始游戏按钮

    public override void StateStart()
    {
        startButton = GameObject.Find("StartButton").GetComponent<Button>();
        startButton.onClick.AddListener(OnStartBtnDown);//添加开始游戏按钮事件监听
    }

    private void OnStartBtnDown() {//开始游戏按钮事件
        sceneController.SetState(new FightState("FightScene", sceneController));//设置为战斗状态(战斗场景)
    }
}

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

/// <summary>
/// 第三状态(战斗场景)
/// </summary>
public class FightState : ISceneState
{
    /// <summary>
    /// 开始界面(状态)构造方法
    /// </summary>
    /// <param name="sceneName">场景名</param>
    /// <param name="sceneController">场景控制器</param>
    public FightState(string sceneName, SceneStateController sceneController) : base(sceneName, sceneController){}

    private Button returnMenuBtn;//返回按钮

    public override void StateStart()
    {
        returnMenuBtn = GameObject.Find("ReturnMenuButton").GetComponent<Button>();
        returnMenuBtn.onClick.AddListener(OnReturnMenuBtnDown); //添加返回主菜单按钮事件监听
    }

    private void OnReturnMenuBtnDown() {//返回按钮点击事件
        sceneController.SetState(new MainMenuState("MainMenu", sceneController));//设置场景为主菜单场景
    }
}

2.3 场景状态模式的UML图(上面已经给出,不在赘述)
2.4 开发状态切换(场景切换)功能:
代码在SceneStateController场景管理器类中实现的。
2.5 控制开始场景的动画播放:
既然是开始场景的动画播放,那就是处于开始场景状态中该做的事情,所以具体代码实现在StartState类中(也不过就是一个Color.Lerp()方法而已)。
2.6 由开始状态切换到主菜单状态:
设计是当开始动画播放完后就会进入主菜单场景,所以具体代码也在StartState中体现。
2.7 设计主菜单界面和战斗场景的切换:
主要是在主菜单界面点击开始游戏按钮然后就进入战斗场景。所以在主菜单界面绑定开始游戏按钮的点击事件,点击进入战斗场景即可。

注:整个Demo项目我会放在Github上,可自行下载学习:https://github.com/MrZhiFu/Design-Patterns-In-the-game;建议下载压缩文件形式

©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页