Unity UGUI GraphicRaycaster.Raycast详解
一、源码/// summary/// 对当前 Canvas 上的所有可交互 UI 图形执行射线检测判断是否被点击或触碰。/// /summary/// param nameeventData指针事件的数据包含鼠标位置、触摸点等/param/// param nameresultAppendList用于存储命中的 UI 元素结果列表/parampublicoverridevoidRaycast(PointerEventDataeventData,ListRaycastResultresultAppendList){// 如果 Canvas 不存在则无法进行任何 UI 检测直接返回if(canvasnull)return;// 获取当前 Canvas 中所有可以参与射线检测的 UI 元素如 Image、Text 等varcanvasGraphicsGraphicRegistry.GetRaycastableGraphicsForCanvas(canvas);// 如果没有图形元素或数量为 0说明没有需要检测的 UI直接返回if(canvasGraphicsnull||canvasGraphics.Count0)return;intdisplayIndex;varcurrentEventCameraeventCamera;// 缓存摄像机引用避免多次调用 Camera.main// 根据 Canvas 渲染模式决定使用哪个显示设备索引多显示器支持if(canvas.renderModeRenderMode.ScreenSpaceOverlay||currentEventCameranull)displayIndexcanvas.targetDisplay;// Overlay 模式下直接使用 Canvas 自身设置的显示器elsedisplayIndexcurrentEventCamera.targetDisplay;// 否则使用摄像机所指向的显示器// 获取鼠标在屏幕上的相对坐标考虑多显示器情况vareventPositionMultipleDisplayUtilities.RelativeMouseAtScaled(eventData.position);// 如果返回值不是 Vector3.zero表示平台支持多显示器系统if(eventPosition!Vector3.zero){inteventDisplayIndex(int)eventPosition.z;// 如果点击发生在其他显示器上则忽略该事件防止跨屏操作if(eventDisplayIndex!displayIndex)return;}else{// 平台不支持多显示器时使用原始事件位置eventPositioneventData.position;#ifUNITY_EDITOR// 在 Unity Editor 中如果 GameView 当前目标显示器与 DisplayIndex 不一致也忽略事件if(Display.activeEditorGameViewTarget!displayIndex)return;// 补充 z 值为当前 GameView 目标显示器编号eventPosition.zDisplay.activeEditorGameViewTarget;#endif}// 将事件位置转换为视口空间坐标范围 [0,1]Vector2pos;if(currentEventCameranull){// 如果是 ScreenSpaceOverlay 模式或没有摄像机使用屏幕分辨率计算比例floatwScreen.width;floathScreen.height;// 如果是其他显示器使用对应显示器的分辨率if(displayIndex0displayIndexDisplay.displays.Length){wDisplay.displays[displayIndex].systemWidth;hDisplay.displays[displayIndex].systemHeight;}posnewVector2(eventPosition.x/w,eventPosition.y/h);}else{// 使用摄像机将屏幕坐标转换为视口坐标poscurrentEventCamera.ScreenToViewportPoint(eventPosition);}// 如果坐标超出摄像机视口范围直接返回无效输入if(pos.x0f||pos.x1f||pos.y0f||pos.y1f)return;// 初始化阻挡距离为最大值表示默认没有阻挡物挡住 UIfloathitDistancefloat.MaxValue;RayraynewRay();// 如果有摄像机生成从摄像机出发到鼠标位置的射线if(currentEventCamera!null)raycurrentEventCamera.ScreenPointToRay(eventPosition);// 如果不是 Overlay 模式并且启用了阻挡对象检测即检查是否有 2D/3D 物体遮挡 UIif(canvas.renderMode!RenderMode.ScreenSpaceOverlayblockingObjects!BlockingObjects.None){// 设置一个默认的射线检测距离100单位用于限制检测深度floatdistanceToClipPlane100.0f;// 如果有摄像机根据摄像机参数动态计算射线长度if(currentEventCamera!null){floatprojectionDirectionray.direction.z;// 避免除以零处理正交投影等情况distanceToClipPlaneMathf.Approximately(0.0f,projectionDirection)?Mathf.Infinity:Mathf.Abs((currentEventCamera.farClipPlane-currentEventCamera.nearClipPlane)/projectionDirection);}#ifPACKAGE_PHYSICS// 如果启用了 3D 阻挡检测if(blockingObjectsBlockingObjects.ThreeD||blockingObjectsBlockingObjects.All){if(ReflectionMethodsCache.Singleton.raycast3D!null){// 执行 3D 射线检测获取所有命中物体varhitsReflectionMethodsCache.Singleton.raycast3DAll(ray,distanceToClipPlane,(int)m_BlockingMask);if(hits.Length0)hitDistancehits[0].distance;// 记录最近的阻挡距离}}#endif#ifPACKAGE_PHYSICS2D// 如果启用了 2D 阻挡检测if(blockingObjectsBlockingObjects.TwoD||blockingObjectsBlockingObjects.All){if(ReflectionMethodsCache.Singleton.raycast2D!null){// 执行 2D 射线检测获取所有命中物体varhitsReflectionMethodsCache.Singleton.getRayIntersectionAll(ray,distanceToClipPlane,(int)m_BlockingMask);if(hits.Length0)hitDistancehits[0].distance;// 记录最近的阻挡距离}}#endif}// 清空临时结果列表准备存储本次射线检测的结果m_RaycastResults.Clear();// 执行对 UI 元素的实际射线检测Raycast(canvas,currentEventCamera,eventPosition,canvasGraphics,m_RaycastResults);inttotalCountm_RaycastResults.Count;// 遍历所有命中候选对象筛选出最终有效的 UI 结果for(varindex0;indextotalCount;index){vargom_RaycastResults[index].gameObject;boolappendGraphictrue;// 如果启用了反向剔除ignoreReversedGraphics检查 UI 是否朝向摄像机if(ignoreReversedGraphics){if(currentEventCameranull){// 没有摄像机时默认 UI 是正向的vardirgo.transform.rotation*Vector3.forward;appendGraphicVector3.Dot(Vector3.forward,dir)0;}else{// 有摄像机时比较 UI 正面和摄像机方向varcameraForwardcurrentEventCamera.transform.rotation*Vector3.forward*currentEventCamera.nearClipPlane;appendGraphicVector3.Dot(go.transform.position-currentEventCamera.transform.position-cameraForward,go.transform.forward)0;}}// 如果需要加入结果if(appendGraphic){floatdistance0;Transformtransgo.transform;Vector3transForwardtrans.forward;// 如果是 Overlay 模式或没有摄像机距离为 0if(currentEventCameranull||canvas.renderModeRenderMode.ScreenSpaceOverlay){distance0;}else{// 使用几何算法计算射线与 UI 平面的交点距离distance(Vector3.Dot(transForward,trans.position-ray.origin)/Vector3.Dot(transForward,ray.direction));// 如果物体在摄像机后方跳过if(distance0)continue;}// 如果 UI 被 3D/2D 物体挡住跳过if(distancehitDistance)continue;// 构建最终的 RaycastResult 并添加进结果列表varcastResultnewRaycastResult{gameObjectgo,modulethis,distancedistance,screenPositioneventPosition,displayIndexdisplayIndex,indexresultAppendList.Count,depthm_RaycastResults[index].depth,sortingLayercanvas.sortingLayerID,sortingOrdercanvas.sortingOrder,worldPositionray.originray.direction*distance,worldNormal-transForward};resultAppendList.Add(castResult);}}}这段代码是 Unity UGUI 中GraphicRaycaster的核心方法之一Raycast()用于在 2D/3D 场景中检测鼠标点击事件是否命中 UI 元素。二、 目标现在只关注与 2D 点击交互相关的关键逻辑部分并忽略以下非关键内容多显示器支持3D 射线检测blockingObjects摄像机视口转换反向面剔除ignoreReversedGraphics三、 整体流程简述获取当前 Canvas 上所有可交互的 UI 图形Graphic获取鼠标屏幕坐标遍历这些图形判断是否被鼠标“点中”如果命中就添加到resultAppendList中供后续事件系统使用四、精简后关键代码解析1. 获取当前 Canvas 上所有可被射线检测的 UI 元素varcanvasGraphicsGraphicRegistry.GetRaycastableGraphicsForCanvas(canvas);canvasGraphics是一个 List里面包含所有可以接收点击事件的 UI 组件如 Image、Text 等。这些组件必须满足两个条件raycastTarget true不透明度大于 0color.a 02. 获取鼠标位置简化为屏幕坐标eventPositioneventData.position;eventPosition是鼠标的屏幕坐标以像素为单位3. 执行实际的 Raycast 检测/// summary/// 在屏幕上进行射线检测并收集所有在该点下方的 GraphicUI 元素。/// 用于事件系统如点击、拖拽等判断哪些 UI 元素被交互到。/// /summary[NonSerialized]staticreadonlyListGraphics_SortedGraphicsnewListGraphic();privatestaticvoidRaycast(Canvascanvas,// 当前要检测的 CanvasCameraeventCamera,// 拍摄这个 Canvas 的摄像机可以是 UICamera 或世界摄像机Vector2pointerPosition,// 鼠标或触控点在屏幕上的坐标IListGraphicfoundGraphics,// 所有在这个 Canvas 上注册的可交互 Graphic 列表ListGraphicresults// 最终筛选出的、在该点下的 Graphic 结果列表){// 获取当前需要检测的 UI 元素总数inttotalCountfoundGraphics.Count;// 遍历所有在这个 Canvas 上注册的 Graphic 元素for(inti0;itotalCount;i){GraphicgraphicfoundGraphics[i];// 如果// - 不允许射线检测// - 已经被裁剪未显示// - depth -1表示尚未被 Canvas 渲染系统处理过即还未绘制// 就跳过这个元素if(!graphic.raycastTarget||graphic.canvasRenderer.cull||graphic.depth-1)continue;// 检查指针位置是否在该 UI 元素的矩形区域内考虑 paddingif(!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform,pointerPosition,eventCamera,graphic.raycastPadding))continue;// 如果摄像机不为 null并且该 UI 元素的位置在摄像机远裁剪面之外则跳过if(eventCamera!nulleventCamera.WorldToScreenPoint(graphic.rectTransform.position).zeventCamera.farClipPlane)continue;// 进一步使用自定义的 Raycast 方法例如 Image、Text 等子类可能重写做更精确的检测if(graphic.Raycast(pointerPosition,eventCamera)){// 符合条件的 UI 元素加入临时结果列表s_SortedGraphics.Add(graphic);}}// 对符合条件的 UI 元素按深度depth从高到低排序// - 深度越大越靠上覆盖在上面优先响应事件s_SortedGraphics.Sort((g1,g2)g2.depth.CompareTo(g1.depth));// 把最终排序后的结果添加到输出列表中totalCounts_SortedGraphics.Count;for(inti0;itotalCount;i)results.Add(s_SortedGraphics[i]);// 清空临时列表以便下次使用s_SortedGraphics.Clear();}术语解释raycastTarget控制该 UI 元素是否参与射线检测是否能接收点击事件canvasRenderer.cull表示该 UI 元素是否被裁剪不在可视区域不会渲染depthUI 元素在 Canvas 下的层级深度决定谁在最上层raycastPadding可选的额外检测范围扩展用于提高命中精度s_SortedGraphics临时缓存满足条件的 UI 元素并根据深度排序RectangleContainsScreenPoint判断鼠标是否点击在一个 UI 元素上4. 对结果进行筛选和排序for(varindex0;indextotalCount;index){vargom_RaycastResults[index].gameObject;// 如果启用了 ignoreReversedGraphics排除背对摄像机的物体这里省略// 检查深度distance如果比阻挡层近才加入结果if(distancehitDistance)continue;// 构造最终的 RaycastResult 并添加进列表varcastResultnewRaycastResult{gameObjectgo,modulethis,distancedistance,screenPositioneventPosition,displayIndexdisplayIndex,indexresultAppendList.Count,depthm_RaycastResults[index].depth,sortingLayercanvas.sortingLayerID,sortingOrdercanvas.sortingOrder};resultAppendList.Add(castResult);}五、 关键逻辑总结步骤描述1. 获取 UI 列表从GraphicRegistry获取当前 Canvas 上所有可点击的 UI 元素2. 获取鼠标位置通过eventData.position得到鼠标在屏幕上的坐标3. 遍历 UI 元素对每个 UI 元素执行点击检测矩形区域 alpha 值4. 排序并返回结果按照深度depth、sortingLayer 排序后返回命中的 UI 元素六、哪些元素会被点击只有满足以下条件的 UI 元素才会参与点击检测条件说明raycastTarget true在 Inspector 中勾选了 “Raycast Target”color.a 0透明度不为 0否则不会响应点击CanvasRenderer存在UI 元素必须有 CanvasRenderer 组件未被遮挡如果前面有更靠前的 UI 元素后面的可能不会被检测到示例如何让某个 UI 元素不能被点击把它的Image.raycastTarget false或者设置color.a 0完全透明或者移除CanvasRenderer组件但这样也不会渲染七、如何扩展自定义点击行为可以继承UI.Graphic并重写Raycast()方法来自定义点击范围比如圆形、多边形等publicclassCircleGraphic:Image{publicoverrideboolRaycast(Vector2sp,CameraeventCamera){// 自定义圆形点击检测returnRectTransformUtility.RectangleContainsScreenPoint(rectTransform,sp,eventCamera)IsInCircle(sp,rectTransform.rect.center,rectTransform.rect.width/2f);}}八、 总结2D UI 点击机制关键点内容说明点击检测方式矩形检测RectTransform 包围盒影响因素raycastTarget,alpha,Canvas.renderMode点击顺序按照depth和sortingOrder排序事件分发由EventSystem根据命中对象调用IPointerDownHandler等接口Unity UGUI (Unity’s User Interface) 事件系统是一个复杂但灵活的机制用于处理用户交互如点击、拖动等。UGUI 事件系统工作流程输入检测Unity首先从输入设备鼠标、触摸屏、键盘等接收输入。输入模块例如StandaloneInputModule或TouchInputModule监听这些输入。射线投射Raycast当接收到输入后事件系统会通过当前激活的GraphicRaycaster组件对UI进行射线投射。这个过程确定了哪个UI元素位于用户的输入位置比如鼠标点击的位置。生成指针事件数据根据射线投射的结果创建相应的指针事件数据PointerEventData包括位置信息、点击状态等。事件传播事件系统根据指针事件数据触发相应的事件。这包括但不限于以下几种类型的事件IPointerEnterHandler,IPointerExitHandler,IPointerDownHandler,IPointerUpHandler,IClickHandler等。事件按照一定的顺序在UI层次结构中传播通常是从根到叶子节点即从父级到子级。事件处理如果某个UI元素实现了对应的接口例如实现了IPointerClickHandler接口以处理点击事件那么该元素就会执行相应的事件处理逻辑。开发者可以通过实现这些接口来为UI元素添加自定义的交互行为。回调和响应在事件处理过程中可能会调用预设的回调函数或者触发其他游戏逻辑。这些回调可以用来更新UI状态、播放动画、修改数据模型等。重复上述过程随着用户持续与界面互动上述过程不断重复以实时响应新的输入。

