简介这是一份面向互动投影开发者的Unity3D捕鱼游戏案例包基于激光雷达技术实现肢体交互画面清晰流畅适合在商场、展馆、游乐场等互动场景中进行功能演示与效果测试。压缩包体量约26.64MB整体轻量便捷目前已有1739人浏览学习。资源定位为非代码版案例不含源代码但保留了完整的游戏场景与交互反馈解压后可直接运行体验。通过该案例可以直观测试雷达联动响应、肢体捕捉精度、捕鱼特效的视觉表现以及投影映射的同步效果帮助开发者评估互动方案在真实环境中的可用性与流畅度。同时案例也为展项汇报、现场路演或二次开发提供了一个可运行的参考底座能有效减少从零搭建的验证成本。对于刚接触互动投影或激光雷达技术的团队而言这是一份低门槛、高可视性的入门验证资源特别适合用于技术选型前的快速体验和效果预演。1. 需求背景与选型为什么捕鱼用激光雷达比摄像头靠谱搭一套Unity3D互动投影捕鱼案例需求本身一句话就能讲清楚用户站在投影画面前用手或身体动作去“拍”水里游动的鱼系统实时识别动作并给出命中反馈。大多数人第一反应是用摄像头做人体关键点识别但我在这个项目里选了激光雷达作为交互输入。原因很直接捕鱼动作本质是“拍打水面”手需要在某个高度平面上被稳定锁定而单线激光雷达恰好只测一个平面的距离天然不受投影光斑、复杂光照和画面反光的干扰。这套方案适合两类人一是要做互动投影类课设或毕设的学生需要一套从硬件信号到游戏反馈的完整链路二是手上已有雷达和投影设备、想把游戏逻辑快速跑通的从业者。它的核心价值是“雷达技术 Unity3D 事件驱动”的完整落地路径。2. 总体架构设计从雷达点云到投影画面的四层链路整个系统按数据流向拆成四层物理层雷达、投影机、PC、数据层点云接收与滤波、交互层目标聚类、坐标映射、挥手事件、表现层Unity场景中的鱼群、UI、音效。这样分层的核心目的是让每一层都能独立调试调试雷达时不开游戏也能看到实时点云调试游戏时用鼠标模拟挥手事件即可两边互不阻塞。这也是整个项目里我最坚持的一个决定——事件流向下依赖而不是耦合在一起。2.1 硬件方案单线雷达、投影机与PC的组网硬件拓扑非常简洁PC作为运算中枢单线激光雷达通过USB转TTL串口接入投影机作为画面输出雷达布置在交互区域的一侧。安装位置是这套方案里最影响效果的因素通常的做法是雷达水平安装距地面高度约70-90cm。这个高度接近成年人自然挥手时手部的平均高度如果面向儿童场景降到60cm左右。雷达选型上测距范围0.15-6m、扫描频率10Hz以上的单线雷达足够用精度不需要追求毫米级厘米级就能满足挥手判定。投影机优先吊装或正装输出分辨率与Unity场景一致画面边界要和雷达扫描区域尽量对齐。设备选型要求安装要点单线激光雷达测距0.15-6m帧率≥10Hz串口输出高度70-90cm避免阳光直射投影机亮度≥3000流明支持1280×720画面范围与雷达扫描区域对齐PC主机无需独立显卡Unity场景复杂度低USB转串口驱动稳定线材固定防脱落安装顺序有讲究先固定雷达再调节投影画面保证雷达扫描平面穿过交互区域的中心而不是贴着边缘。装好后用一块白色硬纸板在场景里移动在雷达调试工具里观察点云是否出现在预期位置。这一步做得越细后面标定就越省事否则点云坐标和画面坐标会一直对不上。2.2 软件框架Unity作为交互中枢的事件流设计软件侧采用“接收器 分发器”的职责划分。雷达点云进入后经过滤波和检测最终被抽象成两个语义事件OnWaveEnter手进入交互区和 OnWaveHit手挥动触发拍击。场景里的捕鱼逻辑只订阅这些事件不直接感知硬件的存在。// 事件流动线雷达数据 - 滤波 - 检测 - 游戏事件 RadarReceiver - PointCloudFilter - HandDetector - InteractionEvent(s) - FishController / UIController / AudioManager这段链路的意义在于可替换性验收时如果雷达临时故障可以用鼠标模拟OnWaveHit事件继续演示游戏部分反过来雷达部分可以独立写一个调试面板来验证。我一般会把事件接口定义成接口类型而不是具体类这样后面替换多点雷达或改用摄像机方案时游戏层一行代码都不用改。关键参数上要区分雷达帧率和游戏帧率雷达10Hz意味着每100ms生成一帧点云而Unity的Update是60Hz。不要试图每帧都去读串口而是把雷达数据缓存在队列里按雷达帧率消费。最大化利用这个缓存策略能把主线程的阻塞时间压到忽略不计。2.3 帧率预算与延迟分配互动投影最容易被答辩老师质疑的就是“延迟”。把链路的时间预算拆开看雷达出一帧点云约100ms串口读取与解析控制在5ms内使用后台线程完成滤波与聚类用滑动窗口3帧时引入约30ms缓存延迟坐标映射与事件触发在单帧内完成小于1ms最后Unity表现层按60Hz渲染。整体延迟约150ms对“挥手拍鱼”这个动作来说手感是跟手的。用户感知到的延迟主要来自滤波窗口而不是雷达本身。所以窗口长度的取舍要按“误触率与跟手度的平衡”来调。窗口越长点云轨迹越平滑但延迟越大窗口太短手部抖动会被放大成误触发。这个值没有万能答案我一般从3帧开始调现场测试手感再决定加不加。注意延迟预算里的“雷达帧”不是固定的。如果调试时觉得反应慢优先检查滤波窗口和雷达实际刷新率别急着改游戏代码。3. 核心实现雷达数据解析、目标聚类与坐标标定这一章把最容易写崩的三块讲透串口数据怎么读、目标怎么从点云里抠出来、坐标怎么映射到屏幕。这三块在演示现场出问题的概率最大直接决定评审的第一印象。3.1 串口数据接收与点云结构雷达通过串口按帧返回数据常见格式是“角度,距离,角度,距离”的键值序列角度单位是度距离是毫米。以某单线雷达为例每帧包含360个测距点小于最小量程或无效值用0表示。Unity侧用独立线程读串口避免阻塞主线程using System.IO.Ports; using System.Threading; public class RadarReceiver : MonoBehaviour { private SerialPort serialPort; private Queuestring frameQueue new Queuestring(); public string portName COM3; // 串口号以设备管理器为准 public int baudRate 115200; // 雷达通常用115200 void Start() { serialPort new SerialPort(portName, baudRate); serialPort.ReadTimeout 10; serialPort.Open(); Thread thread new Thread(ReadLoop); thread.IsBackground true; thread.Start(); } void ReadLoop() { while (serialPort ! null serialPort.IsOpen) { try { string line serialPort.ReadLine(); lock (frameQueue) frameQueue.Enqueue(line); } catch { /* 读超时继续下一轮 */ } } } void FixedUpdate() { lock (frameQueue) { while (frameQueue.Count 1) // 丢弃积压帧只处理最新 frameQueue.Dequeue(); if (frameQueue.Count 1) ParseFrame(frameQueue.Dequeue()); } } }这段代码有两个关键点一是生产消费者模式串口的ReadLine是阻塞的放进后台线程避免卡住Unity主线程二是队列只保留最新一帧因为雷达10Hz的帧率远低于Unity物理帧率积压的旧帧没有任何渲染价值全处理反而会增加延迟。解析帧数据时把字符串按逗号切分后转成角度-距离的极坐标点数组。这里要过滤三类点距离为0的无效点、超出最大交互距离的远点、以及同一角度重复出现的异常点。这部分代码建议写在独立的PointCloudFilter类里方便后续加滤波算法。3.2 目标聚类与挥手触发判定原始点云是离散的测距点手扫过扫描平面时会形成一段连续的点簇。最简单的提取方式是用距离突变判断同一角度段内距离变化超过阈值就认为有目标入侵。实际实现里我不做全场景聚类而是先限定一个交互区域只处理区域内的点再找区域中距离最近的连续点簇质心作为“手的位置”。public class HandDetector : MonoBehaviour { public float triggerSpeed 1.2f; // 挥手速度阈值单位m/s public float minDistance 0.3f; // 最近检测距离 public float maxDistance 2.5f; // 最远有效距离 private Vector2 lastHandPos; private bool hasLast; private float lastTriggerTime; public void OnRadarPoint(float angleDeg, float distMeter) { if (distMeter minDistance || distMeter maxDistance) return; // 极坐标转二维平面坐标雷达所在平面即交互平面 float rad angleDeg * Mathf.Deg2Rad; Vector2 pos new Vector2(distMeter * Mathf.Cos(rad), distMeter * Mathf.Sin(rad)); if (!interactZone.Contains(pos)) return; if (hasLast) { float dt Time.time - lastTriggerTime; float speed (pos - lastHandPos).magnitude / Mathf.Max(dt, 0.01f); if (speed triggerSpeed) { OnWaveHit?.Invoke(pos); hasLast false; // 触发后需要手离开再回来避免连续触发 return; } } lastHandPos pos; lastTriggerTime Time.time; hasLast true; } }triggerSpeed取1.2m/s约等于人快速挥手时手部线速度的下限慢动作不会误触发。这里特别要注意用Time.time差值而不是Time.deltaTime因为雷达事件是异步到达的上一帧的deltaTime和实际事件间隔对不上用差值才算得准。触发后把hasLast置为false要求手先离开交互区再回来才能触发下一次这是防止“一次挥手连续计分”的关键。如果现场觉得太灵敏优先调triggerSpeed和连续有效帧数而不是改滤波窗口——窗口改大了延迟就上去了。3.3 坐标系标定从物理空间到投影屏幕雷达输出的是物理平面坐标投影画面是屏幕UV坐标两者之间存在平移、旋转和缩放。最省事的标定方式是四点映射在投影画面的四个角落对应的物理位置各放一个标定物记录雷达坐标然后用仿射变换矩阵把物理点映射到屏幕点。public class ProjectorCalibration : MonoBehaviour { // 物理坐标来自雷达检测的标定物位置 public Vector2[] physicalPoints new Vector2[4]; // 屏幕UV坐标左下、右下、左上、右上 public Vector2[] screenPoints new Vector2[4]; private Matrix2x3 transformMatrix; public Vector2 PhysicalToScreen(Vector2 physical) { return transformMatrix.Multiply(physical); } public void ComputeMatrix() { // 用最小二乘求解仿射变换参数 // 这里比手调旋转角更稳定也是标定精度差异的来源 transformMatrix Matrix2x3.Solve(physicalPoints, screenPoints); } }仿射变换用最小二乘解出来比手调旋转角度靠谱得多。常见的误区是直接用投影画面角点做标定但雷达扫描平面和投影面很难完全平行必须用“投影画面角点在地面上的投影位置”作为物理点。标定完成后用地面贴纸标记四个标定点位置重新开机后只需五分钟复核一遍。提示每次改动雷达位置、投影机位置、投影画面大小中的任意一个都必须重新标定。这是互动投影项目最常被忽略的细节也是最常见的现场事故来源。4. Unity捕鱼场景实现鱼群游动、捕鱼判定与反馈有了交互事件游戏侧就好做多了。捕鱼玩法的核心循环是鱼群在场景中游动 → 用户挥手产生拍击 → 拍打击中鱼体 → 鱼晕眩下沉 → 播放得分与特效 → 新鱼补充。这一章把鱼群管理、判定逻辑和反馈表现三块讲完整。4.1 鱼群生成与游动路径鱼群用对象池管理预设一定数量的鱼预制体在场景边缘生成游向另一侧后回池。鱼的运动用“直线前进 正弦摆动”模拟比刚性物理驱动更可控也方便在答辩时讲清楚参数含义。public class FishController : MonoBehaviour { public float swimSpeed 1.5f; // 基础游速 public float swayAmplitude 0.5f; // 摆动幅度 public float swayFrequency 2f; // 摆动频率 private Vector3 startPoint; private Vector3 endPoint; private float progress; public void Init(Vector3 start, Vector3 end, float speedScale) { startPoint start; endPoint end; swimSpeed 1.5f * speedScale; progress 0f; } void Update() { progress Time.deltaTime * swimSpeed / Vector3.Distance(startPoint, endPoint); Vector3 basePos Vector3.Lerp(startPoint, endPoint, progress); Vector3 normal Vector3.Cross(endPoint - startPoint, Vector3.forward).normalized; float offset Mathf.Sin(Time.time * swayFrequency) * swayAmplitude; transform.position basePos normal * offset; if (progress 1f) gameObject.SetActive(false); // 游出场景回池 } }鱼群按体型分三档小鱼游速快、分值低1分大鱼游速慢、分值高5分中鱼居中3分。这个设计让玩家自然倾向于追大鱼现场互动节奏就拉开了。同一屏鱼控制在12-20条太多了会遮挡交互判断区域太少了显得空旷。鱼在画面上的尺寸还要和挥手命中半径匹配——鱼要大到“拍一下能覆盖”否则容易给人打空的感觉。对象池的复用逻辑要跟上鱼游出边界或被打晕沉底后回到池子等待下次生成而不是频繁Instantiate。这个细节在长时间运行后的GC表现差异很明显。4.2 捕鱼判定与命中反馈捕鱼判定要回答两个问题“手拍到哪了”和“那里有没有鱼”。挥手事件OnWaveHit携带的是平面位置Unity里把它转成世界坐标然后用圆形射线检测找出落点附近的鱼。public class FishingGameController : MonoBehaviour { public float hitRadius 0.8f; // 命中判定半径米 public LayerMask fishLayer; public void OnWaveHit(Vector2 hitPos) { Vector3 worldPos new Vector3(hitPos.x, hitPos.y, 0); Collider2D[] hits Physics2D.OverlapCircleAll(worldPos, hitRadius, fishLayer); foreach (var hit in hits) { FishController fish hit.GetComponentFishController(); if (fish ! null fish.IsActive) { fish.Stun(); // 进入晕眩状态下沉 ScoreManager.AddScore(fish.points); SpawnHitEffect(fish.transform.position); } } } }hitRadius取0.8m不是玄学是按投影画面宽度比例定的画面宽4m时0.8m约为画面宽度的五分之一和成年人手挥动的有效覆盖范围匹配。这个半径直接决定手感——太大会奖励乱挥太小会让儿童和老年人挫败感强。建议做一个调试开关在画面上绘制命中圆的线框现场调参数时效率很高。被打晕的鱼要做下沉动画而不是直接消失。下沉持续约0.8秒期间鱼变成灰色且不再响应捕鱼事件。这个“延迟消失”的设计很重要用户要能看到“这一下真的打到鱼了”反馈链路才完整。4.3 计分、UI状态与音效节奏计分和反馈是捕鱼体验的“多巴胺来源”不能只做数字变化。标准做法是分数弹出动画、音效和粒子特效三件套。分数在命中位置生成一个飘动的数字向上浮动后淡出粒子特效打出水花粒子数量控制在20-30个太多会掉帧。public class AudioManager : MonoBehaviour { public AudioClip smallFishClip; public AudioClip bigFishClip; public void OnFishHit(int fishSize) { // fishSize按鱼的类型划分1小鱼2中鱼3大鱼 AudioClip clip fishSize 1 ? smallFishClip : bigFishClip; audioSource.PlayOneShot(clip); } }音频触发不要每帧轮询状态而是直接订阅命中事件。声音也不要无限制叠加同一种鱼连续命中时播放间隔低于100ms的可以直接忽略否则现场声音会糊成一片——这个细节决定了演示时评委听到的是“热闹”还是“噪音”。UI层建议用“无限模式 连击计数”倒计时模式会给用户压力而连击会鼓励持续互动。5. 常见问题排查调试激光雷达互动项目的五个翻车点激光雷达互动项目有个特点问题很少出在游戏代码里而是出在“雷达看到的东西和你想的不一样”。下面这五个问题是我在这个类型项目里踩过的坑每个都按现象、原因、解决路径写清楚。5.1 现象无人操作却频繁误触发现象现场没人挥手画面里的鱼自动被打计分不停跳。这种问题最容易让人误判成游戏逻辑Bug查了半天才发现根本不是。原因雷达扫描平面内有固定物体在挥手动作范围内比如吊装投影机的线缆、装饰绿植被空调风吹动的叶子、或者投影光斑在墙面上的镜像反射被雷达误判为近距目标。单线雷达只认距离不认人任何进入扇形区域的物体都成了“手”。解决先在雷达调试面板里打印完整点云静态观察一圈凡是长时间固定不动的点簇都要处理。方式有两种一是物理上把雷达移到远离线缆和绿植的位置二是在代码里配置屏蔽角度区间把固定噪点所在的角度段直接丢弃。注意屏蔽区间要留约5度余量防止目标在边界反复横跳。5.2 现象挥手有延迟像“慢半拍”现象用户挥手后约0.3-0.5秒鱼才有反应体验明显拖沓。评审现场出现这个问题会直接拉低整体评价。原因延迟往往不是雷达慢而是滤波窗口和事件触发逻辑叠加出来的。滑动窗口取5帧中值滤波加上雷达10Hz帧率就是0.5秒的响应延迟。另一个隐蔽问题是上一帧位置没更新导致速度计算出现额外延迟。解决把滤波窗口从5帧减到3帧总延迟立刻下降。再检查代码里是否在FixedUpdate中消费雷达数据物理帧率默认0.02秒而雷达帧率0.1秒两者不匹配会让事件触发不均匀。正确做法是事件按雷达帧率驱动不依赖FixedUpdate。改完后再实测“挥手到反馈”的间隔控制在0.2秒以内就是合格的跟手节奏。5.3 现象标定好的坐标用两天就偏移现象昨天调试时挥手位置和画面命中点完全对齐今天开机后偏移了10cm以上鱼在左边手在右边。原因标定矩阵本身没问题但物理环境变了。最常见的原因是雷达或投影机被碰了一下哪怕只动1度在4米宽的画面上就会放大成几十厘米的偏移。还有一种情况是雷达安装支架在夏季展厅里热胀冷缩这是隐蔽的环境因素。解决固定雷达和投影机用防松螺丝禁止用可调角度的云台装完就不管。开机后增加一个复核程序在画面四角放置标定标记让雷达扫描一遍确认偏差小于2cm才进入游戏模式。这个复核程序写成一个独立调试场景是这类项目最值得投入的功能。5.4 现象投影暗区识别率骤降现象场景里光线变化后同一个挥手动作有时识别到、有时识别不到尤其在窗户附近或者音箱屏幕旁。原因单线雷达的测距受目标反射率影响黑色衣物、深色裤子比浅色衣物反射率低得多。如果手刚好在暗色背景下或者投影画面本身很亮造成传感器过曝都会出现丢点。解决先确认雷达的测量范围和灵敏度参数有些雷达有增益可调调高一档能改善暗色目标丢失。另外演示时要求用户“挥手掌而不是手背”手掌反射面积和角度都更好。把演示着装建议写成“浅色上衣”现场命中率能高出不少——这是低成本高收益的细节。5.5 现象串口偶发断流游戏卡死现象运行半小时后Unity不再有任何交互反馈日志面板没有任何报错只有雷达调试工具里数据还在动。原因串口驱动在长时间读取后偶发缓冲区溢出或者USB转串口的线材在音响震动下接触不良。最隐蔽的情况是后台读线程还在跑但主线程的队列不再更新——看起来像死锁其实是串口读到了异常字符导致解析分支卡住。解决在ReadLoop中加异常重连机制任何异常就重新Open串口解析字符串时用TryParse包裹并跳过异常帧不要中断循环。运行前用扎带固定USB头这是物理层面的防呆。这个Bug在演示现场是毁灭性的把重连逻辑做扎实哪怕串口拔了重插也能自动恢复能换来整场验收的安心。6. 联调验收与答辩演示让方案在评审现场稳定跑通这类互动项目的验收重点不是“代码多漂亮”而是“演示多稳定”。一个频繁误触、偶尔卡死的方案即使架构再好也拿不到高分。所以验收标准要按用户体验和稳定性的维度来定。6.1 验收标准与测试流程我通常按四个维度验收功能完整性进入交互区后挥手能稳定触发捕鱼命中鱼群后计分、特效、音效同步出现。延迟指标从挥手动作到画面反馈不超过200ms用秒表人工目测连续测10次取平均值。稳定性连续运行4小时无串口断流、无游戏卡死、无点云漂移。环境适应性分别测试夜晚灯光、白天自然光、投影机高亮三种场景挥手命中率都应达到90%以上。测试流程建议先跑“鼠标模拟模式”验证游戏逻辑再切“真实雷达模式”验证交互链路最后合在一起做连续运行测试。这样任何一层出问题都能快速定位责任方。6.2 现场演示脚本与答辩问答预案演示不要从游戏开始要从“为什么选雷达”切入30秒内讲完。我习惯把演示固定成三步第一步展示雷达调试面板的点云让评审看到手在扫描平面里移动产生的真实数据变化第二步切到游戏场景用一个连贯动作连续捕三条不同大小的鱼展示命中反馈差异第三步走近画面展示鱼群持续游动状态下交互依然稳定。答辩高频问题提前准备被问“为什么不用摄像头”回答要点是摄像头受投影光干扰大人体关键点识别在强光投射场景下延迟和误判率都偏高而雷达测距不受画面光影响方案更简单稳定。被问“延迟多大”直接报实测数据不要说理论值。被问“多人同时玩怎么区分”按当前方案如实说支持单人或分区域识别再补充多目标版本的扩展思路。6.3 一条降低事故率的调试习惯我从这个项目里学到的最大教训是每次现场改动物理硬件后都必须强制走一遍“标定复核 串口重连测试 连续运行十分钟”的最小检查流程而不是直接进游戏。以前做过一次演示别人帮忙挪了一下投影机我没复核标定就上场整个评审过程里挥手命中的位置始终偏左20cm那种现场没法解释的尴尬至今记得。从那以后我每次部署这类互动投影项目都坚持开机后把“角落标定标记扫描一遍”这个动作走完才允许进入正式展示模式。这套Unity3D互动投影捕鱼案例的方案和代码结构完全可以照着搭雷达层和游戏层解耦的设计也让后续调试省了不少力希望帮到你。本文还有配套的精品资源点击获取