需求描述:在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)。