简介这份Unity VR工程资源聚焦于“用眼睛注视选择物体”的交互实现面向具备一定Unity基础、正在开发VR应用的开发者与学习者。工程基于Unity 2019.4.9f1与VS2019构建核心思路是通过在MainCamera上挂载WatchController与HightlightingRenderer脚本配合物体上的WatchEvent事件注册模拟类似UGUI Button的注视触发机制可用于VR场景中的菜单选择、物体拾取等交互场景。资源包共283个文件约4.7MB包含41个C#脚本、19个Unity场景、10个预制体、6个Shader、17个材质及若干动画、贴图与配置文件脚本与场景可直接导入测试便于快速理解注视交互的完整链路。目前已有214人学习下载。借助该工程读者能够掌握VR注视交互的脚本组织方式、高亮渲染与事件回调的配合逻辑并可在自带Test场景中验证效果适合作为VR交互开发的参考范例或项目起步模板。1. 用注视点当输入Unity VR 眼动选物到底怎么落地做 VR 交互时手柄射线选物几乎是默认方案但真到医疗培训、展厅导览、无障碍交互这些场景里用户双手往往被占用或者压根不想抬手。这时候「眼睛看哪就选哪」就成了刚需。我手上这份 Unity VR 眼动注视选物工程基于 Unity 2019.4.9f1 VS2019核心就三个脚本WatchController、HightlightingRenderer、WatchEvent配合一个 Test 场景可以直接跑。它解决的不是「怎么接眼动仪硬件」这种底层问题而是把注视点转成类似 UGUI Button 的事件流——你盯着物体看物体高亮停留够久就触发点击。适合已经会 Unity 基础操作、想快速给 VR 项目加一套眼动选物逻辑的开发者也适合做 Pico4 开发 Unity 时想省掉自己造轮子的团队。下面我按「资源结构 → 脚本挂载 → 参数调优 → 避坑」的顺序拆一遍。2. 工程结构与三个核心脚本先看清文件再动手2.1 从资源清单反推工程骨架这份资源里除了脚本还带了一串 ProjectSettings 下的配置文件ProjectSettings.asset、InputManager.asset、EditorBuildSettings.asset、QualitySettings.asset、GraphicsSettings.asset、NavMeshAreas.asset、Physics2DSettings.asset、TagManager.asset。另外还有 WatchPoint.anim 和 Cool.anim 两个动画文件。这说明作者不是只丢几个 .cs 文件而是把整个工程的输入、质量、图形、标签配置都打包了。你导入后如果发现自己的项目里 Input 轴不对、Tag 缺失、Quality 等级差异导致高亮效果不一致大概率就是这些配置没同步。常见做法是新建一个空工程把资源包里的 Assets 文件夹和 ProjectSettings 文件夹一起覆盖进去而不是只拖脚本。因为 WatchController 很可能依赖自定义 Input 轴比如眼动停留的确认键HightlightingRenderer 可能依赖特定的 Tag 或 Layer 来筛选可注视物体。只导脚本不导配置运行时报 NullReference 或轴不存在的血泪经验我见过太多次。2.2 WatchController注视射线的发射器WatchController.cs 挂在 MainCamera 上职责是每帧从相机中心发一条射线检测前方有没有带 WatchEvent 的物体。它的核心逻辑通常长这样// WatchController.cs 核心片段示意 using UnityEngine; public class WatchController : MonoBehaviour { public float gazeTime 1.5f; // 注视触发所需停留时间 public float maxDistance 20f; // 射线最大检测距离 public LayerMask targetLayer; // 只检测可注视层减少开销 private WatchEvent currentTarget; // 当前注视的物体事件 private float timer 0f; void Update() { Ray ray new Ray(transform.position, transform.forward); RaycastHit hit; if (Physics.Raycast(ray, out hit, maxDistance, targetLayer)) { WatchEvent evt hit.collider.GetComponentWatchEvent(); if (evt ! currentTarget) { // 注视目标切换重置计时并通知旧目标取消高亮 if (currentTarget ! null) currentTarget.OnGazeExit(); currentTarget evt; timer 0f; if (currentTarget ! null) currentTarget.OnGazeEnter(); } else if (currentTarget ! null) { timer Time.deltaTime; currentTarget.OnGazeStay(timer / gazeTime); // 传进度供高亮或进度条使用 if (timer gazeTime) { currentTarget.OnGazeClick(); // 触发注册的事件 timer 0f; // 防止连续触发可改为冷却 } } } else { if (currentTarget ! null) currentTarget.OnGazeExit(); currentTarget null; timer 0f; } } }逻辑说明射线从相机正前方发出命中后取 WatchEvent 组件。目标切换时先调 OnGazeExit 清掉旧高亮再调 OnGazeEnter 点亮新目标。停留时间累加达到 gazeTime 就调 OnGazeClick。参数 gazeTime 控制「看多久算点击」maxDistance 控制交互距离targetLayer 是性能关键——不设层的话射线会打到地板、墙壁、UI 上导致注视乱跳。2.3 HightlightingRenderer让被注视物体「亮起来」HightlightingRenderer.cs 同样挂在 MainCamera 上它通常配合一个高亮材质或后处理对当前注视物体做描边或变色。常见实现是维护一个 Renderer 列表在 OnGazeEnter 时替换材质OnGazeExit 时还原。如果你发现物体高亮后材质变成紫红色那是 shader 不兼容或材质丢失不是脚本逻辑问题——Unity 材质变成紫红色这个热搜词背后就是这类情况。2.4 WatchEvent像 UGUI Button 一样注册事件WatchEvent.cs 挂在需要被注视的物体上对外暴露 OnGazeEnter、OnGazeStay、OnGazeExit、OnGazeClick 四个回调。用法和 Button 的 onClick 几乎一致在 Inspector 里拖一个物体选一个方法注视触发时就调过去。这样业务逻辑和注视检测解耦换场景、换物体都不用改 WatchController。3. 从零跑通 Test 场景挂载、注册、调参三步走3.1 导入工程并检查版本兼容摘要里写的是 Unity 2019.4.9f1这是 LTS 版本。如果你用 Unity 2021 或 Unity 6 打开大概率能跑但要注意两点一是 InputManager.asset 里的轴定义在新版本可能被 Input System 包覆盖二是 GraphicsSettings 里的渲染管线设置如果和你当前项目不一致高亮效果会异常。我一般会先备份自己的 ProjectSettings再用资源里的覆盖然后逐个对比差异。导入步骤# 1. 关闭 Unity 编辑器 # 2. 备份你当前工程的 ProjectSettings 文件夹 # 3. 将资源包中的 Assets 和 ProjectSettings 复制到工程根目录覆盖同名文件 # 4. 重新打开 Unity等待编译完成 # 5. 打开 Test 场景确认 Console 无报错如果 Console 报「Input Axis xxx is not set up」就去 Edit → Project Settings → Input Manager 里对照资源里的配置补上。如果报脚本编译错误先看 VS2019 里有没有引用缺失Unity 2019 对应的 .NET 版本是 4.x别用太新的 C# 语法。3.2 新建场景并挂载 WatchController 与 HightlightingRendererTest 场景能跑通后建议自己新建一个场景练手。在 Hierarchy 里找到 MainCameraAdd Component 搜 WatchController把需要注视的物体拖到它的目标列表或依赖字段里。再 Add Component 搜 HightlightingRenderer同样按 Inspector 提示拖入高亮材质或渲染器引用。这里有个细节WatchController 的 targetLayer 要设成你放置可注视物体的层。比如新建一个层叫 GazeTarget把所有可注视物体设成这层然后 WatchController 的 LayerMask 只勾 GazeTarget。不这么做的话射线会打到场景里的地板、墙壁、甚至 UI 上导致注视目标频繁切换体验像「玄学」一样飘。3.3 给物体加 WatchEvent 并注册事件选中一个可注视物体Add Component 搜 WatchEvent。然后在 Inspector 里找到它暴露的 UnityEvent 字段点加号拖入一个接收对象比如一个管理脚本所在的空物体选择要调用的方法。比如你想让物体被注视时播放动画就把 Cool.anim 或 WatchPoint.anim 拖到 Animator 上然后在 WatchEvent 的 OnGazeClick 里调 Animator.Play。注册事件的代码方式如下// 在某个管理脚本里动态注册注视事件 using UnityEngine; public class GazeDemo : MonoBehaviour { public WatchEvent targetEvent; // 拖入带 WatchEvent 的物体 public Animator targetAnimator; // 拖入该物体的 Animator void Start() { if (targetEvent ! null) { // 类似 Button.onClick.AddListener但这里是自定义事件 targetEvent.OnGazeClick.AddListener(() { Debug.Log(注视点击触发); if (targetAnimator ! null) targetAnimator.Play(Cool); }); } } }逻辑说明OnGazeClick 是一个 UnityEvent支持 Inspector 拖拽和代码 AddListener 两种注册方式。参数 targetEvent 是目标物体上的 WatchEvent 组件targetAnimator 是同一物体上的动画控制器。这样注视点击后播放 Cool.anim实现「看一眼就动」的效果。3.4 参数调优gazeTime、maxDistance、冷却时间gazeTime 默认 1.5 秒是常见值但展厅场景可以调到 2 秒防止误触游戏场景可以降到 0.8 秒提升响应。maxDistance 根据场景大小设室内 5 到 10 米够用室外要加大但注意射线检测开销。冷却时间很重要——如果 OnGazeClick 后不重置 timer 或加冷却用户持续注视会每帧触发事件被刷爆。我一般会在 OnGazeClick 后把 timer 设成负的冷却值或者加一个 bool 锁。4. 避坑与排查眼动选物最容易翻车的五个点4.1 注视目标频繁跳动现象视线稍微一动高亮就在两个物体之间来回闪。原因射线命中的碰撞体边界重叠或者 targetLayer 没限制射线打到了背景。解决给可注视物体加独立的 LayerWatchController 的 LayerMask 只勾这层同时把物体的 Collider 稍微缩小避免边界重叠。4.2 高亮材质变紫红现象物体被注视后整个变成紫红色。原因HightlightingRenderer 替换的材质 shader 在当前渲染管线下不兼容或者材质文件丢失。解决检查 GraphicsSettings 里的渲染管线设置确认高亮材质用的 shader 和当前管线匹配如果是内置管线换 URP需要换 shader 或改用 Renderer Feature 做描边。4.3 注视点击不触发现象盯着物体看很久OnGazeClick 就是不调。原因WatchEvent 没挂到带 Collider 的物体上或者 Collider 被禁用也可能是 gazeTime 设太大测试时没等够。解决确认物体有 Collider 且 enabledWatchEvent 和 Collider 在同一物体或父物体上把 gazeTime 临时改成 0.5 秒快速验证。4.4 导入后 Input 轴报错现象Console 报「Input Axis Horizontal is not set up」。原因只导了 Assets 没导 ProjectSettings或者新工程的 InputManager 覆盖了资源配置。解决把资源里的 InputManager.asset 复制到 ProjectSettings 下覆盖重启 Unity如果还不行手动在 Input Manager 里补轴。4.5 打包后眼动失效现象编辑器里正常打包到 Pico4 或 Quest 后注视没反应。原因打包时 LayerMask 或 Tag 丢失或者相机不是主相机导致射线起点不对。解决检查打包设置里有没有剔除相关 Layer确认 WatchController 挂在 Tag 为 MainCamera 的相机上如果是 Pico4 开发 Unity还要确认眼动追踪权限和 SDK 初始化顺序。5. 进阶把注视进度做成可视反馈与多物体管理5.1 用 OnGazeStay 的进度参数做环形进度条WatchController 里我传了 timer / gazeTime 给 OnGazeStay这个 0 到 1 的进度值可以直接驱动 UI。常见做法是在被注视物体上方挂一个 World Space 的 ImagefillAmount 设为进度值。这样用户能直观看到「还要看多久」比单纯高亮更友好。代码上只需在 WatchEvent 的 OnGazeStay 事件里接一个方法把 float 参数传给 Image.fillAmount。5.2 多物体注视的优先级与分组场景里物体多了之后射线可能同时穿过多个可注视物体。Physics.Raycast 默认返回最近的但如果你想让某些物体优先可以用 RaycastAll 然后按距离和优先级排序。我一般会给 WatchEvent 加一个 int priority 字段排序时距离近且优先级高的排前面。这样重要按钮不会被前面的装饰物挡住。5.3 验证注视选物是否真的生效别只靠肉眼。我习惯在 WatchController 里加一个调试射线用 Debug.DrawRay 把射线画出来颜色随状态变没命中是白色命中未触发是黄色触发瞬间是绿色。再在 OnGazeClick 里打一条带时间戳的 Log。这样测试时一眼就能看出是射线没打到、还是计时没到、还是事件没注册。打包前记得把 DrawRay 关掉不然性能会掉。5.4 一个我踩过的坑早期我做眼动选物时把 WatchController 挂在了子相机上结果射线起点偏移注视永远打不准。从那以后我每次挂脚本前都强制确认三件事相机 Tag 是不是 MainCamera、脚本是不是挂在真正渲染的那个相机上、LayerMask 有没有包含目标层。这三步走完基本不会再出现「看着明明对准了却没反应」的情况。希望帮到你。本文还有配套的精品资源点击获取