Unity实现自己的简易游戏窗口管理器

Unity实现自己的简易游戏窗口管理器

概述:

在一个手游项目中,进入游戏之后,我们会打开很多游戏窗口(界面),比如帮助窗口(界面){新手指引界面,玩法介绍界面,技能介绍界面},模块窗口(界面){任务界面,铸造界面,宠物界面,签到界面等等},这些界面我们都可以使用窗口管理器来实现,那么今天我将从一个很简单的小例子入手,和大家一起来完成一个简易的窗口管理器,最后我会把复杂的窗口管理器的实现思路告诉大家!


功能需求:

可以打开窗口,可以关闭打开的这个窗口,最后打开的窗口永远显示在前面,而且能够实现后面窗口的事件屏蔽。


实现过程:

1.游戏开始后,层次面板将出现一个我们自定义的UIRoot(身上需要挂载UIRoot脚本,Panel必须添加),这个游戏物体下面需要有个Camera,Camer身上需要的组件是Camera和UICamera,这个游戏物体下面还需要一个WindowRoot(窗口根节点)(本来应该还有DiagLogRoot的但是这个小例子为了测试,就不必写了),其中WindowRoot这个游戏物体是个空物体,他身上无需任何脚本,它的主要功能就是用来承放 我们打开的窗口的。

2.既然叫做窗口管理器,当然是希望有需要打开窗口的地方调用这个窗口管理器啊,所以,这个窗口管理器需要做成单例:(贴一个单例模式的写法)

/// <summary>
/// singleton test class.
/// </summary>
public class SingletonTest
{

    private static SingletonTest mInstance;

    public static SingletonTest GetSingletonTestInstance()
    {
        //if minstance == null.
        if (mInstance == null)
        {
            mInstance=new SingletonTest();
        }

        return mInstance;
    }

}


根据游戏过程中的第一条,我们可以分析写出如下代码:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class WindowManager
{

    //UIRoot根节点
    private GameObject CustomRoot;
    //摄像机
    private GameObject Camera;
    //打开的窗口根节点
    private GameObject WindowRoot;
    //存储打开过的窗口
    private Dictionary<string, GameObject> mLoadWindow;


    /// <summary>
    /// 单例模式
    /// </summary>
    private static WindowManager mInstance;

    public static WindowManager GetWindowManagerSingleton()
    {
        if (mInstance == null)
        {
            mInstance = new WindowManager();
        }

        return mInstance;
    }


   
    private WindowManager()
    {
        CustomRoot = new GameObject("UI Root");
        CustomRoot.AddComponent<UIRoot>();

        Camera = new GameObject("Camera");
        Camera.transform.SetParent(CustomRoot.transform);
        Camera.AddComponent<Camera>();
        Camera.AddComponent<UICamera>();

        WindowRoot = new GameObject("WindowRoot");
        WindowRoot.transform.SetParent(CustomRoot.transform);
    }
}

好了,上面我们就是完成了一个很简单的窗口管理器的变量声明部分,单例模式部分,接着我们在工程中来实践一下,看看调用窗口管理器之后会出现什么样的效果



好了,到了这一步我们就算是把窗口管理器的前期工作做好了,接下来,我们之前说了,窗口管理器要实现打开窗口和关闭窗口,那么这个windowmanager中就需要一个openwindow和closewindow的函数,那我们继续完善工程,完善工程之前,我们需要建一个存储待打开窗口的文件夹,我的命名规范如下:


好了,接下来我们就要分别打开这两个窗口,test1,test2,完善我们的窗口管理器代码:

(不完善版本)

    public GameObject OpenWindow(string varWindowName)
    {
        GameObject tempWindow;
        if(string.IsNullOrEmpty(varWindowName))return null;
        //集合中已经在加载过的情况下
        if (mLoadWindow.TryGetValue(varWindowName, out tempWindow))
        {
            SetWindow(tempWindow);
            return tempWindow;
        }
        //没有加载过这个窗口
        //1.根据名字加载
        GameObject loadWindow = Resources.Load<GameObject>(varWindowName);
        //2.实例预制体中同名子级
        GameObject childUI = loadWindow.transform.Find(loadWindow.name).gameObject;
        tempWindow = GameObject.Instantiate(childUI);
        //3.添加进集合(防止以后重复实例)
        mLoadWindow.Add(varWindowName,tempWindow);
        //4.设置实例出来的物体
        SetWindow(tempWindow);
        //5.返回值
        return tempWindow;
    }

    /// <summary>
    /// set window 
    /// </summary>
    /// <param name="varWindowGameObject"></param>
    public void SetWindow(GameObject varWindowGameObject)
    {
        varWindowGameObject.transform.SetParent(WindowRoot.transform);
        varWindowGameObject.transform.localScale=Vector3.one;
    }

如果之前构造函数里面没有给我们实例出来的UI Root添加Panel的话,它的效果会是这个样子(记得自己去掉克隆后缀):


所以我们需要回到构造函数那里,添加上这条代码:

        //添加panel组件,不添加会有不好的效果
        CustomRoot.AddComponent<UIPanel>();
添加完毕之后,我们再来运行,看下效果,我们会发现,虽然问题解决了,但是并没有窗口显示在游戏视图,但是场景视图是有的,这个时候,你需要做的事情就是:

1.检查窗口的大小

2.检查摄像机的相关参数




这个时候,我们该如何解决这个问题呢?我们之前不是自己实例出来的一个摄像机吗?我们可以给那个摄像机修改一下参数,参数代码:

        Camera = new GameObject("Camera");
        Camera.transform.SetParent(CustomRoot.transform);
        Camera camera=Camera.AddComponent<Camera>();
        Camera.AddComponent<UICamera>();

        camera.orthographic = true;//正交视野
        camera.orthographicSize = 1f;
        camera.nearClipPlane = -0.03f;
        camera.farClipPlane = 200f;