相关新闻

计算机网络-3

计算机网络-3

1. DNS解析流程windows host文件是否配置域名对应的ip查询本地DNS缓存是否有这个域名对应的ip询问本地DNS(网卡配置的)是否知晓域名对应的ip本地DNS访问根域名解析服务器,但是根DNS只有顶级域名的记录,根告诉我们.cn顶级域名的DNS…

2026/7/2 22:26:16 阅读更多 →
windows 虚拟环境编译flash_attn

windows 虚拟环境编译flash_attn

要安装Bagel试试效果,需要安装flash_attn,windows上flash_attn编译非常慢,都是用网络上别人编译好的.whl文件,Bagel要求的flash_attn2.5.8 torch2.5.1,但网上找不到要求的版本就很难受,其实不用完全符合要求…

2026/7/3 10:34:10 阅读更多 →
如何使用PowerTOP交互式模式:3分钟掌握Linux功耗实时监控

如何使用PowerTOP交互式模式:3分钟掌握Linux功耗实时监控

如何使用PowerTOP交互式模式:3分钟掌握Linux功耗实时监控 【免费下载链接】powertop The Linux PowerTOP tool -- please post patches to the mailing list instead of using github pull requests 项目地址: https://gitcode.com/gh_mirrors/po/powertop P…

2026/5/17 12:39:55 阅读更多 →

