`

NGUI所见即所得之UICamera

阅读更多

NGUI所见即所得之UICamera

 

       UI,除了界面的显示,还有一个重要的元素:事件响应。MoneBehaviour提供一些事件提供了一些函数接口(OnMouseUp,OnMouseDown等),只要MonBehaviour的子类实现这相应的方法以及方法执行的条件达到,Unity底层就会分发调用执行这个函数。一般地,UI事件响应处理机制会有4个基本元素:

 

      1.event object:事件对象,即当前事件的类型,如鼠标左键按下等。

      2.event source:事件源,或事件的触发器,比如说,鼠标左键单击点击一个button,那么button就是event source,鼠标左键单击就是event source。

      3.event handle:事件处理方法。

      4.event listener:事件的监听,比如上面说的鼠标左键点击一个button,event listener就是监听打button的一个mouse click事件,然后分发调用对应的event handle进行处理

 

        一般event source不会单独存在,经常会跟event handle绑定在一起,其实就是指定了不同的event handle就是不同event source,如Button就有单击双击事件,Input就有输入焦点事件,event listener就好像人的大脑,监控这个所有的事件,同时作出不同的响应。

     NGUI自己组织了一套UI事件响应处理机制, 不是对MonoBehaviour的方法的封装调用,UICamera就是NGUI框架中的event listener,原理很简单:在Update中捕获鼠标,键盘等设备的输入(也可以狭义的认为UICamera就是event listener),判断不同event object 和event source,然后“广播”分发执行event handle,下面附上分发的函数:

	/// <summary>
	/// Generic notification function. Used in place of SendMessage to shorten the code and allow for more than one receiver.
	/// </summary>

	static public void Notify (GameObject go, string funcName, object obj)
	{
		if (go != null)
		{
			go.SendMessage(funcName, obj, SendMessageOptions.DontRequireReceiver);

			if (genericEventHandler != null && genericEventHandler != go)
			{
				genericEventHandler.SendMessage(funcName, obj, SendMessageOptions.DontRequireReceiver);
			}
		}
	}

       知道的谁是event listener,那要怎么实现event handle。实现NGUI的事件的方法有很多种,③给了三种方式监听NGUI的事件方法。文末贴了NGUI支持的event handle。

                                                                                                                                                                                                                   增补于:11/10/2013 8:45,感觉之前的没头没尾的,就加了上面的铺垫

 

       记得刚开始用NGUI的时候,就有心思要去琢磨下UICamera,那个时候NGUI还是2.6的版本,现在已经到了3.0.3f,NGUI更新真的很强劲, 当然改动也挺大的,特性也越来越多了。之前本来研究下UICamera最后还是放弃了,因为UICamera的代码太复杂了,很凌乱,也就放下去了,这几天重新翻看了下,发现UICamera的可读性太强了,代码的组织逻辑很强,完全可以当做文本来从上到下来阅读,所以才会有这篇文章。

       UICamera做了很多有优化,新增了一些特性,之前可能觉得NGUI只是做一个工具,现在越来越完美了,少废话,下面把看到的亮点呈上。

ClickNotification

        /// <summary>
	/// Whether the touch event will be sending out the OnClick notification at the end.
	/// </summary>

	public enum ClickNotification
	{
		None,
		Always,
		BasedOnDelta,
	}

       ClickNotification定义了OnClick响应的条件,后面也定义了ClickNotification变量 public ClickNotification clickNotification = ClickNotification.Always;

       ClickNotification.None: 不响应OnClick事件

       ClickNotification.Always:总是响应OnClick事件

       ClickNotification.BaseOnDelta:依据移动的delta的距离判断是否响应OnClick函数,如果移动距离大于float click  = isMouse ? mouseClickThreshold : touchClickThreshold;则不响应OnClick事件

下面这部分代码是当响应了OnDrag事件就把currentTouch.clickNotification = ClickNotification.None;就不在会响应OnClick事件了。

                                                bool isDisabled = (currentTouch.clickNotification == ClickNotification.None);
						Notify(currentTouch.dragged, "OnDrag", currentTouch.delta);
						isDragging = false;

						if (isDisabled)
						{
							// If the notification status has already been disabled, keep it as such
							currentTouch.clickNotification = ClickNotification.None;
						}
						else if (currentTouch.clickNotification == ClickNotification.BasedOnDelta && click < mag)
						{
							// We've dragged far enough to cancel the click
							currentTouch.clickNotification = ClickNotification.None;
						}

 然后再执行OnClick和OnDoubleClick事件先判断条件currentTouch.clickNotification != ClickNotification.None 是否成立:

// If the touch should consider clicks, send out an OnClick notification
					if (currentTouch.clickNotification != ClickNotification.None)
					{
						float time = Time.realtimeSinceStartup;

						Notify(currentTouch.pressed, "OnClick", null);

						if (currentTouch.clickTime + 0.35f > time)
						{
							Notify(currentTouch.pressed, "OnDoubleClick", null);
						}
						currentTouch.clickTime = time;
					}

 EventType

public enum EventType
	{
		World,	// Perform a Physics.Raycast and sort by distance to the point that was hit.
		UI,		// Perform a Physics.Raycast and sort by widget depth.
	}

        这个很简单就是定义当前射线和碰撞体碰撞的判断标准,如果是UI则以Depth来判断,如果是World是以实际距离来判断。

List<UICamera> list

        /// <summary>
	/// List of all active cameras in the scene.
	/// </summary>
 
	static public List<UICamera> list = new List<UICamera>();

        UICamera在初始化的时候会被加入 mList这个链表中,然后对链表进行排序,根据相机的深度,深度值越小的相机排位靠前,最靠前的相机为场景的主UICamera,然后只有只有主UICamera才会去监测场景中的事件,其他的UICamera并不执行监测任务。UICamera利用Unity的Raycast去监测事件发生的对象,因为发射出去的Ray对象必须碰撞到Collider才会有反应,所以NGUI中所有需要响应事件的控件均需要添加Collider,同时Ray只会碰撞到深度最小的Collider,Ray射线的最大深度为rangeDistance,当这个值为-1时则发射深度和相机深度一样,主UICamera每一帧都会主动去发射Ray检测鼠标此时触碰到的对象并将其记录在对应的鼠标按键事件中,这是能监测到OnHover这个动作的关键(当然只有在useMouse为true时才会有此操作)。

        在游戏场景初始化阶段,每个UICamera都会根据平台义useMouse、useTouch、useKeyboard和useController 这些属性,分别对应的是能否在场景使用鼠标、触摸屏、键盘以及摇杆。

        public bool useMouse = true;

	public bool useTouch = true;

	public bool allowMultiTouch = true;

	public bool useKeyboard = true;

	public bool useController = true;

 

MouseOrTouch

        /// <summary>
	/// Ambiguous mouse, touch, or controller event.
	/// </summary>

	public class MouseOrTouch
	{
		public Vector2 pos;				// Current position of the mouse or touch event
		public Vector2 delta;			// Delta since last update
		public Vector2 totalDelta;		// Delta since the event started being tracked

		public Camera pressedCam;		// Camera that the OnPress(true) was fired with

		public GameObject current;		// The current game object under the touch or mouse
		public GameObject pressed;		// The last game object to receive OnPress
		public GameObject dragged;		// The last game object to receive OnDrag

		public float clickTime = 0f;	// The last time a click event was sent out

		public ClickNotification clickNotification = ClickNotification.Always;
		public bool touchBegan = true;
		public bool pressStarted = false;
		public bool dragStarted = false;
	}

       MouseOrTouch是一个很重要的类,是一个事件的结构体,然后就定义了不同平台的事件,记录Camera监测的事件:MouseOrTouch只是记录“鼠标”等的移动的“物理”信息——位置,移动距离等,只有鼠标是否按下只有在Update中每帧监测。

       下面定义不同平台的事件,例如鼠标事件,mMouse记录鼠标左键,右键和中键的事件(因为鼠标这里只记录鼠标的三个按键,所以mMouse才是有三个元素,现在明白为啥了吧)。

        // Mouse events
	static MouseOrTouch[] mMouse = new MouseOrTouch[] { new MouseOrTouch(), new MouseOrTouch(), new MouseOrTouch() };

	// The last object to receive OnHover
	static GameObject mHover;

	// Joystick/controller/keyboard event
	static MouseOrTouch mController = new MouseOrTouch();

	// Used to ensure that joystick-based controls don't trigger that often
	static float mNextEvent = 0f;

	// List of currently active touches
	static Dictionary<int, MouseOrTouch> mTouches = new Dictionary<int, MouseOrTouch>();

 currentTouch

        /// <summary>
	/// ID of the touch or mouse operation prior to sending out the event. Mouse ID is '-1' for left, '-2' for right mouse button, '-3' for middle.
	/// </summary>

	static public int currentTouchID = -1;

	/// <summary>
	/// Current touch, set before any event function gets called.
	/// </summary>

	static public MouseOrTouch currentTouch = null;

       currentTouch这个变量是整个UICamera中控制事件监测的关键所在,记录了当前事件的触发对象和一些其他诸如position位置、dealta时间、totaldealta总时间等属性,然后用currentTouchID记录当前事件的类型,这些类型包括鼠标事件、键盘控制器事件以及触摸屏事件。

 

ProcessTouch

       ProcessTouch这个函数就是根据currentTouch来针对不同的情况响应不同的函数,被ProcessMouse,ProcessTouch和ProcessOthers调用,如ProcessMouse,分别捕获鼠标三个按键的状态,然后调用ProcessTouch来响应:

                // Process all 3 mouse buttons as individual touches
		if (useMouse)
		{
			for (int i = 0; i < 3; ++i)
			{
				bool pressed = Input.GetMouseButtonDown(i);
				bool unpressed = Input.GetMouseButtonUp(i);
	
				currentTouch = mMouse[i];
				currentTouchID = -1 - i;
	
				// We don't want to update the last camera while there is a touch happening
				if (pressed) currentTouch.pressedCam = currentCamera;
				else if (currentTouch.pressed != null) currentCamera = currentTouch.pressedCam;
	
				// Process the mouse events
				ProcessTouch(pressed, unpressed);
			}

『ProcessMouse分析

         因为之前版本升级到NGUI3.0.6时,UICamera出现了一个Bug:当Time.ScaleTime != 1f 的时候,事件响应有问题,当时由于时间关系,只是和之前的版本进行比对,增加了些代码解决的。但是还是感觉没有能对UICamera具体细节没能完全掌握,挺蹩脚的,还不能达到“自主”的处理目的,所以一直都想有时间好好把UICamera的事件分发流程细节清理下。

        /// <summary>
	/// Update mouse input.
	/// </summary>

	public void ProcessMouse ()
	{
		// No need to perform raycasts every frame
		if (mNextRaycast < RealTime.time)     //更新鼠标current为当前的 hoveredObject,如果时间间隔小于 20毫秒,就不更新
		{
			mNextRaycast = RealTime.time + 0.02f;
			if (!Raycast(Input.mousePosition, out lastHit)) hoveredObject = fallThrough;
			if (hoveredObject == null) hoveredObject = genericEventHandler;
			for (int i = 0; i < 3; ++i) mMouse[i].current = hoveredObject;
		}

		lastTouchPosition = Input.mousePosition;
		bool highlightChanged = (mMouse[0].last != mMouse[0].current);
		if (highlightChanged) currentScheme = ControlScheme.Mouse;

		// Update the position and delta                  更新三个鼠标按键的位置 delta 和pos ,
		mMouse[0].delta = lastTouchPosition - mMouse[0].pos;
		mMouse[0].pos = lastTouchPosition;
		bool posChanged = mMouse[0].delta.sqrMagnitude > 0.001f;

		// Propagate the updates to the other mouse buttons
		for (int i = 1; i < 3; ++i)    
		{
			mMouse[i].pos = mMouse[0].pos;
			mMouse[i].delta = mMouse[0].delta;
		}

		// Is any button currently pressed?
		bool isPressed = false;

		for (int i = 0; i < 3; ++i)
		{
			if (Input.GetMouseButton(i))
			{
				currentScheme = ControlScheme.Mouse;
				isPressed = true;
				break;
			}
		}

		if (isPressed)
		{
			// A button was pressed -- cancel the tooltip
			mTooltipTime = 0f;
		}
		else if (posChanged && (!stickyTooltip || highlightChanged))    //更新Tip显示
		{
			if (mTooltipTime != 0f)
			{
				// Delay the tooltip
				mTooltipTime = RealTime.time + tooltipDelay;
			}
			else if (mTooltip != null)
			{
				// Hide the tooltip
				ShowTooltip(false);
			}
		}

		// The button was released over a different object -- remove the highlight from the previous
		if (!isPressed && mHover != null && highlightChanged)   //更新hover GameObject ,并分发 OnHover事件
		{
			currentScheme = ControlScheme.Mouse;
			if (mTooltip != null) ShowTooltip(false);
			Notify(mHover, "OnHover", false);
			mHover = null;
		}

		// Process all 3 mouse buttons as individual touches   分别处理鼠标的三个按键,获取按键状态,进行事件分发
		for (int i = 0; i < 3; ++i)
		{
			bool pressed = Input.GetMouseButtonDown(i);
			bool unpressed = Input.GetMouseButtonUp(i);

			if (pressed || unpressed) currentScheme = ControlScheme.Mouse;

			currentTouch = mMouse[i];
			currentTouchID = -1 - i;
			currentKey = KeyCode.Mouse0 + i;
	
			// We don't want to update the last camera while there is a touch happening
			if (pressed) currentTouch.pressedCam = currentCamera;
			else if (currentTouch.pressed != null) currentCamera = currentTouch.pressedCam;
	
			// Process the mouse events
			ProcessTouch(pressed, unpressed);
			currentKey = KeyCode.None;
		}
		currentTouch = null;

		// If nothing is pressed and there is an object under the touch, highlight it
		if (!isPressed && highlightChanged)
		{
			currentScheme = ControlScheme.Mouse;
			mTooltipTime = RealTime.time + tooltipDelay;
			mHover = mMouse[0].current;
			Notify(mHover, "OnHover", true);
		}

		// Update the last value
		mMouse[0].last = mMouse[0].current;
		for (int i = 1; i < 3; ++i) mMouse[i].last = mMouse[0].last;
	}

  这次回看UICamera的代码,更加UICamera优化了很多,代码逻辑清晰简单了,之前的一直感觉很乱(一堆条件判断)才一直没有细看。虽然上面代码还是有加点注释,其实已经完全没必要了。然后在NGUI3.0.7版本还增加了 在Editor下用鼠标做屏幕Touch的操作的功能:

/// Process fake touch events where the mouse acts as a touch device.

/// Useful for testing mobile functionality in the editor.

                                                                                                                                                                                                                    增补于 2013,12,29 15:15

其他

        UICamera还提供其他一些“特性”,能够让开发者实现更多的功能(就不解释了吧, 有注释):

/// <summary>
	/// If 'true', once a press event is started on some object, that object will be the only one that will be
	/// receiving future events until the press event is finally released, regardless of where that happens.
	/// If 'false', the press event won't be locked to the original object, and other objects will be receiving
	/// OnPress(true) and OnPress(false) events as the touch enters and leaves their area.
	/// </summary>

	public bool stickyPress = true;

/// <summary>
	/// If set, this game object will receive all events regardless of whether they were handled or not.
	/// </summary>

	static public GameObject genericEventHandler;

	/// <summary>
	/// If events don't get handled, they will be forwarded to this game object.
	/// </summary>

	static public GameObject fallThrough;

 

最后,NGUI一共支持一下事件:

 

void OnHover (bool isOver) – Sent out when the mouse hovers over the collider or moves away from it. Not sent on touch-based devices.
void OnPress (bool isDown) – Sent when a mouse button (or touch event) gets pressed over the collider (with ‘true’) and when it gets released (with ‘false’, sent to the same collider even if it’s released elsewhere).
void OnClick() — Sent to a mouse button or touch event gets released on the same collider as OnPress. UICamera.currentTouchID tells you which button was clicked.
void OnDoubleClick () — Sent when the click happens twice within a fourth of a second. UICamera.currentTouchID tells you which button was clicked.
void OnSelect (bool selected) – Same as OnClick, but once a collider is selected it will not receive any further OnSelect events until you select some other collider.
void OnDrag (Vector2 delta) – Sent when the mouse or touch is moving in between of OnPress(true) and OnPress(false).
void OnDrop (GameObject drag) – Sent out to the collider under the mouse or touch when OnPress(false) is called over a different collider than triggered the OnPress(true) event. The passed parameter is the game object of the collider that received the OnPress(true) event.
void OnInput (string text) – Sent to the same collider that received OnSelect(true) message after typing something. You likely won’t need this, but it’s used by UIInput
void OnTooltip (bool show) – Sent after the mouse hovers over a collider without moving for longer than tooltipDelay, and when the tooltip should be hidden. Not sent on touch-based devices.
void OnScroll (float delta) is sent out when the mouse scroll wheel is moved.
void OnKey (KeyCode key) is sent when keyboard or controller input is used.

 

小结:

       最近由于项目要用到FastGUI,然后手上的FastGUI不支持NGUI(NGUI变动太大了),然后自己要升级下FastGUI,就要更多的掌握NGUI的原理,所以才会一直不断的写一些文章。写文章主要是记录下自己从中看到的东西,当然D.S.Qiu最喜欢和大家分享,希望能对读者有帮助,哪怕只有一个人,D.S.Qiu也会很兴奋的,因为很多次D.S.Qiu都不打算写的(文章写的太烂,没有深度,逻辑差,每次都要熬夜等),但当我看到别人文章的亮点时,我就觉得自己还是可以分享些的。

       今天把FastGUI 兼容到了NGUI3.0.3f,还增加一些功能,然后要写一个文档给美术的同事,我感觉头就大了,感觉如果要我口述一定能让听者完全明白,但是写起来就完全不着调,所以觉得D.S.Qiu的文字很渣,马上就是凌晨1:30,睡觉,晚安!

 

        如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件(gd.s.qiu@gmail.com)交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。

        转载请在文首注明出处:http://dsqiu.iteye.com/blog/1971866

更多精彩请关注D.S.Qiu的博客和微博(ID:静水逐风)

 

 

参考:

2B青年: http://blog.sina.com.cn/s/blog_6f16aba701017mgz.html

②tasharen: http://www.tasharen.com/?page_id=160

雨松MOMOhttp://www.xuanyusong.com/archives/2390

   

       

  • 大小: 44.2 KB
3
0
分享到:
评论
6 楼 kevinmw 2013-12-14  
有个问题,使用fastgui1.3和ngui2.6.4协作
按文档说的,修改psd以后重新reimport,结果硬是多加出一遍新的sprite出来
是我的版本问题吗
5 楼 kevinmw 2013-12-14  
DSQiu 写道

比较耗时,然后FastGUI 没有更新到NGUI3.0.x,所以不改动几乎不能用了,晚上我把我修改的FastGUI 代码放出来……


期待DSQiu的FastGUI修改新功能。。
4 楼 DSQiu 2013-12-13  
kevinmw 写道
DSQiu 写道
kevinmw 写道
fastgui 好用不,用来改善ui流程是吧
坑多不多

比较耗时,然后FastGUI 没有更新到NGUI3.0.x,所以不改动几乎不能用了,晚上我把我修改的FastGUI 代码放出来……



如果是用老版本的ngui(如2.6.4),是不是基本不用改什么?
耗时是指什么?导入psd?运行,还是指修改适配3.0.x

对,网上的FastGUI 1.3.2 版本是支持到 NGUI 2.6.3 ,耗时是指,如果psd的图层的层次和深度多了,photoshop就会未响应(不过一直等着还是会导出来的),完全导出来时间会很长……
3 楼 kevinmw 2013-12-13  
DSQiu 写道
kevinmw 写道
fastgui 好用不,用来改善ui流程是吧
坑多不多

比较耗时,然后FastGUI 没有更新到NGUI3.0.x,所以不改动几乎不能用了,晚上我把我修改的FastGUI 代码放出来……



如果是用老版本的ngui(如2.6.4),是不是基本不用改什么?
耗时是指什么?导入psd?运行,还是指修改适配3.0.x
2 楼 DSQiu 2013-12-13  
kevinmw 写道
fastgui 好用不,用来改善ui流程是吧
坑多不多

比较耗时,然后FastGUI 没有更新到NGUI3.0.x,所以不改动几乎不能用了,晚上我把我修改的FastGUI 代码放出来……
1 楼 kevinmw 2013-12-13  
fastgui 好用不,用来改善ui流程是吧
坑多不多

相关推荐

    NGUI的核心组件UICamera2

    NGUI的核心组件UICamera2

    NGUI Next-Gen UI 3.9.1

    - NEW: Added UICamera.first referencing the active NGUI event system. - FIX: Alpha should now work as expected with Linear lighting. - FIX: UICamera.isOverUI should now work properly for all types of ...

    NGUI Next-Gen UI v3.5.7

    NGUI是严格遵循KISS原则并用C#编写的Unity(适用于专业版和免费版)插件,提供强大的UI系统和事件通知框架。其代码简洁,多数类少于200行代码。这意味着程序员可以很容易地扩展NGUI的功能或调节已有功能。对所有其他...

    NGUI_Next-Gen UI v2.6.4.unitypackage

    NGUI_Next-Gen UI v2.6.4.unitypackage NGUI is a very powerful UI system and event notification framework. Features - Full Inspector integration - No need to hit Play to see the results - Supports all ...

    NGUI Next-Gen UI v3.11.2 (u5)

    NGUI Next-Gen UI v3.11.2 (u5)

    NGUI: Next-Gen UI 3.12.1

    NGUI: Next-Gen UI 3.12.1 所支持的Unity版本:4.7.0 及以上版本 NGUI is a very powerful UI system and event notification framework. Features - Editor integration, WYSIWYG - Localization, data binding,...

    NGUI Next-Gen UI 3.12.1.zip

    NGUI Next-Gen UI 3.12.1(基于unity)NGUI Next-Gen UI 3.12.1(基于unity)

    Unity插件 NGUI各种版本合集

    NGUI Next-Gen UI 3.6.0.unitypackage NGUI Next-Gen UI 3.12.1(u5.6.5).unitypackage NGUI Next-Gen UI 2019.3.0.unitypackage NGUI Next-Gen UI v2018.3.0.unitypackage NGUI Next-Gen UI v2018.3.0c(u2018.3.0)....

    最新版本的NGUI插件NGUI Next-Gen UI 覆盖unity多个版本

    NGUI Next-Gen UI是一款功能强大、灵活性高的UI插件,是当前最新版本的NGUI插件。它可以覆盖Unity的多个版本,包括Unity 5、Unity 2017和Unity 2018等。与其他UI插件相比,NGUI Next-Gen UI具有高效的性能和优秀的...

    最新版本的NGUI插件NGUI Next-Gen UI

    最新版本的NGUI插件NGUI Next-Gen UI

    NGUI: Next-Gen UI v3.12.0b 2018年5月

    NGUI: Next-Gen UI v3.12.0b 2018年5月 不用多说了吧。

    NGUI Next-Gen UI.zip

    NGUI资源,包含:NGUI Next-Gen UI 3.12.1(u5.6.5)|NGUI Next-Gen UI 2019.3.0|NGUI Next-Gen UI v3.11.0b (u5.4.3)|NGUI Next-Gen UI v2018.3.0|NGUI Next-Gen UI v2018.3.0c(u2018.3.0)|NGUI Next-Gen UI v2018.3....

    NGUI Next-Gen UI v2021.11.30.unitypackage

    NGUI Next-Gen UI v2021.11.30.unitypackage

    NGUI: Next-Gen UI v2.6.2

    NGUI+Next-Gen+UI v2.6.2.unitypackage 新版本 供大家玩玩

    NGUI Next-Gen UI v2018.3.0

    新测无误 。NGUI Next-Gen UI v2018.3.0 NGUI: Next-Gen UI kit Copyright © 2011-2018 Tasharen Entertainment Version 2018.3.0

    NGUI:Next-Gen UI 3.11.2

    NGUI:Next-Gen UI 3.12.1 最新版本的NGUI:Next-Gen UI

    NGUI Next-Gen UI v3.6.0

    最新版unity3d扩展插件:NGUI Next-Gen UI3.6.0 运行Unity3D,解压后此压缩包,在菜单Assets中选择自定义导入。 如果无法导入时,请检查导入目录中是否存在中文字符。...所见即所得的集成编辑器,支持所有平台。

    NGUI Next-Gen UI v3.8.0

    NGUI is a powerful UI system and event notification framework for Unity (both Pro and Free) written in C# that closely follows the KISS principle. It features clean code and simple, minimalistic ...

    NGUI Next-Gen UI 3.9.2

    NGUI Next-Gen UI 3.9.2 NGUI是严格遵循KISS原则并用C#编写的Unity(适用于专业版和免费版)插件,提供强大的UI系统和事件通知框架。

    NGUI - Next-Gen UI v3.11.4 (2017.1).unitypackage

    NGUI - Next-Gen UI v3.11.4 (2017.1).unitypackage

Global site tag (gtag.js) - Google Analytics