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


最後再次展示一下工程結構圖:



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