最新新闻

猫抓Cat-Catch:重塑浏览器资源捕获体验的开源革命

猫抓Cat-Catch:重塑浏览器资源捕获体验的开源革命

猫抓Cat-Catch:重塑浏览器资源捕获体验的开源革命 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 在数字内容爆炸式增长的时代&#xf…

2026/7/3 21:39:45 阅读更多 →
WinDiskWriter:macOS上制作Windows启动U盘的智能解决方案

WinDiskWriter:macOS上制作Windows启动U盘的智能解决方案

WinDiskWriter:macOS上制作Windows启动U盘的智能解决方案 【免费下载链接】windiskwriter 🖥 Windows Bootable USB creator for macOS. 🛠 Patches Windows 11 to bypass TPM and Secure Boot requirements. 👾 UEFI & Legac…

2026/7/3 21:37:44 阅读更多 →
UI自动化测试:基于Figma与Playwright实现像素级颜色一致性验证

UI自动化测试:基于Figma与Playwright实现像素级颜色一致性验证

1. 项目概述:当UI设计稿遇上自动化测试在软件开发的漫长周期里,UI(用户界面)的一致性一直是前端工程师和测试工程师的“心头大患”。设计师在Figma或Sketch里精心调制的渐变色、品牌色、状态色,到了开发手里&#xff0…