图解摄像机的修改参数:


经过修改,我们最终发现窗口可以正常被打开了,效果图:


但是最后我们发现console控制台报出了警告,正规项目,所有黄色警告都是要尽量解除的,报了警告就是说明你的代码有问题,如图:


就是说panel父级物体和子级物体的层不一样,虽然不一样,但NGUI自动帮我们转了相同层,就把这个警告显示出来了。这个问题说白了就是父级和子级层的问题,层的问题相当好解决,之前有篇文章是通过位运算来修改layer层的,那么我们在这个小例子里面不需要使用位运算。

        //varWindowGameObject.layer = LayerMask.NameToLayer("Default");

还有一种方式就是我们项目中常用的方式,在我们创建窗口预制时候,就直接修改UIRoot(父级),父级一修改,所有子级都会改变!

通过上面的两种方式之中的任选其一,我们会发现日志输出上面没有警告了!开心吧?好了,至此我们就实现了使用WindowManager打开一个预制窗口,但是打开一个是明显不行的,在游戏项目中,我们通常是打开通过button打开很多个窗口。


二 多窗口打开的窗口管理器

要做这个功能,我们就需要对上面的预制体进行修改了,怎么修改呢?我们之前不是说了,我们在实例窗口的时候,其实是实例的同名子级,所以我们需要给那个子级添加父亲,在root下面新建一个gameobject,和子级同名,然后把子级拖到这个新建的gameobject下面去,同时:记住给这个gameobject添加panel脚本,同时修改depth深度为1,如下图:



关于为什么要给新建的父级添加panel,后面会详细说到。我们说了,打开窗口的时候,要把打开的这个窗口显示在最前面,那么通过修改什么?当然是panel中的depth了,关闭这个窗口的时候,我们要把这个窗口的depth变成原来的1!所以这个功能需要在打开窗口和关闭窗口中去实现!修改代码:

(具体:可以定义一个int类型的变量,来存存windowroot下面所打开的窗口之后的最大深度值,为什么要这么做?


①.首先,我打开一个窗口的时候,本身panel值为1,那么我们把这个1赋值给全局深度值,这个时候全局深度值就是1,

②.当我们打开第二个窗口的时候,(默认深度值也是1)第二个窗口的深度必须为2,才能显示在第一个的上面,一次类推,

③.可能有人会问了,我打开一个窗口之后再打开第二个窗口的时候,我把第一个关闭不就行了,那么第二个不用去改深度值照样可以显示在最上面啊,当然这样也是可以的,

④.但我们这个例子要实现的就是:打开一个窗口,通过该窗口的关闭按钮关闭窗口,同时关闭窗口之时,需要将panel深度值变为初始值,

⑤.还有一种情况需要我们处理,我们打开一个窗口,但是这个窗口下面其实还有n个隐藏窗口,当我们操作打开窗口上面的某个button时候,可以将这些隐藏的窗口显示在最上层


这种情况也是需要我们去思考的,所以我们写出的代码一定要有把握全局应对所有情况的功能

    /// <summary>
    /// set window 
    /// </summary>
    /// <param name="varWindowGameObject"></param>
    public void SetWindow(GameObject varWindowGameObject)
    {
        //varWindowGameObject.layer = LayerMask.NameToLayer("Default");
        varWindowGameObject.transform.SetParent(WindowRoot.transform);
        varWindowGameObject.transform.localScale=Vector3.one;

        //获取当前打开的窗口下面有多少panel
        UIPanel[] panels = varWindowGameObject.GetComponentsInChildren<UIPanel>();
        int currentWindowDepth = mWindowDepth;
        foreach (var itemPanel in panels)//假如当前打开的窗口下面有2个panel
        {
            itemPanel.depth += currentWindowDepth;
            //更新当前panel数组中要加的panel值
            if (itemPanel.depth > currentWindowDepth)
            {
                currentWindowDepth = itemPanel.depth;
            }
        }

        //所有panel都加完了,更新全局深度
        mWindowDepth = currentWindowDepth;

    }

这样修好之后,我们可以保证每个后打开的窗口都在最前面显示了,那么如何实现点击关闭窗口之后,修改回原来的depth值?

    /// <summary>
    /// 关闭某个窗口,active=false,修改depth
    /// </summary>
    /// <param name="varName"></param>
    public void CloseWindow(string varName)
    {
        GameObject tempWindow;
        if (string.IsNullOrEmpty(varName)) return;
        if (mLoadWindow.TryGetValue(varName, out tempWindow))
        {
            //1.设置active
            tempWindow.SetActive(false);
            //2.修改depth值时,看看这个窗口下面有多少panel
            int tempDepth = mWindowDepth;
            UIPanel[] panels = tempWindow.GetComponentsInChildren<UIPanel>();//当然这里可以在上面封装,全局公用depth数量
            for (int i = panels.Length-1; i >=0; i--)
            {
                panels[i].depth -= tempDepth -1;
                if (panels[i].depth < tempDepth)
                {
                    tempDepth = panels[i].depth;
                }
            }
            mWindowDepth = tempDepth;
            Debug.LogError(mWindowDepth);
        }
    }

经过调用,我们很好的实现了我们想要的多窗口打开的简易窗口管理器,当然这我这也说了只是一个简单版本,真正项目中用到的比这个稍微复杂,但是也不是没有规律可循,只不过多了一下封装而已,本案例我会提供工程下载的地址:

链接:http://pan.baidu.com/s/1kVPTFfx 密码:wau4


最后再次展示一下工程结构图:



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章