关于游戏架构设计(二)


UI架构

先看一下模块架构设计思路图:
在这里插入图片描述
UI 的设计框架有很多的,这次编写的 UI 框架不能说是最好的,它最大的优点就是实现了资源和代码的彻底分离,真正实现了程序和美术人员的分工合作。 UI 框架也是采用了 MVC 设计模式,但是在外面加了一层 State 状态变换,因为每个 UI 切换是在不同的状态之间的变换,这样整个 UI 架构设计就完成了。

游戏的每个 UI 都是使用 Panel 面板制作完成的,每个面板就是一个窗体,用 MVC 中的 View 表示,而用代码表示就是 Window 窗体类,MVC 中的 Control 也同样对应窗体的 Control 类。MVC 中的 Model 表示的是数据的传输,在当前可以直接使用配置文件进行读取加载,或者通过网络进行传输。


基类BaseWindow

先来看 Window 窗体类的设计,每个 UI 对应自己的 Window 窗体类。下面先进行 Window 类模块的代码编写,在写某个模块时,首先做的事情是搞清楚这个模块包括哪些内容,再考虑一下编写此类时扩展是否方便。这些都是编写时需要注意的问题,有的程序员拿过策划需求就开始编写代码,这样导致的后果是一旦策划需求改变,代码中就要重新加功能,搞的很麻烦,这对于程序员来说是大忌。

以 Window 类编写为例,游戏中显示窗体首先要创建窗体,还有窗体可以隐藏、销毁。另外,创建窗体首先要知道窗体的资源名字,还有这个窗体是在哪个场景中创建的,是登录场景还是游戏战斗场景等。因为窗体类不继承 Mono,为了方便使用窗体中的控件,所以还要做初始化窗体控件的功能以及做监听处理。

这些功能对于游戏中的任何 UI 窗体都是适用的,换句话说,所有的 UI 这些功能都是必备的,也就是 UI 的共性。这让人自然而然想到创建一个父类,如果不建父类,每个 Window 类都要写一套逻辑,这会使得代码很乱,而且如果是多个人合作的话,每人都来一套逻辑,后期代码无法维护。所以必须要建一个父类,代码如下:

 public abstract class BaseWindow
{
   
    protected Transform mRoot; //UI根结点

    protected EScenesType mScenesType; //场景类型
    protected string mResName;         //资源名
    protected bool mResident;          //是否常驻 
    protected bool mVisible = false;   //是否可见


    //类对象初始化
    public abstract void Init();

    //类对象释放
    public abstract void Realse();

    //窗口控制初始化
    protected abstract void InitWidget();

    //窗口控件释放
    protected abstract void RealseWidget();

    //游戏事件注册
    protected abstract void OnAddListener();

    //游戏事件注消
    protected abstract void OnRemoveListener();

    //显示初始化
    public abstract void OnEnable();

    //隐藏处理
    public abstract void OnDisable();

    //每帧更新
    public virtual void Update(float deltaTime) {
    }

    //取得所以场景类型
    public EScenesType GetScenseType()
    {
   
        return mScenesType;
    }

    //是否已打开
    public bool IsVisible() {
    return mVisible;  }

    //是否常驻
    public bool IsResident() {
    return mResident; }

    //显示
    public void Show()
    {
   
        if (mRoot == null)
        {
   
            if (Create())
            {
   
                InitWidget();
            }
        }

        if (mRoot && mRoot.gameObject.activeSelf == false)
        {
   
            mRoot.gameObject.SetActive(true);

            mVisible = true;

             OnEnable();

            OnAddListener();
        }
    }

    //隐藏
    public void Hide()
    {
   
        if (mRoot && mRoot.gameObject.activeSelf == true)
        {
   
            OnRemoveListener();
            OnDisable();

            if (mResident)
            {
   
                mRoot.gameObject.SetActive(false);
            }
            else
            {
   
                RealseWidget();
                Destroy();
            }
        }

        mVisible = false;
    }

    //预加载
    public void PreLoad()
    {
   
        if (mRoot == null)
        {
   
            if (Create())
            {
   
                InitWidget();
            }
        }
    }

    //延时删除
    public void DelayDestory()
    {
   
        if (mRoot)
        {
   
            RealseWidget();
            Destroy();
        }
    }

    //创建窗体
    private bool Create()
    {
   
        if (mRoot)
        {
   
            Debug.LogError("Window Create Error Exist!");
            return false;
        }

        if (mResName == null || mResName == "")
        {
   
            Debug.LogError("Window Create Error ResName is empty!");
            return false;
        }

        if (GameMethod.GetUiCamera.transform== null)
        {
   
            return false;
        }

        GameObject obj = LoadUiResource.LoadRes(GameMethod.GetUiCamera.transform, mResName);

        if (obj == null)
        {
   
            return false;
        }

        mRoot = obj.transform;

        mRoot.gameObject.SetActive(false);

        return true;
    }

    //销毁窗体
    protected void Destroy()
    {
   
        if (mRoot)
        {
   
            LoadUiResource.DestroyLoad(mRoot.gameObject);
            mRoot = null;
        }
    }

