unity实现点击rawimage,确定对应的世界坐标点
前提:存在多个摄像机和rawimage
代码:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FluffyUnderware.DevTools.Extensions;
using FluffyUnderware.DevToolsEditor;
using ISCAS.Common;
using Unidux.Util;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using Zenject;public class FollowWindow : MonoBehaviour, IPointerClickHandler
{[Header("对应的跟随摄像机obj")]public GameObject followCameraObj;[Header("ui圆点")]public Image dot;private Camera _followCam;//对应的跟随摄像机private RawImage _rawimage; //对应的rawimagepublic LayerMask hitLayers; // 可检测的物体层级//测试public Material highMaterial;void Start(){hitLayers = LayerMask.NameToLayer("model");_rawimage = this.GetComponentInChildren<RawImage>();_followCam = followCameraObj.GetComponent<Camera>();}// 实现点击事件接口public void OnPointerClick(PointerEventData eventData){// 获取点击的物体GameObject hitObject = GetObjectAtClick(eventData);if (hitObject != null){Debug.Log($"点击了物体: {hitObject.name}");// 触发自定义事件OnObjectClicked(hitObject);}}/// <summary>/// 获取点击位置对应的3D物体/// </summary>public GameObject GetObjectAtClick(PointerEventData eventData){// 1. 获取RawImage组件if (_rawimage == null || _followCam == null) return null;// 2. 获取点击位置在RawImage上的本地坐标Vector2 localPoint;RectTransformUtility.ScreenPointToLocalPointInRectangle(_rawimage.rectTransform,eventData.position,eventData.pressEventCamera,out localPoint);// 调试日志:本地坐标Debug.Log($"原始本地坐标: {localPoint}");dot.rectTransform.localPosition = localPoint;// 3. 转换为视口坐标Vector2 viewportPoint = ConvertToViewportPoint(localPoint,_rawimage.rectTransform);Debug.Log($"视口坐标: {viewportPoint}");// 4. 发射射线检测物体return RaycastFromViewport(viewportPoint);}/// <summary>/// 将RawImage本地坐标转换为视口坐标(0-1),就是获得localPoint相对于整个rawimage的比例/// </summary>private Vector2 ConvertToViewportPoint(Vector2 localPoint, RectTransform rectTransform){//获取rawimage的长宽float imageWidth = rectTransform.rect.width;float imageHeight = rectTransform.rect.height;//获取rawimage的左下角点,适用于所有的pivot情况float localPositionX = rectTransform.localPosition.x - (rectTransform.rect.width * rectTransform.pivot.x);float localPositionY = rectTransform.localPosition.y - (rectTransform.rect.height * rectTransform.pivot.y);//获取在预览映射相机viewport内的坐标(坐标比例);float p_x = (localPoint.x - localPositionX) / imageWidth;float p_y = (localPoint.y - localPositionY) / imageHeight;return new Vector2(p_x, p_y);}/// <summary>/// 从视口坐标发射射线检测物体/// </summary>private GameObject RaycastFromViewport(Vector2 viewportPoint){// 创建射线(根据视口坐标创建射线)Ray ray = _followCam.ViewportPointToRay(viewportPoint);// 可视化射线(在场景视图中查看)ray.origin是射线起点,ray.direction是射线方向Debug.DrawRay(ray.origin, ray.direction * 100000, Color.red, 2);// 执行射线检测RaycastHit hit;if (Physics.Raycast(ray, out hit, Mathf.Infinity, hitLayers)){return hit.collider.gameObject;}return null;}/// <summary>/// 物体点击事件处理/// </summary>private void OnObjectClicked(GameObject clickedObject){// 示例:高亮被点击的物体HighlightObject(clickedObject);// 示例:在目标窗口显示选中物体// DisplaySelectedTarget(clickedObject);}// 高亮物体方法private async void HighlightObject(GameObject obj){// 实现高亮逻辑if (highMaterial == null)return;// 获取渲染器MeshRenderer renderer = obj.GetComponent<MeshRenderer>();if (renderer == null) return;// 保存原始材质Material[] originalMaterials = renderer.materials;// 创建新材质列表(添加高亮材质)List<Material> newMaterials = new List<Material>(originalMaterials);newMaterials.Add(highMaterial);// 应用新材质renderer.materials = newMaterials.ToArray();try{// 使用Unity协程替代Task.Delay(确保在主线程)await Task.Delay(2000).ConfigureAwait(true); // 确保在主线程继续// 恢复原始材质renderer.materials = originalMaterials;}catch (Exception ex){Debug.LogError($"高亮恢复失败: {ex.Message}");// 确保恢复材质if (renderer != null)renderer.materials = originalMaterials;}}
}
整体流程:根据屏幕上的鼠标坐标->得到rawimage上的局部坐标(直接使用api:RectTransformUtility.ScreenPointToLocalPointInRectangle)->得到摄像机的视口坐标(就是比例换算,得到0-1的比例)->摄像机发射射线,碰撞到指定层级的物体