2026/7/3 21:35:43 阅读更多 →
深圳本地人常去火锅实测|理性避坑选型指南

深圳本地人常去火锅实测|理性避坑选型指南

一、引言:深圳火锅消费乱象与选型痛点作为粤港澳餐饮消费高地,深圳火锅赛道门店超3200家,川渝、潮汕、北派派系扎堆,但当下消费痛点愈发突出:一是菜品同质化严重,多数门店锅底配方趋同,依靠营销…

2026/7/3 21:33:43 阅读更多 →
从0到1掌握openeuler/cpds-agent:容器数据采集入门到精通

从0到1掌握openeuler/cpds-agent:容器数据采集入门到精通

从0到1掌握openeuler/cpds-agent:容器数据采集入门到精通 【免费下载链接】cpds-agent Collect Container info for Container Problem Detect System. 项目地址: https://gitcode.com/openeuler/cpds-agent 前往项目官网免费下载:https://ar.ope…

2026/7/3 21:33:43 阅读更多 →
AI审查模型偏见导致金融级代码逃逸?——基于127万行真实PR数据的偏差检测与校准白皮书(限首批500份)

AI审查模型偏见导致金融级代码逃逸?——基于127万行真实PR数据的偏差检测与校准白皮书(限首批500份)

更多请点击: https://codechina.net 第一章:AI审查模型偏见导致金融级代码逃逸?——基于127万行真实PR数据的偏差检测与校准白皮书(限首批500份) 金融领域代码审查正面临隐性偏见引发的系统性风险:当AI审查…

