第05课:UI 架构案例实现

接着上一篇的内容,我们已经实现了 UI 的 View、Control、State,已经把一个 UI 窗体 Login 的基础代码写完了。在此总结一下,一个 UI 窗体主要是包含三个代码模块:Window、Ctl、State。Window 和 Ctrl 之间是通过 Event 和接口连接起来的,而 State 和 Ctrl 是通过调用接口连接起来的。具体模块之间的关系框架如下所示:

unity 网络游戏架构设计(第05课:UI 架构案例实现)之美_其他

但是我们如何使用呢?下面开始介绍如何将这三者串联起来,思考一下,UI 窗体是非常多的,这么多窗体该如何管理?很容易让人想到管理类,我们需要一个窗体管理类进行统一调度,其实这种情况在开发中很常见,让人会想到工厂模式,WindowMananger 管理类具体是如何管理的?要管理这么多窗体,首先要做的事情是将其注册也就是将其存储到字典中,对应代码如下:

public enum EScenesType
{
    EST_None,
    EST_Login,
    EST_Play,
}

public enum EWindowType
{
    EWT_LoginWindow, //登录
    EWT_RoleWindow, //用户
    EWT_PlayWindow,//战斗
}
 public WindowManager()
    {
        mWidowDic = new Dictionary<EWindowType, BaseWindow>();

        mWidowDic[EWindowType.EWT_LoginWindow] = new LoginWindow();

        mWidowDic[EWindowType.EWT_RoleWindow] = new RoleWindow();
        mWidowDic[EWindowType.EWT_PlayWindow] = new PlayWindow();
    }

在上述代码中,我们定义了窗体对应自己的类型,便于区分不同的窗体,构造函数实现了窗体的注册,注册好了窗体后,下面开始窗体方法的实现,比如游戏中不同的窗体之间进行 UI 切换,从一个 UI 切换到另一个 UI。再比如切换窗体到游戏场景、切换窗体返回到登录场景等等,这些方法对应的实现函数如下所示:

//切换到游戏场景
  public void ChangeScenseToPlay(EScenesType front)
    {
        foreach (BaseWindow pWindow in mWidowDic.Values)
        {
            if (pWindow.GetScenseType() == EScenesType.EST_Play)
            {
                pWindow.Init();

                if(pWindow.IsResident())
                {
                    pWindow.PreLoad();
                }
            }
            else if ((pWindow.GetScenseType() == EScenesType.EST_Login) && (front == EScenesType.EST_Login))
            {
                pWindow.Hide();
                pWindow.Realse();

                if (pWindow.IsResident())
                {
                    pWindow.DelayDestory();
                }
            }
        }
    }
    //切换到登录场景
    public void ChangeScenseToLogin(EScenesType front)
    {
        foreach (BaseWindow pWindow in mWidowDic.Values)
        {
            if (front == EScenesType.EST_None && pWindow.GetScenseType() == EScenesType.EST_None)
            {
                pWindow.Init();

                if (pWindow.IsResident())
                {
                    pWindow.PreLoad();
                }
            }

            if (pWindow.GetScenseType() == EScenesType.EST_Login)
            {
                pWindow.Init();

                if (pWindow.IsResident())
                {
                    pWindow.PreLoad();
                }
            }
            else if ((pWindow.GetScenseType() == EScenesType.EST_Play) && (front == EScenesType.EST_Play))
            {
                pWindow.Hide();
                pWindow.Realse();

                if (pWindow.IsResident())
                {
                    pWindow.DelayDestory();
                }
            }
        }
    }

上述两个方法,其实就是遍历窗体,看看它是对窗体隐藏还是显示?还是对其进行预加载操作。这两个方法会经常使用,另外,还需要提供隐藏所有窗体接口和更新窗体。代码如下所示:

public void HideAllWindow(EScenesType front)
    {
        foreach (var item in mWidowDic)
        {
            if (front == item.Value.GetScenseType())
            {
                Debug.Log(item.Key);
                item.Value.Hide();
                //item.Value.Realse();
            }
        }
    }

 public void Update(float deltaTime)
    {
        foreach (BaseWindow pWindow in mWidowDic.Values)
        {
            if (pWindow.IsVisible())
            {
                pWindow.Update(deltaTime);
            }
        }
    }

因为每个 UI 窗体对应三者:Window、Ctrl、State,这样每个窗体对应的 State 跟 Window 是同样多的,而且到现在还没看到状态之间的切换,State 与 Window 是同样多的,而且这么多状态也需要一个管理类进行管理,而且我们还需要执行状态切换。将其都放到 StateManager 管理类中,这样既管理了 State,也做了状态切换处理。跟窗体设计类似,我们也需要设置状态转换类型,这个使用枚举表示:

    public enum GameStateType
{
    GST_Continue,
    GST_Login,
    GST_Role,
    GST_Loading,
    GST_Play,
}

