《Unity3D高级编程 主程手记》第四章 用户界面(五) 快速构建一个简单易用的 UI 框架

avatar
作者
猴君
阅读量:0

从宏观角度看 UI 框架

        一个项目中拥有众多 UI,每个界面有很多组件;有不少界面是通用的;也有不少界面是父子关系;界面上的按钮需要有一个处理输入的句柄。基于这些思考,可以编写处统一的管理类、基类和输入事件响应机制。

1、管理类

        我们需要用一个单实例来管理所有的 UI,让它们有统一的接口进行以上的操作,创建UI管理类是最好的选择,我们可以命名它为 UIManager,这个名字符合它代表的功能。

具体作用:

  • 创建 UI
  • 查找现有的 UI
  • 销毁 UI
  • 完成 UI 的统一接口调用和调配工作

存储内容:

  • UI 实例
  • UI 常用变量,比如屏幕的适配标准大小、Camara 等。

        UIManager 是 UI 的管理员,统筹管理 UI 问题。其中涉及的管理包括上下层 UI 切换、不同的加载方式(预加载 UI 、销毁和隐藏)等,其源码如下。

public class ScreenManager : CSingleton<ScreenManager> {     protected Transform _transform = null;     private Dictionary<string, UIScreenBase> _DicScreens = new Dictionary<string, UIScreenBase>();      // 关闭所有界面     public void CloseAll()     {         ...     }      // 是否UI正打开     public bool IsShow(string screenID)     {         ...     }      // 关闭界面     public void CloseScreen(UIScreenBase screen)     {         ...     }      // 创建所有界面     public T CreateMenu<T>() where T : UIScreenBase     {     	...     }      // 找出某个界面     public T FindMenu<T>() where T : UIScreenBase     {         ...     }      ... } 

 2、基类

        项目中有很多界面,这些界面都有一定的共性。共性产生统一特征的接口,如Init、Open和Close等。

        继承基类可使管理比较方便,比如上面提到的 UIManager 里的 UI 实例可以统一使用基类的方式存储。UIScreenBase

public abstract class UIScreenBase : MonoBehaviour {     protected bool mInitialized = false;     protected UIState mState = UIState.None;     public UIState State { get { return mState; } }      public delegate void OnScreenHandlerEventHandler(UIScreenBase screen);     public event OnScreenHandlerEventHandler onCloseScreen;      // 初始化 	protected virtual void Init() 	{	         mInitialized = true; 	}  	//打开 	public virtual void Open() {}  	//关闭 	public virtual void Close() {} } 

        每个界面都继承自 UI 基类,每个界面成为扩展界面功能的一个类实体,可以自主定义自己的功能性的接口,同时还会受到管理类的统一调配。 

3、输入事件响应机制

        Unity3D 的 UGUI 输入事件响应机制建立通常有两种,一种是继承型,一种是绑定型。

继承型

        事件先响应到基类,再由基类反应给父类,由父类做处理,这样 UI 既可以得到对输入事件的响应,也可以自行修改自己需要的逻辑。

绑定型

        在对输入事件响应之前,为 UI 元素绑定一个事件响应的组件。

