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
最后再次展示一下工程结构图: