Unity UI事件的拓展与应用
前言
Unity本身的UI事件监听机制没办法传递额外的数据,而我们在游戏实际开发中经常需要传递事件的业务数据,以方便业务功能的开发。例如,我们点击一个按钮,如果这个按钮的点击事件中能传递我们的自定义数据,例如枚举,那岂不是更加有效的提高开发效率和代码的可读性。下面我们来对Unity UI事件进行一个扩展,以实现这个功能。
一、UI事件数据的设计
Unity 原本也提供了UI事件的数据结构传递,但它不满足我们的需求。我们需要在不改变它原有的传递结构上加上我们的自定义数据。我们可以定义一个类,如UIEventData。这个类有我们自定义的数据结构,它也把原本的事件数据包含进来,响应事件时传递这个类就行。这个类的代码如下:
UIEventData.cs
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
namespace Simple.UI
{
public class UIEventData
{
public PointerEventData PointerData;
public object OtherData;
}
}
二、UI事件监听器的设计
Unity 原本提供了各种UI事件的监听,例如OnPointerDown、OnPointerUp等。核心思想是,我们不需要改变它们的监听逻辑,只需要在它的监听逻辑之后加入我们的自定义数据,然后向上传递事件即可。要实现这个功能,我们可以定义一个UIListener,它继承自EventTrigger,代码如下:
using System;
using UnityEngine;
using UnityEngine.EventSystems;
using System.Collections.Generic;
using UnityEngine.UI;
namespace Simple.UI
{
/// <summary>
/// UI事件监听器
/// </summary>
public class UIListener : EventTrigger
{
public UIEventData EventData { private set; get; }//我们自定义的事件数据
public Action<UIEventData> OnClickDown;//自定义上向上传递的事件
public Action<UIEventData> OnClickUp;
public Action<UIEventData> OnClick;
public Action<UIEventData> OnStartDrag;
public Action<UIEventData> OnOverDrag;
public Action<UIEventData> OnDraging;
public Action<UIEventData> OnDroping;
private bool _interactable = true;//是否可交互
//go为ui对象,如button\image等,eventData为安需要自定义的外部数据
public static UIListener Get(GameObject go, object eventData)
{
UIListener listener = go.GetComponent<UIListener>();
if (listener == null)
listener = go.AddComponent<UIListener>();//加入监听器
listener.EventData = new UIEventData();
listener.EventData.OtherData = eventData;//保存自定义数据
return listener;
}
public static UIListener Get(Transform t, object eventData)
{
return Get(t.gameObject, eventData);
}
public static UIListener Get(Component c, object eventData)
{
return Get(c.gameObject, eventData);
}
/// <summary>
/// 设置是否可以交互
/// </summary>
/// <param name="interactable"></param>
public void SetInteractable(bool interactable)
{
_interactable = interactable;
}
void OnDestroy()
{
OnClickDown = null;
OnClickUp = null;
OnClick = null;
OnStartDrag = null;
OnOverDrag = null;
OnDraging = null;
OnDroping = null;
triggers.Clear();
}
//重写事件传递逻辑
public override void OnPointerDown(PointerEventData eventData)
{
if (!_interactable)
return;
base.OnPointerDown(eventData);
EventData.PointerData = eventData;//保存unity的事件数据
OnClickDown?.Invoke(EventData);//向上传递事件
}
public override void OnPointerUp(PointerEventData eventData)
{
if(!_interactable) return;
base.OnPointerUp(eventData);
EventData.PointerData = eventData;
OnClickUp?.Invoke(EventData);
}
public override void OnPointerClick(PointerEventData eventData)
{
if (!_interactable) return;
base.OnPointerClick(eventData);
EventData.PointerData = eventData;
OnClick?.Invoke(EventData);
}
public override void OnBeginDrag(PointerEventData eventData)
{
if (!_interactable) return;
base.OnBeginDrag(eventData);
EventData.PointerData = eventData;
OnStartDrag?.Invoke(EventData);
_clickDownCount++;
}
public override void OnEndDrag(PointerEventData eventData)
{
if (!_interactable) return;
base.OnEndDrag(eventData);
EventData.PointerData = eventData;
OnOverDrag?.Invoke(EventData);
}
public override void OnDrag(PointerEventData eventData)
{
if (!_interactable) return;
base.OnDrag(eventData);
EventData.PointerData = eventData;
OnDraging?.Invoke(EventData);
}
//public override void OnDrop(PointerEventData eventData)
//{
// base.OnDrop(eventData);
// EventData.PointerData = eventData;
// OnDraging?.Invoke(EventData);
//}
}
}
三、监听器的应用
我们以一个切换游戏语言的例子来说明拓展后的事件监听器如何应用。首先我们定义一个语言枚举,如下所示:
public enum LangTypeEnum
{
none = 0,
English = 1,
ChineseSimplified = 2,
ChineseTraditional = 3,
French = 4,
German = 5,
Italian = 6,
Japanese = 7,
Dutch = 8,
Spanish = 9,
Portuguese = 10,
Hebrew = 11,
Russia = 12,
Danish = 13,
Norwegian = 14,
Finnish = 15,
Swedish = 16,
Hindi = 17,
Bengali = 18,
Turkish = 19,
Indonesian = 20,
Filipino = 21,
Thai = 22,
Malay = 23,
Arabic = 24,
Vietnamese = 25,
Ukrainian = 26,
Korean = 27,
Czech = 28,
Polish = 29,
Slovak = 30,
Slovenian = 31,
Hungarian = 32,
Romanian = 33,
Greek = 34,
Croatian = 35,
Bulgarian = 36,
}
然后我们在游戏的语言设置界面上,当点击对应的语言按钮时传入相关的语言枚举,以实现游戏语言的切换,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Simple.UI;
using System;
namespace Gamelogic
{
/// <summary>
/// 设置界面绑定
/// </summary>
public class UIStageSettingsBinder : MonoBehaviour
{
private LangTypeEnum _curLang = LangTypeEnum.English;
private void Awake()
{
LoadLangItem();
}
private void OnDestroy()
{
}
private void LoadLangItem()
{
var ems = Enum.GetNames(typeof(LangTypeEnum));
//通过枚举实例化语言按键
foreach (var e in ems)
{
var em = Enum.Parse<LangTypeEnum>(e);
if (em == LangTypeEnum.none)
continue;
var item = Pool.SpawnOut(ResConfig.Pre_UI_LangItem, true, 0);//语言按钮prefab
item.SetParent(_langItemParent);
var igo = item.ActiveTransform;
igo.localScale = Vector3.one;
igo.localPosition = Vector3.zero;
UIListener.Get(igo, em).OnClick = OnSelectedClick;//绑定事件和自定义数据
UIListener.Get(igo, em).OnStartDrag = OnStartDrag;
UIListener.Get(igo, em).OnDraging = OnDraging;
UIListener.Get(igo, em).OnOverDrag = OnOverDrag;
var binder = igo.GetComponent<UIStageLangItemBinder>();
binder.SetName(e);
binder.SetIcon($"flag_{e}");
}
}
private void OnStartDrag(UIEventData ed)
{
}
private void OnOverDrag(UIEventData ed)
{
}
private void OnDraging(UIEventData ed)
{
}
private void OnSelectedClick(UIEventData ed)
{
var lt = (LangTypeEnum)ed.OtherData;
_curLang = lt;
OnChangeClick(ed); //切换言语
}
private void OnChangeClick(UIEventData ed)
{
}
}
}
四、运行效果
界面内的每个按钮对应着一个语言枚举,只要我们按下按钮,就可以把枚举值传递到相关业务层,对编码的友好度和效率的提升还是比较明显的。