从宏观角度看 UI 框架
一个项目中拥有众多 UI,每个界面有很多组件;有不少界面是通用的;也有不少界面是父子关系;界面上的按钮需要有一个处理输入的句柄。基于这些思考,可以编写处统一的管理类、基类和输入事件响应机制。
1、管理类
我们需要用一个单实例来管理所有的 UI,让它们有统一的接口进行以上的操作,创建UI管理类是最好的选择,我们可以命名它为 UIManager,这个名字符合它代表的功能。
具体作用:
- 创建 UI
- 查找现有的 UI
- 销毁 UI
- 完成 UI 的统一接口调用和调配工作
存储内容:
- UI 实例
- UI 常用变量,比如屏幕的适配标准大小、Camara 等。
UIManager 是 UI 的管理员,统筹管理 UI 问题。其中涉及的管理包括上下层 UI 切换、不同的加载方式(预加载 UI 、销毁和隐藏)等,其源码如下。
cs
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
cs
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元素关联
**区别:**继承型融入在了各种组件内,而绑定型以独立的组件形式体现出来的
cs
/// <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 来设置当前节点的回调参数。
为什么要这么做?
- 统一管理所有事件句柄
- 更加方便地使用输入事件句柄
- 更方便地设置参数
4、自定义组件
(1)UI 动画组件
-
首先它需要依赖 Unity3D 的 Animator 组件 [RequireComponent (typeof(Animator))]
-
其次它要有播放(Play)接口用来播放指定动画,这里 Play 的参数包括动画名、播放完毕后的回调函数委托等。
-
再次在 public 变量中需要 AutoPlay 这个参数,这样美术人员就可以在 Unity3D 界面上设置自动播放而无需程序调用了。
-
最后需要在自动播放时选择指定的动画名和是否循环播放,以及循环播放间隔。
(2)按钮播放音效组件
(3)UI 元素跟随 3D 物体组件
比如游戏中的血条、场景中建筑物头上的标志等。通过不断地计算 3D 物体在屏幕中的位置来确定 UI 位置,当前位置不同时再进行更改以避免不必要的移动。
(4)无限滚动页面组件
比如背包界面。用一个自定义的无线滚动页面组价来替换原来的模式。
(5)其他组件
包括数字飘字组件、计数组件、下拉框组件等。
编写自定义的 UI 组件的目标就是,增加更多通用的组件,减少重复劳动,让程序员在编写 UI 界面时更加快捷、高效,同时也可提升 UI 的运行效率。拥有属于自己的一套自定义套件,对项目来说,也是一件非常有价值和高效的事。