用Unity里的NGUI做如何让登陆界面跳转怎么让它不

NGUI在Unity中因更改窗口区域而出现Bug的处理
时间: 18:56:25
&&&& 阅读:1173
&&&& 评论:
&&&& 收藏:0
标签:&&& 最近,在Unity5.0.1中遇到了NGUI中的一些BUG问题,比如,你用NGUI开发进行拖拽Sprite
的功能,或者在一些Lable上使用了dynamic font, 然后把它编译成可在PC上运行的exe可执行
文件 (记得 Player Settings的Resolution中勾上Resizealbe Window),& 当它运行时,这时你就
会发现问题来了,比如打开时是720*480大小的窗口区域,然后你通过拖拽窗口边界,或者直接
进行窗口最大化放大,这时你会发现UI Root中的Sprite仍然只能在720*480的大小区域中拖动,
而dynamic font也会因为放大而失真,如图:
(这里图片缩小了,dynamic font失真的效果可能看不大出来,原来的字体比这里的更清晰,
&& 另外,那张拖拽的Sprite角色图片已经到达边界了,处在720*480右下角位置)
&&& 我试了一下,这个问题在4.6.4中也存在,但这绝不是我个人的问题!!为什么这么说呢?因为这个问题即便是
在NGUI的example11示例中也存在!!后来我去Unity官方重新下了个4.5.5的版本,突然奇迹的发现,NGUI这个
问题消失了,所以我推测,自从UGUI出现后,Unity对NGUI的支持就不好了,所以除非你对NGUI了解到了能随意
修改源代码的地步,否则最好不要在4.6.x以上的版本中使用NGUI 。(其实我从使用NGUI到现在只有3天)
(PS: 我用的NGUI是最新的3.8.2版本,3.6.8也试过,情况没有改变)
&& 好了,现在知道BUG出现的原因了,但是我的项目使用的是Unity5.0.1, 而且我已经使用了NGUI才发现了这个BUG
的存在,那么是否有办法在Unity中友好的解决这个问题呢?答案是肯定的。
&& 但我本身对NGUI还不是很了解,而且我看过部分的源代码,发现要去彻底的熟悉并去修改它的底层代码还需要一些
时间,且有一定难度,所以我选择了第二条路,直接从高层修改,将变化响应到NGUI,那么具体该怎么做呢?很简单,
只要把我写的这个C#脚本挂到UI Root 上就行了,下面是脚本的内容:
using UnityE
using System.C
public class Screen_Adapt : MonoBehaviour {
float alphaDelta = 0.001f;
// Use this for initialization
void Start () {
panel = GetComponent&UIPanel&();
screen = new Vector2(Screen.width, Screen.height);
// Update is called once per frame
void Update () {
bool scrSizeChanged = screen.x != Screen.width || screen.y != Screen.
if (scrSizeChanged)
//设置分辨率来通知NGUI更新拖拽区域
Screen.SetResolution(Screen.width, Screen.height, false);
screen = new Vector2(Screen.width, Screen.height);
* 如果设置为同样的值,NGUI会考虑到效率,所以不会重新设置,
* 所以用delta来设置看不见的微小改变
alphaDelta = -alphaD
panel.alpha += alphaD
&我们只要在屏幕区域大小改变时,显示调用一次同样窗口区域大小的setResolution()函数就能将
更新的窗口拖拽区域同步到NGUI了,同理,我们更改一次微小变化的panel.alpah值,就能让NGUI
来帮助你更新出失真的dynamic font了,如此一来,便能很好的解决这个问题了,而且性能开销也
很小,所以遇到这个问题的朋友可以借鉴一下。
标签:原文:http://blog.csdn.net/beyond_ray/article/details/
教程昨日排行
&&国之画&&&& &&&&&&
&& &&&&&&&&&&&&&&
鲁ICP备号-4
打开技术之扣,分享程序人生!Unity3D NGUI教程_图文_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
Unity3D NGUI教程
&&Unity3D NGUI教程
阅读已结束,下载文档到电脑
想免费下载本文?
定制HR最喜欢的简历
下载文档到电脑,方便使用
还剩22页未读,继续阅读
定制HR最喜欢的简历
你可能喜欢ngui怎么写一个脚本设置使按钮 不可点击
按钮按一次执行一次后,按钮不可执行。不能用destory button message target,因为我写了一个伪线程在里面。
要评论请先&或者&
UIS private bool cd_bool = private float CDTime = 5.0f; //CD时间 private float CurrentT &//剩余CD时间 // Use this for initialization void Start () {
sprite = gameObject.GetComponentInChildren&UISprite&(); //获取按钮的UISprite
sprite.fillAmount = 0; } void OnClick() {
if (cd_bool == false)
/*Debug.Log(&执行某操作。。。&);*/
sprite.fillAmount = 1;
CurrentTime = CDT
} } // Update is called once per frame void Update () {
if (cd_bool)
CurrentTime -= Time.deltaT
if (CurrentTime & 0)
CurrentTime = 0;
sprite.fillAmount = CurrentTime / CDT
if (sprite.fillAmount == 0)
在CD 为 &true &的时候 按钮 点击 无效刚刚做的项目,由于界面管理做的不太好,所以在开发的过程中出现了很多奇怪或难缠的bug,搞得我们几个写UI逻辑的越写越觉得没意思,想方设法的到处打补丁,后来也就是在这样的情况下,一直在总结开发中关于界面上遇到的坑,写了一年多的UI逻辑,针对那些由于界面架构上导致的问题,自己琢磨了一个简易的UI框架,只是简单的跑了一下没什么问题。
好了正式开始吧。
关于界面的问题(我开发时遇到的)
这个可是太多,总结了几个重要的点
1. 界面的管理
2. 界面生命周期
3. 界面的显示和隐藏
4. 界面逻辑的管理
5. 逻辑代码和view分离
6. 界面之间传值问题
7. 界面穿插和界面层级管理
8. 引用关系
9. 脚本该不该挂在gameobject上
那么下面我就围绕以上几点写了。
界面的资源全部都是打在AssetBundle中,然后通过底层函数把prefab load起来,给它挂上一个脚本,这个脚本就包含着该界面的逻辑,有一个WindowManager来管理这些window,每个window之间有父子引用关系,在WindowManager中还维护了一个栈来管理,每次界面打开或关闭都与该界面的父或子有关系。
例如,当打开一个新界面时,会把父界面的gameobject传进去,把界面显示出来,把父界面隐藏,关闭界面的时候,把当前界面隐藏,父界面显示,这样会出现一个问题,当两个界面同时在最上面时,当它们无论关闭时都会下面的界面显示出来,有时候就会出现穿插。
正常情况下,在同一时刻应该只允许一个界面是可操作的。
又是维持父子关系,一方面又用栈来保存,这样让我真的不知道应该怎么获取父界面,因为有可能在界面中父的引用不是栈里面的“父”。
界面生命周期
界面的生命周期可说是个比较重要的问题,提醒一下!!!千万不要把两个不同的生命周期顺序写在一起,如果真要写一起,请一定一定注意它们之间的顺序。
自己的界面生命周期函数的调用时机一定要很清楚。
说说我们项目,挂在界面上的那个脚本里面就存在两套生命周期函数,一个是Mono的那一套,另一个是底层框架维护的一套。这东西当开始的时候没什么问题,越往后写越改就发现很多时候的bug,都是由于生命周期顺序造成的。例如:NGUI里面很多东西都是在Start做的,所以只要用NGUI,所有设置界面显示都最好是在Start之后去调用,不然可能会出现ScrollView的Item错位的情况。
我们界面几个状态,可见、可操作、不可见。转圈的进度条也被用界面来管理了,所以当时每次转圈完了之后,就会调用一次“可操作”的周期函数,有时候遇到断线重连,就会不停的转圈,当然也会不停的调用函数。
界面的显示和隐藏
有很多种方法
1. gameObject.SetActive(true or false)
2. 把界面移到UI摄像机外面
3. 改变界面的Layer到UI相机不照的层
4. 设置为透明
5. 用不透明的背景遮挡
6. 每个界面都放在不同位置上,这样移动UI相机到相应界面也实现显示隐藏了。
7. 也可采用多相机的方式
其中1、4两种方法对于NGUI并不好,因为那样操作会导致panel的所有“顶点重建”,重新生成drawcall。这也是NGUI消耗性能的地方,过段时间我会整理一下对NGUI的分析。
其中5,要看具体需求(自己脑补)
其中2、3、6、7都是可取的,但具体细节还得认真考虑,我用了改变Layer的方式。
界面逻辑的管理
我们直接在上挂了一个脚本,刚开始做unity的时候,把界面的逻辑全部写在这个脚本里面。一般简单界面还好,但遇到复杂界面就完蛋了,有时候这一个脚本就得上千行,可读写性很差,过一段时间修改原代码很费劲,而且很多逻辑状态放在一起非常容易出现bug,有一段时间bug特别多。
遇到了一个状态非常多的界面,脚本里面放了很多状态变量,有些变量是互斥的,有些可以共存的,然后就这样没有规划的写了,结果这个界面很乱,都不敢做太大改动,出了bug改好了又引发其他的bug。所以后来就用有限状态机来管理这些,把每个状态和状态对应的逻辑拆分,这样每个脚本行数变少了,逻辑得到很大的改善,后来改bug都不费脑子了,呵呵。(后来在知乎上看到一个人说用行为树。。。后面再尝试吧)
逻辑代码和view分离
当业务代码越复杂时,修改代码就成了费脑的事情。
当时间越来越久,理解代码就非常困难。
同一个逻辑不能复用,在很多地方复制粘贴,如果出现错误就会修改很多地方。
测试变得非常麻烦,没都要整体测一次才能确保一切完好。
使用MVC或MVP等架构模式,使代码达到低耦合、高复用、易测试、好维护、易扩展。
记得刚刚学习网站开发的时候,MVC是首先接触到的设计思想,应该滚瓜烂熟的东西。有一段时间我研究了一下MVC,发现和之前的认识不一样,比如View需要观察Model,MVC实际是UI框架的一种模式,可并不是整个系统。下面就看看那些模式:
是一种使用Model View Controller设计创建web应用程序的程序。它强制性的使应用程序的输入、处理和输出分开。使用MVC应用程序被分成三个核心部件:模型、视图、控制器。它们各自处理自己的任务。最典型的MVC就是jsp + servlet + javabean的模式。
Model - 表示应用的程序的核心,提供数据和数据相关的逻辑,通知View数据变化
View - 显示数据,观察Model变化,可以从Model取得数据进行显示
Controller - 处理输入,调用model处理业务逻辑,逻辑处理完之后,修改Model,并选择View显示结果
注意:这里所说的是经典MVC模式,后来发展了很多版本,它们之间无非就是这三者关系的变化,具体可以看看相关的文章和论文。
它是从MVC演变而来,其中Presenter处理业务逻辑,Model提供数据和数据的逻辑,View负责显示。
作为一种新模式,和MVC的重大区别就是在MVP中View不直接使用Model,它们之间通过Presenter来进行的,所有交互发生在Presenter内部,Presenter代替了Controller的角色,在处理业务逻辑的基础上还要负责帮View从Model中取数据。而在MVC中,View会直接从Model中读取数据。
对MVVM不了解,也没有使用过,看了一些网上的文章,最重要的概念应该就是:数据绑定。把Presenter换成了ViewModel,换汤不换药,最终发生改变就是三者之间的关系和三者所负责的事情。了解更多就去网上搜一搜。
以上对一些模式的简介,总结起来,虽然有这些模式的存在,但需求是万变的,没有哪个模式能适用于一切情况,所以一切都要以实际项目、实际需求为主,吸收那些模式的思想,应用于各个开发场景。一句话就是,怎样让开发简单、代码好看、易于维护就怎么做喽。
界面之间传值问题
不管是使用哪种开发模式。在实际开发中应该都会遇到一个问题,对于界面管理,界面之间的传值是一个重要的问题。
在Android中,两个Activity之间传值使用了一个叫Intent的组件,Activity持有Intent的引用。
在unity开发中,需要注意传值的时机,在界面逻辑脚本中用成员变量保存该值。
界面穿插和界面层级管理
影响渲染顺序的因素:
在NGUI中,panel之间的层级,weight之间的层级都是用depth属性控制的。虽然有以上几个方面都可以控制渲染顺序,但还是建议使用depth吧,毕竟这是NGUI提供的最正规的方式。
注意,panel和weight的depth是不交叉的,先是panel和panel深度排序,然后再是同一个panel下的weight进行深度排序。而且即使panel在hierarchy视图中有层次关系,也不会影响depth的排序。
当然关于层级关系还有一个重要的方面:3D模型和粒子特效的裁剪问题,有些游戏有这样的需求,比如在界面上显示一个英雄的模型,有些界面需要在模型上面,有些则在模型下面。我现在的做法是用多个相机,一个界面对应一个相机,模型相机也是分开,利用相机的depth达到效果。
取决于具体开发的框架了,建议使用MVC或MVP,各个层次的引用关系就是这些模式所描述的,能使代码结构清晰,减少bug的出现,利于后期维护。
脚本该不该挂在gameobject上
关于这个问题就看项目的框架了,有些框架是把界面的脚本直接挂在gameObject上,有些则是通过脚本内持有gameObject引用关联的。
经过上面的讨论,已经把遇到过关于界面比较重要一些地方了解了,然后自己写了一个简单的UI框架。
在Unity开发中,客户端UI框架的脚本有两种方式:
如果每个界面都有单独处理业务逻辑的脚本挂在自己身上,这种是通过Unity自身来驱动界面,把两个生命周期放在一个脚本中。
首先需要知道,写逻辑的脚本不能静态绑定的,因为网络游戏都需要资源热更新,所以我们要把几乎所有的美术资源打成AssetBundle的形式(这是Unity美术资源的一种存在形式),unity中资源结构的组织及管理通过.meta文件完成的,unity会为工程中每个文件和文件夹创建一个.meta文件,里面记录着一个GUID,每个电脑生成的GUID不一样,而且资源只要变化了就会重新生成GUID,在开发时要不停往这些脚本中写代码,脚本变化对应的GUID也会变化,这会导致已经打好的AssetBundle里通过记录的GUID找不到挂的脚本,也就是脚本丢失。
那么逻辑脚本也就只能动态的挂上去了:
TestScript test = gameObject.AddComponent&TestScript&()
test.SetParams(param)
test.Init()
这段代码是很多时候是这样的,但需要注意,此时的TestScript只执行了Awake,还没有执行Start就调用了初始化,如果界面是NGUI的,那么NGUI很多初始化工作都在Start中完成,也就是说UI本身都还没有初始化完成,就开始执行显示逻辑了,这是不对的。所以Init里面不能写让UI显示数据的代码,只能写在TestScript 的Start中,这样才能保证所有UI控件已经初始化完成了。
如果整个框架是有某个脚本来驱动的,也就是界面的逻辑不直接挂在gameObject上的,而是通过代码中存在的引用关联的,这样脚本中没有mono相关的生命周期,只有自己底层维护的周期了,所有脚本都完全自己把控。但还是得注意,自己的周期也一定要合理,NGUI中一定要保证UI全部初始化完成了才能执行显示逻辑。
UI框架部分
整体的类图
我直接在gameObject上挂脚本,但是挂的一个通用的脚本:Window,这个类继承自MonoBehaviour,用来驱动我的逻辑。
using System.Collections.G
using UnityE
public class Window : MonoBehaviour
private IPresenter _presenter = null;
private bool _isStart = false;
void Start()
_isStart = true;
gameObject.layer = UnityLayer.ShowUIL
_presenter.OnStart();
this.Show();
void OnDestroy()
_presenter.OnDestroy();
public void AddPresenter(IPresenter presenter)
this._presenter =
public void Show()
if (_isStart)
_presenter.OnEnter();
public void Hide()
_presenter.OnLeave();
public void OnStop()
_presenter.OnStop();
public void ReStart(IIntent intent)
_presenter.SetIntent(intent);
_presenter.OnStart();
this.Show();
IPresenter是定义的处理界面逻辑的接口:
public interface IPresenter
void OnStart();
void OnEnter();
void OnLeave();
void OnStop();
void OnDestroy();
void BindView(GameObject go);
void SetIntent(IIntent intent);
IView是定义的界面接口:
public interface IView
void Init(GameObject view);
IIntent是参数传递的接口:
public interface IIntent { }
结构可以理解为一个界面对一个IPresenter,对应一个IView。IPresenter中负责业务逻辑、设置界面,IView中负责写界面设置函数和事件监听,这样把UI和逻辑分开了。
接着看看实现IPresenter的一个基础类:Presenter,它接受一个泛型,用来把IView和它联系起来,并实现了一些函数。
using System.Collections.G
using UnityE
public abstract class Presenter&T& : IPresenter where T : IView
protected FSM _fsm = null;
protected IIntent _intent = null;
protected T _view = default(T);
public void SetIntent(IIntent intent)
this._intent =
public abstract void OnEnter();
public abstract void OnLeave();
public virtual void OnStart() { }
public virtual void OnStop() { }
public virtual void OnDestroy() { }
public void BindView(GameObject view)
_view = Activator.CreateInstance&T&();
_view.Init(view);
当然IView也有基本实现:View
public abstract class View : IView
protected GameObject _view = null;
public virtual void Init(GameObject view)
this._view =
public void Show()
_view.layer = UnityLayer.ShowUIL
public void Hide()
_view.layer = UnityLayer.HideUIL
其中UnityLayer是定义的通过UnityEditor创建的Layer,之前也说过,我是通过改变layer来显示和隐藏界面的。
public class UnityLayer
public const int HideUILayer = 8;
public const int ShowUILayer = 5;
还有一个类负责管理界面:WindowManager,它维护了一个栈的结构(虽然我是用List装的),每次打开界面的时候 - 进栈,每次关闭界面的时候 - 出栈。
界面IPresenter的生命周期:
WindowManager 对外提供两个函数,一个打开一个关闭,并且还对无用的界面做了缓存,限制cache容器的大小,并用一个定时器定期去检查cache,超过限制就把前面的释放掉,满足先进先出的规则。
public class WindowManager
private List&Window& win = new List&Window&();
private List&Window& cache = new List&Window&();
private static WindowManager ins = null;
private WindowManager()
public static WindowManager GetInstance()
if (ins == null)
ins = new WindowManager();
public void OpenWin(string name, IIntent intent)
List&Window&.Enumerator etor = cache.GetEnumerator();
Window old = null;
while (etor.MoveNext())
if (etor.Current.gameObject.name.Equals(name))
old = etor.C
if (old != null)
cache.Remove(old);
win.Add(old);
old.ReStart(intent);
UnityEngine.Object obj = Resources.Load(name);
GameObject go = GameObject.Instantiate(obj) as GameO
Type type = PresenterCfg.pconfig[name];
IPresenter p = Activator.CreateInstance(type) as IP
Window w = go.AddComponent&Window&();
w.AddPresenter(p);
if (win.Count & 0)
win[win.Count - 1].Hide();
win.Add(w);
p.SetIntent(intent);
p.BindView(go);
public void CloseWin(GameObject go)
int i = 0;
for (i = 0; i & win.C ++i)
if (win[i].gameObject == go)
win[win.Count - 1].Hide();
if (i &= win.Count)
for (int j = win.Count - 1; j &= --j)
win[j].OnStop();
cache.Add(win[j]);
win.RemoveRange(i, win.Count);
if (win.Count & 0)
win[win.Count - 1].Show();
private void _Examine()
if(cache.Count & 0)
Window w = cache[0];
cache.Remove(w);
至此,一个简单的界面框架就完成了,那么在开发的时候只需要写一个Presenter和一个View:
public class ViewPresenter : Presenter&MainView&
public override void OnStart()
Debug.Log("view presenter start");
public override void OnEnter()
Debug.Log("view presenter enter");
_view.Show();
public override void OnLeave()
Debug.Log("view presenter leave");
_view.Hide();
public override void OnStop()
Debug.Log("view presenter stop");
public override void OnDestroy() { Debug.Log("view presenter destroy"); }
public class MainView : View
public override void Init(GameObject view)
base.Init(view);
在做项目的时候就一直琢磨,要自己写一个UI框架,不然对不起自己写了这么久界面。最近终于完成了第一版,里面还存在很多问题,比如多个界面的层次关系怎么管理、有两处代码使用了反射可以想办法改进,当然还有没有考虑到的问题,所以后续还要陆续修改。
花了一周时间整理了这些东西,整理自己的思路,这次一定印象深刻,可能写的不太好,有什么问题请直接指出,一起讨论,不断总结,不断学习,不断提升。
本文已收录于以下专栏:
相关文章推荐
/p/061e67308e5f
背景和目的
小哈接触Unity3D也有一段时间了,项目组在UI解决方案的选型一直是用的原生的uGUI,因此本人也是使用了一...
Unity实现自己的简易游戏窗口管理器
浅谈使用NGUI的界面架构(一)
浅谈使用NGUI的界面架构(二)关于NData
作者:kUANG tOBY
来源:知乎
...
NGUI做的界面比较漂亮,有时候想直接放到三维场景中,如果将直接做好的NGUI界面直接拷贝到场景中,会出现NGUI界面直接在场景中显示,界面会不清楚。想要得到二维界面那种清晰的效果,必须从场景中重新创...
【Button】
+Sprite/Label
+box collider
+Button Script (OnClick上添加点击触发方法-命名:OnXXXButtonClick)
今天上午的时候,对NGUI制作登录界面做了一个联系,下面给大家分享下
做一个简单的登录界面...................................
1、登录背景创建---Sp...
这一篇我个人认为还是很常用的,一开始也是实习的时候学到的,所以我觉得实习真的是一个快速学习工程技巧的途径。
提醒:这篇教程比较复杂,如果你不熟悉NGUI、iTween、C#的回调...
unity和NGUI的各种层级关系整理
目录1、介绍两大UI插件NGUI和UGUI
2、unity渲染顺序控制方式
3、NGUI的控制
4、UGUI的控制
5、模型深度的控制
6、粒子特效深度控制
7、NGUI与模型和粒子特效穿...
他的最新文章
讲师:董岩
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)请完成以下验证码
您当前的位置: &
Unity超友好界面框架:NGUI的功能介绍及NData
查看: 832|
|原作者: 文/kUANG tOBY(匡正)
  版权所有,转载须注明出处以及作者,Email:  在我的印象中,Unity一直没有一套成熟的界面体系。现在可供选择的不外乎NGUI和UGUI,之前也用过EZGUI和2D ToolKit,但最后我还是选择了NGUI。  很多人说NGUI不好用。我的感觉是,一个工具,只要你对它足够熟悉,就一定有一套最适合它的使用方法。当然每个工具都一定有它的无法回避的缺点和硬伤,但大部分人只是使用方式不对罢了,还没到受工具局限性影响的阶段。  选择NGUI作为主要的界面工具主要是基于以下考虑:有较多的文档,新人容易上手功能比较全,一个手游需要的界面功能基本都有有个MVVM工具NData可以和NGUI配合使用,这样可以极大地提高开发效率,适应需求修改。这个后面会详细介绍。  在设计界面架构的时候,我主要想实现以下几个目的:把游戏的各个界面模块集中管理,统一调度,但又必须把每个模块之间的耦合性降到最低。这样可以不同的人开发不同的模块,互不干扰,做出来的东西运行起来又不会互相冲突。把界面逻辑和界面版式尽量分离开来,让美术也可以参与界面的修改(事实证明,这个想法最后救了程序的命)。界面模块的开发必须有一套统一的流程,统一的格式,方便不同的人维护。  在具体设计之前,首先要了解NGUI的局限性。  NGUI有很多缺点,最受人诟病的就是性能问题和内存消耗问题。这两个问题都和NGUI的底层渲染机制有关。NGUI的渲染基于每一帧的Mesh重建,把一个UIPanel下的多个使用相同图集的UIWidget合并成一个Mesh,以此来减少draw call。NGUI本身对此做了优化,即如果一个UIPanel下的内容没有变化,就使用缓存的Mesh。但界面往往是不停变化的,这样就不可避免的每一帧都要重建Mesh,从而造成CPU的负担和多余的内存消耗。解决的方法就是把要经常变化和移动的界面放在单独的UIPanel下,去移动和变化UIPanel依附的物体。这样虽然会增加draw call,但节省了重建Mesh的性能损耗。  因为这个问题,我把游戏模块分成一个个不同的页面,每个页面都有一个UIPanel,然后在一个统一的地方调度各个页面,这个统一的地方是一个单例类,叫做MainPageMgr。  每个页面都有一个生命周期,即出现:准备出现-&播放出现动画-&动画完毕,展示在目标位置消失:准备消失-&播放消失动画-&完全消失  其中出现动画和消失动画由NGUI自带的UIPlayTween组件控制,直接把TweenPosition等脚本贴在UIPanel所在的物体上,让技术美术去调整。我把这一系列动作的逻辑都放在一个TweenPage类里,只要调一个弹出或消失的方法,就让它自动运行这个流程。  TweenPage类中有一个Bring(Boolean isBringIn)方法提供给MainPageMgr调用:Bring(true)出现Bring(false) 消失  当MainPageMgr调用一个页面TweenPage的Bring方法时,页面就按照它的生命周期开始运动,并触发相应的回调方法。  TweenPage类中有以下回调接口,供具体的页面实现相关逻辑:OnPreBringIn 准备弹出的回调OnBringIn 播放完弹出动画的回调OnPreBringOut 准备播放消失动画的回调OnBringOut 完全消失的回调  这样,只需要在具体的页面类中重写这几个接口,就可以在这几个时间点做一些事情。例如在OnPreBring接口中实现页面的数据刷新。  自此,页面TweenPage的框架基本成型,然后是实现MainPageMgr的统一调度。  我的做法是把每个页面的TweenPage实例都添加到MainPageMgr中,然后在MainPageMgr中为每个TweenPage实例都提供一个弹出方法,如弹出或关闭英雄管理界面BringPageHero(Boolean isBringIn)。这个弹出方法可以根据不同页面的情况设置不同的参数,但都有一个Boolean值表示是让页面出现还是消失,并且都要调用MainPageMgr的BringPage(TweenPage page)方法。  BringPage(TweenPage page)方法主要实现页面的统一调度管理,例如把一个页面显示到屏幕最前面,挡住其他所有页面。  在MainPageMgr中实现List pageList,用来保存当前已经打开的页面。我把页面设计成叠加遮挡模式,即一个页面弹出会叠在前一个页面上面并挡住它。pageList中就按顺序保存正处于打开状态的页面TweenPage实例。每次打开一个页面,都会给pageList中的所有TweenPage的UIPanel设置一个新的深度,并把当前已经打开的所有页面GameObject的Z轴往前移,这样可以确保夹在两个UIPanel之间的3D模型(如角色模型)显示正常。最后把要打开的页面加入到pageList中,并给它的UIPanel赋值一个当前最大的深度,使其可以遮挡前面所有的页面。让页面消失就使用相反的操作。这些操作都是用MainPageMgr的方法BringPage(Boolean isBringIn)实现。  界面的调度逻辑基本就是这样,现在要加一个新的页面只需要新建一个继承TweenPage的类,贴上UIPanel和其他UIPlayTween组件,并在MainPageMgr中添加一个弹出方法即可。  这里只是提供一个大致的思路,细节就不再深入说明了。  关于NData  NData是三年前我无意中发现的一个界面插件,这个插件开启了我做U3D界面的新篇章,刚开始用的那个感觉,就好像写JSP的人突然用上了SSH。虽然这个插件的作者已经停止更新了,但他的MVVM思想非常值得借鉴。  在刚接触NGUI的时候,我们一般会采用在脚本中获取NGUI 组件的形式给NGUI 组件赋值。有两种选择:一种是在代码中根据路径获取NGUI 组件;另一种是在场景中,直接把组件拖到脚本上。第一种方法的的缺点是需要维护NGUI组件的路径,第二种方法的缺点是替换组件时总是需要重新拖组件。两种方法都比较不方便,这里用第二种来举例。  打个比方,我们界面右上角要显示玩家拥有的金币总数。于是我们做了个UILabel拖进脚本,在脚本里给它的text赋值显示当前金币数量。  PagePlayer.cs中:&&public UILabel goldL& & public void SetGold( string gold )& & {& && &&&goldLabel.text =& & }复制代码  后来策划需求在左下角和右下角也要显示金币,于是我们又做了两个uilabel放到相应位置,并在脚本里添加变量,把新加的uilabel拖到脚本里。每次金币的值发生变化,就要找到所有的uilabel变量给他们一一赋值。  PagePlayer.cs中:public UILabel goldL& & public UILabel goldLabel1;& & public UILabel goldLabel2;& & public void SetGold( string gold )& & {& && &&&goldLabel.text =& && &&&goldLabel1.text =& && &&&goldLabel2.text =& & }作者:kUANG tOBY链接:/p/来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。复制代码  然而可能以后又会增加其它显示金币的界面,每加一个金币的UILabel,就要去脚本里增加一个UILabel 的变量然后在金币变化的时候给它赋值。虽然麻烦,但也能把功能做出来。我们不能就此满足,偷懒是提高生产力的最大动力。  现在想要简化这个流程,就要实现以下功能。  1,不需要每次添加金币文字的时候都在脚本中新增一个UILabel变量,并把对应的UILabel组件拖进来。  2,不需要每次修改金币的时候都要找到所有的金币UILabel变量去修改他们的值。  初步的解决方案是这样子的:我希望脚本里面有一个值 gold代表的是金币数量。所有的金币UILabel 都跟这个值产生关联。只要修改这个值,所有跟他关联的UILabel 都自动发生变化。另外,在我要添加一个金币UILabel 的时候,我希望它能自动去找页面脚本中的gold变量来发生关联,而不需要我在脚本中改代码。  具体实现的思路,就是在带有UILabel脚本的物体上加一个脚本,使其与页面脚本种的gold变量发生关联。然后给gold变量加set方法,在这个方法中发一个消息,告知所有和gold有过关联的的UILabel要发生值的改变。这样每次给gold变量赋值的时候,所有与其关联的UILabel就会自动更新显示的内容。  本着不重复造轮子的原则,在疑似开始造轮子之前一定要Google一下。于是在网上搜出了MVVM模式,NData插件等等。并发现NData不仅可以用于UILabel,还可以用于各种NGUI组件,并有很好的绑定层级管理。  NData就是基于MVVM模式,其中用户自定义继承EZData.Context的类,就相当于是自定义ViewModel层的内容。  剩下的问题就是,怎样用这个工具来管理页面。  根据上文,我把界面分成很多个TweenPage,然后在单例MainPageMgr中统一管理。对于数据,我希望把每个页面的数据也独立出来,即每个页面有一个对应的继承EZData.Context的类,这个页面相关的数据都放在这个类中,然后再由MainPageMgr来统一管理。  例如有个游戏页面PageInGame,用来显示游戏中获得的金币,钻石和星星。现在新建一个PageInGameContext继承EZData.Context。现在PageInGame页面就有一个model层PageInGame类和一个ViewModel层PageInGameContext类。View层自然就是PageInGame物体下面的NGUI组件了。这样就形成了MVVM模式。using UnityEusing System.Cpublic class PageInGameContext : EZData.Context{& & #region Property Gold& & private readonly EZData.Property _privateGoldProperty = new EZData.Property ();& & public EZData.Property GoldProperty { get { return _privateGoldP } }& & public int Gold {& && &&&get& & { return GoldProperty.GetValue (); }& && &&&set& & { GoldProperty.SetValue (value); }& & }& & #endregion& & #region Property Diamond& & private readonly EZData.Property _privateDiamondProperty = new EZData.Property ();& & public EZData.Property DiamondProperty { get { return _privateDiamondP } }& & public int Diamond {& && &&&get& & { return DiamondProperty.GetValue (); }& && &&&set& & { DiamondProperty.SetValue (value); }& & }& & #endregion& & #region Property Star& & private readonly EZData.Property _privateStarProperty = new EZData.Property ();& & public EZData.Property StarProperty { get { return _privateStarP } }& & public int Star {& && &&&get& & { return StarProperty.GetValue (); }& && &&&set& & { StarProperty.SetValue (value); }& & }& & #endregion}public class PageInGame : TweenPage {& & public PageInGameContext C&&& & protected override void Awake ()<li style="word-wrap: break- margin: 0px 0px 0px 2 padding: 0px 0px 0px 10 list-style-type: decimal-leading- font-family: Monaco, Consolas, '" lucida="" console"',="" '"courier="" new"',=""="" font-size:="" 12
微信扫一扫
专注于VR的学习、开发和人才交流
&津ICP备号

我要回帖

更多关于 ugui 让界面在最上层 的文章

 

随机推荐