    //取得根节点
    public Transform GetRoot()
    {
   
        return mRoot;
    }

}

该父类设计成了一个抽象类并提供了一些接口便于子类的实现,这些接口所要实现的具体内容是不同的,父类无法具体一一实现,但是显示、隐藏、破坏这些都是通用的函数,可以在父类中实现。再看一下子类的实现方式,以 LoginWindow 类为例,制作一个简单的 UI 界面,如下图所示:
在这里插入图片描述
在这里插入图片描述
我们就以这两个按钮的界面为例来编写代码:

public class LoginWindow : BaseWindow
{
   
    //开始
    Transform mBtnStart;

    enum LOGINUI
    {
   
        None = -1,
        Login,
        SelectServer,
    }

    public LoginWindow() 
    {
   
        //场景类型
        mScenesType = EScenesType.EST_Login;
        //场景资源
        mResName = GameConstDefine.LoadGameLoginUI;
        //是否常驻内存
        mResident = false;
    }

    继承接口/
    //类对象初始化监听显示和隐藏,为了解耦合
    public override void Init()
    {
   
        EventCenter.AddListener(EGameEvent.eGameEvent_LoginEnter, Show);
        EventCenter.AddListener(EGameEvent.eGameEvent_LoginExit, Hide);
    }

    //类对象释放
    public override void Realse()
    {
   
        EventCenter.RemoveListener(EGameEvent.eGameEvent_LoginEnter, Show);
        EventCenter.RemoveListener(EGameEvent.eGameEvent_LoginExit, Hide);
    }

    //窗口控件初始化以及控件监听
    protected override void InitWidget()
    {
   
        mBtnStart = mRoot.Find("BtnStart");
        mBtnStart.GetComponent<Button>().onClick.AddListener(OnClickAddButton);

        DestroyOtherUI();
    }

    //消息回调函数
    private void OnClickAddButton()
    {
   
        //在这里监听按钮的点击事件
        LoginCtrl.Instance.StartGame();
    }

    //删除Login外其他控件,例如
    public static void DestroyOtherUI()
    {
   
        Canvas canvas = GameMethod.GetCanvas;
        for (int i = 0; i < canvas.transform.childCount; i++)
        {
   
            if (canvas.transform.GetChild(i) != null && canvas.transform.GetChild(i).gameObject != null)
            {
   

                GameObject obj = canvas.transform.GetChild(i).gameObject;
                if (obj.name != "Login(Clone)")
                {
   
                    GameObject.DestroyImmediate(obj);
                }                    
            }
        }
    }

    //窗口控件释放
    protected override void RealseWidget()
    {
   
    }

    //游戏事件注册
    protected override void OnAddListener()
    {
   

    }

    //游戏事件注消
    protected override void OnRemoveListener()
    {
   

    }

    //显示
    public override void OnEnable()
    {
   

    }

    //隐藏
    public override void OnDisable()
    {
   
    }
}

构造函数对资源文件和 UI 所在的场景类型初始化,以及该 UI 是否常住内存。后面函数是继承自父类,并对它们的具体实现,在函数中有 LoginCtrl 类接口调用,这个跟 LoginWidow 窗体息息相关,它是 MVC 中的 Control。下面再编写 LoginCtrl 类。


Control控制类

控制类的主要作用是播放消息,然后在 Loginwindow 中触发已设置监听的函数,如 Show、显示窗体,控制等:

public class LoginCtrl : Singleton<LoginCtrl>
{
   
        public void Enter()
        {
   
            EventCenter.Broadcast(EGameEvent.eGameEvent_LoginEnter);   
        }

        public void Exit()
        {
   
           EventCenter.Broadcast(EGameEvent.eGameEvent_LoginExit);
        }

        //登陆
        public void Login(string account, string pass)
        {
   
        }

        //登陆错误反馈
        public void LoginError(int code)
        {
   
        }

        //登陆失败
        public void LoginFail()
        {
   
        }
        
        //开始游戏
        public void StartGame()
        {
   
            SceneManager.LoadScene("Play");
            WindowManager.Instance.ChangeScenseToPlay(EScenesType.EST_Login);
            GameStateManager.Instance.ChangeGameStateTo(GameStateType.GST_Play);
        }

   }

将其设置成单例模式,在 Enter 函数中进行消息广播,另外在函数 StartGame 中使用了 WindowManager 类接口和 GameStateManager 类接口。这两个类是很关键的,后面会贴出。当然如果只有 Loginwindow 和 LoginCtrl 还是无法执行的,我们还缺少 State 状态类,状态类是负责窗体 UI 之间的切换,每个 UI 窗体对应着自己的状态,为了区分不同的 UI 窗体,利用这些状态枚举表示:

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

State状态接口

通常来说,我们会定义设置某个状态、获取状态、进入状态、停止状态、更新状态这些方法等。我们在设计每个模块时,都会先明确这个模块具体要做哪些事情,其实在设计一个类的内容时,可以先多想想,这样类的函数定义自然就有了。另外,提到的这些方法,每个 UI 状态都会包含,这样我们就可以将其抽离出来,定义

  • 8
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值