        编写一个绑定型事件类 UIEvent,当某个 UI 元素需要输入事件回调时,对这个物体绑定一个 UIEvent,并且对 UIEvent 里需要的相关响应事件进行赋值或注册操作函数。当输入事件响应时,由 UIEvent 来区分输入的是什么类型的事件,再分别调用响应到具体函数。

共同点:都需要与UI元素关联

区别:继承型融入在了各种组件内,而绑定型以独立的组件形式体现出来的

/// <summary> /// UI 事件 /// </summary> public class UI_Event : UnityEngine.EventSystems.EventTrigger {     protected const float CLICK_INTERVAL_TIME = 0.2f; //const click interval time     protected const float CLICK_INTERVAL_POS = 2; //const click interval pos      public delegate void PointerEventDelegate ( PointerEventData eventData , UI_Event ev);     public delegate void BaseEventDelegate ( BaseEventData eventData , UI_Event ev);     public delegate void AxisEventDelegate ( AxisEventData eventData , UI_Event ev);      public Dictionary<string,object> mArg = new Dictionary<string,object>();      public BaseEventDelegate onDeselect = null;     public PointerEventDelegate onBeginDrag = null;     public PointerEventDelegate onDrag = null;     public PointerEventDelegate onEndDrag = null;     public PointerEventDelegate onDrop = null;     public AxisEventDelegate onMove = null;     public PointerEventDelegate onClick = null;     public PointerEventDelegate onDown = null;     public PointerEventDelegate onEnter = null;     public PointerEventDelegate onExit = null;     public PointerEventDelegate onUp = null;     public PointerEventDelegate onScroll = null;     public BaseEventDelegate onSelect = null;     public BaseEventDelegate onUpdateSelect = null;     public BaseEventDelegate onCancel = null;     public PointerEventDelegate onInitializePotentialDrag = null;     public BaseEventDelegate onSubmit = null;      private static PointerEventData mPointData = null;      // 设置参数     public void SetData(string key , object val)     {         mArg[key] = val;     }      // 获取参数     public D GetData<D>(string key)     {         if(mArg.ContainsKey(key))         {             return (D)mArg[key];         }         return default(D);     }      ...      public static UI_Event Get(GameObject go)     {         UI_Event listener = go.GetComponent<UI_Event>();         if (listener == null) listener = go.AddComponent<UI_Event>();         return listener;     }      public override void OnBeginDrag( PointerEventData eventData ) { ... }     public override void OnDrag( PointerEventData eventData ) { ... }     public override void OnEndDrag( PointerEventData eventData ) { ... }     public override void OnDrop( PointerEventData eventData ) { ... }     public override void OnMove( AxisEventData eventData ) { ... }      public override void OnPointerClick(PointerEventData eventData)     {     	...         if(onClick != null)         {             onClick(eventData , this);         }         ...     }      public override void OnPointerDown (PointerEventData eventData) { ... }     public override void OnPointerEnter (PointerEventData eventData) { ... }     public override void OnPointerExit (PointerEventData eventData) { ... }     public override void OnPointerUp (PointerEventData eventData) { ... }     public override void OnScroll( PointerEventData eventData ) { ... } }

        以上代码只把事件响应最重要的部分,其余还包括组件的挂在、事件的调用及参数的设置等。

        最好事先把 UI_Event 挂载到 GameObject 节点上,这样可节省 AddComponent 的消耗,如果要在按钮响应时加入参数,则可再使用 SetData 来设置当前节点的回调参数。

        为什么要这么做?

  1. 统一管理所有事件句柄
  2. 更加方便地使用输入事件句柄
  3. 更方便地设置参数

4、自定义组件

(1)UI 动画组件

  • 首先它需要依赖 Unity3D 的 Animator 组件 [RequireComponent (typeof(Animator))]
  • 其次它要有播放(Play)接口用来播放指定动画,这里 Play 的参数包括动画名、播放完毕后的回调函数委托等。

  • 再次在 public 变量中需要 AutoPlay 这个参数,这样美术人员就可以在 Unity3D 界面上设置自动播放而无需程序调用了。

  • 最后需要在自动播放时选择指定的动画名和是否循环播放,以及循环播放间隔。

(2)按钮播放音效组件

(3)UI 元素跟随 3D 物体组件

        比如游戏中的血条、场景中建筑物头上的标志等。通过不断地计算 3D 物体在屏幕中的位置来确定 UI 位置,当前位置不同时再进行更改以避免不必要的移动。

(4)无限滚动页面组件

        比如背包界面。用一个自定义的无线滚动页面组价来替换原来的模式。

(5)其他组件

        包括数字飘字组件、计数组件、下拉框组件等。

        编写自定义的 UI 组件的目标就是,增加更多通用的组件,减少重复劳动,让程序员在编写 UI 界面时更加快捷、高效,同时也可提升 UI 的运行效率。拥有属于自己的一套自定义套件,对项目来说,也是一件非常有价值和高效的事。

    广告一刻

    为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!