Canvas设置为WorldSpace时,UI事件中鼠标位置信息偏移的问题
需求描述:在Canvas下使用RenderTexture渲染三维场景,同时需要三维场景中需要响应鼠标事件点击,移入,移出等
解决方案:重写RawImage作为RenderTexture载体,实现鼠标事件接口
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class RawImageEx : RawImage,IPointerEnterHandler, IPointerClickHandler, IPointerExitHandler
{
public void OnPointerClick(PointerEventData eventData)
{
RenderTextureEventTransfor.Instance.BroadCast(this.name, RenderTextureEventTransfor.EventType.Click,this.rectTransform, eventData);
}
public void OnPointerEnter(PointerEventData eventData)
{
RenderTextureEventTransfor.Instance.BroadCast(this.name, RenderTextureEventTransfor.EventType.Enter, this.rectTransform, eventData);
}
public void OnPointerExit(PointerEventData eventData)
{
RenderTextureEventTransfor.Instance.BroadCast(this.name, RenderTextureEventTransfor.EventType.Exit, this.rectTransform, eventData);
}
}
上面代码中的RenderTextureEventTransfor是个单例,用来管理事件,里面只有注册,广播两个方法
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class RenderTextureEventTransfor : SingletonMono
{
public enum EventType
{
Click,
Enter,
Exit
}
private Dictionary<string, Action<RectTransform,PointerEventData>> rtClick = new Dictionary<string, Action<RectTransform,PointerEventData>>();
private Dictionary<string, Action<RectTransform,PointerEventData>> rtEnter = new Dictionary<string, Action<RectTransform,PointerEventData>>();
private Dictionary<string, Action<RectTransform,PointerEventData>> rtExit = new Dictionary<string, Action<RectTransform,PointerEventData>>();
public void Register(string rtName, EventType eventType, Action<RectTransform, PointerEventData> callBack)
{
switch (eventType)
{
case EventType.Click:
if (!rtClick.ContainsKey(rtName))
{
rtClick.Add(rtName, callBack);
}
else
{
rtClick[rtName] += callBack;
}
break;
case EventType.Enter:
if (!rtEnter.ContainsKey(rtName))
{
rtEnter.Add(rtName, callBack);
}
else
{
rtEnter[rtName] += callBack;
}
break;
case EventType.Exit:
if (!rtExit.ContainsKey(rtName))
{
rtExit.Add(rtName, callBack);
}
else
{
rtExit[rtName] += callBack;
}
break;
default:
break;
}
}
public void BroadCast(string rtName, EventType eventType,RectTransform rtRect, PointerEventData data)
{
switch (eventType)
{
case EventType.Click:
foreach (var item in rtClick)
{
if (item.Key == rtName)
item.Value?.Invoke(rtRect,data);
}
break;
case EventType.Enter:
foreach (var item in rtEnter)
{
if (item.Key == rtName)
item.Value?.Invoke(rtRect,data);
}
break;
case EventType.Exit:
foreach (var item in rtExit)
{
if (item.Key == rtName)
item.Value?.Invoke(rtRect,data);
}
break;
default:
break;
}
}
}
需要接收触发事件的地方先进行注册,然后再回调中处理逻辑
void Awake()
{
RenderTextureEventTransfor.Instance.Register(rtName, RenderTextureEventTransfor.EventType.Click, OnTriggerMouseClick);
RenderTextureEventTransfor.Instance.Register(rtName, RenderTextureEventTransfor.EventType.Exit, OnTriggerMouseExit);
}
最后在回调中根据传过来的点击事件信息,对鼠标信息做坐标系转换:
private void OnTriggerMouseClick(RectTransform rtRect, PointerEventData data)
{
if(RectTransformUtility.ScreenPointToLocalPointInRectangle(rtRect, data.position, Camera.main,out Vector2 pos))
{
//此处需要根据rtRect的锚点来做对应的偏移
pos += (rtRect.rect.size / 2);
var rate = pos / (rtRect.rect.size);
Ray ray = cam.ViewportPointToRay(rate);
RaycastHit raycastHit;
if (Physics.Raycast(ray, out raycastHit))
{
Debug.DrawLine(ray.origin, raycastHit.point, Color.cyan);
GameObject go = raycastHit.transform.gameObject;
}
}
}
注意:使用RectTransformUtility.ScreenPointToLocalPointInRectangle(rtRect, data.position, Camera.main,out Vector2 pos)接口做屏幕到rtRect的坐标系转换,其中相机为Canvas的渲染相机,得到的pos是相对rtRect的左下角的位置,如果rtRect的锚点在中心,需要加上其大小的一半,以此类推;使用RenderTexture的渲染相机根据得到的位置来做射线检测:Ray ray = cam.ViewportPointToRay(rate)。