2026/7/3 21:31:43 阅读更多 →

日新闻

Nginx防御TLS重协商攻击实战:从原理到配置与监控

Nginx防御TLS重协商攻击实战:从原理到配置与监控

1. 项目概述:为什么TLS重协商攻击至今仍需警惕十多年前的CVE-2011-1473,一个关于TLS/SSL协议重协商机制的漏洞,现在提起来还有必要吗?很多运维和开发朋友可能会觉得,这都老掉牙了,现代服务器和客户端不都默…

2026/7/3 0:03:59 阅读更多 →
华为防火墙双通道远程管理实战:Web与SSH配置详解

华为防火墙双通道远程管理实战:Web与SSH配置详解

1. 项目概述:为什么需要双通道远程管理防火墙?在任何一个稍具规模的企业网络里,防火墙都是那个默默守护在边界的关键角色。作为网络工程师,我们不可能每次都跑到机房,插上console线去配置它。远程管理能力,…

2026/7/3 0:03:59 阅读更多 →
AD74413R与PIC18F65K40的高精度工业数据采集方案

AD74413R与PIC18F65K40的高精度工业数据采集方案

1. 项目概述:AD74413R与PIC18F65K40的协同工作在工业自动化和精密测量领域,同时实现高精度模数转换(ADC)和数模转换(DAC)功能是许多复杂系统的核心需求。AD74413R作为一款四通道可配置模拟输入/输出器件,与PIC18F65K40微控制器的组合&#xf…

2026/7/3 0:05:59 阅读更多 →

周新闻

月新闻