接下来为了管理状态类,我们也需要将其存储在字典中,代码片段如下:

 public GameStateManager()
    {
        gameStates = new Dictionary<GameStateType, IGameState>();

        IGameState gameState;

        gameState = new LoginState();
        gameStates.Add(gameState.GetStateType(), gameState);
        gameState = new RoleState();
        gameStates.Add(gameState.GetStateType(), gameState);
        gameState = new PlayState();
        gameStates.Add(gameState.GetStateType(), gameState);
    }

管理类主要是对于这些同类型的类模块的操作,比如若要获取到当前状态以及切换状态,游戏刚开始的默认状态,当然,还有游戏状态的更新函数 Update 或者 FixedUpdate,其实关于管理类的方法,读者在做的时候动脑子想想就有了,为了逻辑的编写,肯定需要一个统一的接口调用,否则,每个程序自己搞一套,维护起来是相当的麻烦。代码需要不停的写、不停的思考,才会有所收获,熟能生巧,状态管理类 GameStateManager 的代码如下所示:

    // 获取当前状态
 public IGameState GetCurState()
    {
        return currentState;
    }
    //改变状态
    public void ChangeGameStateTo(GameStateType stateType)
    {
        if (currentState != null && currentState.GetStateType() != GameStateType.GST_Loading && currentState.GetStateType() == stateType) return;

        if (gameStates.ContainsKey(stateType))
        {
            if (currentState != null)
            {
                currentState.Exit();
            }

            currentState = gameStates[stateType];
            currentState.Enter();
        }
    }
    //进入默认状态
    public void EnterDefaultState()
    {
        ChangeGameStateTo(GameStateType.GST_Login);
    }

    public void FixedUpdate(float fixedDeltaTime)
    {
        if (currentState != null)
        {
            currentState.FixedUpdate(fixedDeltaTime);
        }
    }
    //更新
    public void Update(float fDeltaTime)
    {
        GameStateType nextStateType = GameStateType.GST_Continue;
        if (currentState != null)
        {
            nextStateType = currentState.Update(fDeltaTime);
        }

        if (nextStateType > GameStateType.GST_Continue)
        {
            ChangeGameStateTo(nextStateType);
        }
    }
    //获取状态
    public IGameState getState(GameStateType type)
    {
        if (!gameStates.ContainsKey(type))
        {
            return null;
        }
        return gameStates[type];
    }
}

上述代码,在核心函数都加了注释,读者一看就明白。本教程的最后,会把整个工程给读者,读者再结合着工程学习,印象会更加深刻。写到这里,我们的 UI 架构设计基本完整了。接下来就是开始使用了,首先建立一个类,这个类是逻辑类,需要挂接到对象上,继承 Mono,在其 Update 函数中写下下列接口调用:

         //更新游戏状态机
        GameStateManager.Instance.Update(Time.deltaTime);
        //UI更新
        WindowManager.Instance.Update(Time.deltaTime);

状态和窗体需要每帧更新,另外在类的 Start 函数中,开始进行状态切换以及进入默认状态,加入一下两行代码:

//首先是切换到登录UI
  WindowManager.Instance.ChangeScenseToLogin(EScenesType.EST_None);
  该函数主要是将登录UI显示出来。

//进入默认状态
GameStateManager.Instance.EnterDefaultState();

通过 EnterDefaultState 将 UI 带入到状态变换中,如果再需要切换 UI,只需要调用如下函数接口:

GameStateManager.Instance.ChangeGameStateTo(.....),

另外,WindowManager 类中的函数:

ChangeScenseToPlay 只需要切换场景时,在加载进度条调用一次即可。整个 UI 面板窗体切换都是围绕着 GameStateManger 类中的 ChangeGameStateTo 函数调用进行的。因为我们经常使用单例模式,在此把完整的代码给读者展示如下:

using UnityEngine;
using System.Collections;

namespace Game
{
    public abstract class Singleton<T> where T : new()
    {
        private static T _instance;
        static object _lock = new object();
        public static T Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (_lock)
                    {
                        if (_instance == null)
                            _instance = new T();
                    }
                }
                return _instance;
            }
        }
    }

    public class UnitySingleton<T> : MonoBehaviour
        where T : Component
    {
        private static T _instance;
        public static T Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = FindObjectOfType(typeof(T)) as T;
                    if (_instance == null)
                    {
                        GameObject obj = new GameObject();
                        //obj.hideFlags = HideFlags.DontSave;
                        obj.hideFlags = HideFlags.HideAndDontSave;
                        _instance = (T)obj.AddComponent(typeof(T));
                    }
                }
                return _instance;
            }
        }
        public virtual void Awake()
        {
            DontDestroyOnLoad(this.gameObject);
            if (_instance == null)
            {
                _instance = this as T;
            }
            else
            {
                Destroy(gameObject);
            }
        }
    }
}

游戏中的 UI 实现如下所示:

unity 网络游戏架构设计(第05课:UI 架构案例实现)之美_其他_02