
简介面向互动投影与激光雷达技术验证的Unity3D捕鱼案例定位为演示型资源适合互动投影开发者、雷达交互工程师以及希望了解投影捕鱼玩法的策划人员。它无需复杂代码逻辑重点呈现清晰美观的捕鱼画面与雷达点位触发的互动反馈可用于项目预研、功能测试和客户演示是技术交流与效果验证的便捷载体。资源压缩包约26.64MB暂无文件数量与类型明细下载后需自行解压查看目录结构。目前已有1739人学习/浏览在互动投影功能测试场景中有一定参考热度。作为非代码版案例其价值在于让使用者快速体验雷达技术下的捕鱼互动流程直观感受画面表现与响应效果并能在现有演示基础上评估二次开发方向为后续效果优化提供直观依据。1. Unity3D互动投影捕鱼用激光雷达把地面变成一片活鱼塘你如果在商场中庭、幼儿园或展厅见过那种“踩上去鱼会四散逃开”的地面投影大概率就是这个方向的项目。这个互动投影捕鱼案例核心是激光雷达捕捉地面上的人体脚部位置把实时点云数据喂进 Unity3D驱动鱼群游荡、逃逸和捕鱼特效。此类方案之所以选激光雷达而非摄像头是因为它不受环境光影响、延迟低位置精度在厘米级投影画面稳定效果不会因为有人穿深色衣服或背光就丢失跟踪。这个工程案例适合三类人做商业互动投影项目的技术团队想从普通 UI 开发转互动方向的 Unity3D 开发者以及正在做展馆、游乐设备互动方案的产品经理。落地思路清晰从雷达接入到鱼群 AI 到捕鱼判定是一条完整闭环链路照着改就能用于其它地面互动题材。2. 互动投影的底层逻辑激光雷达如何在Unity世界里找你的脚2.1 为什么是激光雷达视觉互动方案的三个硬伤互动投影领域一直有两条技术路线一是摄像头加图像识别二是激光雷达测距。很多刚接触的项目方会被摄像头方案的低成本吸引但实际部署过就会发现三个硬伤。第一个硬伤是光照敏感。摄像头方案在暗场环境必须依赖投影区补光而商场中庭或户外展位经常存在射灯、玻璃幕墙反光、午后直射阳光这些都会让图像算法的识别率断崖式下降。第二个硬伤是遮挡。一个人站在另一个人身后或弯腰系鞋带、蹲下捡东西骨骼关键点很容易丢画面里的鱼就会突然像“卡住”一样体验很糟。第三个硬伤是算力消耗。图像识别意味着每帧要跑一遍神经网络或背景建模在低配工控机上明显掉帧拖累 Unity3D 主渲染线程。激光雷达就没有这些问题。它主动发射激光束测量距离点云数据直接包含物理坐标不做“识别”只做“定位”所以不存在光照干扰也不存在“认不出人”的问题。商用互动投影场景里用得比较多的是单线机械雷达或固态雷达每秒扫描 10 到 20 圈水平视场角 90 度到 270 度测距半径 5 到 15 米地面投影区域通常 3 米乘 4 米一台雷达完全覆盖。雷达安装在投影区域边缘、离地 2 米左右、向下俯视这样能获得一条扫过地面的“切面”脚部检测正好落在视场区内。方案选型时有个关键参数要注意雷达的扫描平面与地面夹角。夹角太小雷达扫描平面几乎平行地面人走过时点云会包含小腿和地面反射的混合数据很难精确分离脚步位置夹角太大近处会出现大范围盲区。我一般建议夹角控制在 20 度到 40 度之间具体以投影区的实际长宽和雷达安装位来算。这类方案里雷达输出通常有两种形式UDP 点云数据包或串口数据流。数据包含角度、距离、信号强度三要素。常见做法是在雷达配套上位机里先把扫描平面转成直角坐标再以 JSON 结构发给 Unity3D也有的开发者直接把原始角度距离发进 Unity在 C# 侧统一做坐标转换。两种都是成熟玩法前者调试方便后者少一步通信延迟。项目落地时我偏向后者因为数据链路越短交互延迟越好控。2.2 激光雷达数据解算从极坐标到Unity世界坐标不管你用哪种雷达最终在 Unity3D 里拿到的要么是角度加距离要么是 x、y 坐标对。角度加距离是极坐标形式需要先转成直角坐标。雷达的 0 度方向通常指向雷达正前方角度按逆时针或顺时针递增这个方向定义不同品牌各不相同必须在拿到数据包时先做一次方向验证。常见做法是在 Unity3D 场景里创建一个空物体挂数据处理脚本脚本逻辑如下// LidarDataParser.cs - 雷达极坐标转Unity世界坐标 using UnityEngine; public class LidarDataParser : MonoBehaviour { [Header(雷达安装参数)] public Vector2 radarOriginOffset new Vector2(0f, 2f); // 雷达相对投影区中心偏移 public float rotationY 0f; // 雷达0度方向与Unity正Z轴的夹角 public float scaleFactor 1f; // 实际1米对应Unity单位数 /// 极坐标点转Unity世界坐标 public Vector3 ConvertPolarToWorld(float angleDeg, float distanceMeters) { float rad angleDeg * Mathf.Deg2Rad; float localX distanceMeters * Mathf.Sin(rad); float localZ distanceMeters * Mathf.Cos(rad); Vector3 localPos new Vector3(localX, 0f, localZ); Vector3 rotated Quaternion.Euler(0f, rotationY, 0f) * localPos; return new Vector3( rotated.x * scaleFactor radarOriginOffset.x, 0f, rotated.z * scaleFactor radarOriginOffset.y ); } }逻辑说明先做三角函数把极坐标转为雷达局部直角坐标再按雷达安装朝向做绕 Y 轴旋转最后叠加偏移并缩放。这里的 Sin 和 Cos 分配对应雷达 0 度方向与 X 轴的关系不同雷达定义不一样如果提前搞反点云轨迹看起来会沿镜像方向移动脚踩左边鱼跑右边排查白费半天。参数说明radarOriginOffset 用的是二维向量分别表示雷达零点在投影区坐标系里的 X 与 Z 偏移rotationY 是雷达 0 度方向相对 Unity 正 Z 轴的偏转角scaleFactor 用来做物理米到 Unity 单位的换算。如果雷达扫描平面里没有地面高度信息直接忽略 Y 轴把点压到地面层避免把裤腿误判成脚。2.3 雷达与投影的标定如何让点云位置落在正确的画面像素上坐标转换公式写出来后落地难题变成了标定对齐。这一步做得不仔细投影画面里的鱼和雷达检测到的脚位置就会错位人踩到鱼头上鱼还在原地游。标定的粗糙版本是手动调参在 Unity3D 场景里放四个立方体标记点对应投影画面四角运行后在Scene视图里把点云预览叠加到场景标记点上用键盘交互微调 offset 和 rotationY。精度要求不高时够用但现场调试往往需要用更系统的方法。我一般会在标定时先做一步“点位采集”在投影区域四个角各放一个高反射率的小圆柱体用雷达分别记录各自的角度和距离再在 Unity3D 里放四个空物体记录它们的已知世界坐标然后用最小二乘拟合出一个仿射变换矩阵。这样把“手调玄学”变成“数据标定”现场换一台投影机或移动雷达重新采集一遍就能恢复。// CalibrationHelper.cs - 三点仿射标定简版 // 输入雷达原始点坐标数组 lidarPts[]对应世界坐标数组 worldPts[] public static Matrix4x4 ComputeCalibrationMatrix(Vector2[] lidarPts, Vector2[] worldPts) { // 用前三个点建立仿射变换方程组: // worldX a0 a1 * lidarX a2 * lidarY // worldY b0 b1 * lidarX b2 * lidarY // 这里省略具体解方程代码实际可用 MathNet.Numerics 或手写高斯消元 return Matrix4x4.identity; }逻辑说明三点即可确定二维仿射变换四点做冗余校正。把解出来的矩阵应用到雷达坐标到世界坐标的转换相当于把雷达自身坐标系与 Unity3D 世界坐标系直接对齐。这个矩阵的求解可以直接引用数学库也可以手写一个最小二乘函数项目里不必为它引入重量级依赖。参数说明标定点要尽量铺满整个投影区域不要挤在中间否则外插误差会非常夸张。标定后要在场景里走一圈用鼠标把点云轨迹可视化重点看投影区边缘是否出现系统偏移如果边缘有规律的弧线弯进去说明雷达扫描平面和地面不平行需要调整雷达俯仰角而不是硬改代码。2.4 数据过滤与帧同步给Unity喂干净的坐标流雷达原始点云直接进 Unity 会遇到两个问题一是有噪点和飞点二是网络通信线程和主线程交叉容易卡顿。常规做法是把解析和消费拆开接收线程只往队列里写数据Unity 主线程在Update里取最新帧。// LidarFrameQueue.cs - 线程安全的雷达数据队列 using System.Collections.Concurrent; using UnityEngine; public class LidarFrameQueue : MonoBehaviour { private ConcurrentQueueVector3[] _frameQueue new ConcurrentQueueVector3[](); public int maxQueuedFrames 4; public void PushFrame(Vector3[] points) // 由网络接收线程调用 { if (_frameQueue.Count maxQueuedFrames) { _frameQueue.TryDequeue(out _); // 丢弃旧帧保证低延迟 } _frameQueue.Enqueue(points); } public bool TryGetLatestFrame(out Vector3[] points) // 由Unity主线程调用 { if (!_frameQueue.TryDequeue(out points)) return false; while (_frameQueue.TryDequeue(out Vector3[] newer)) points newer; // 只要最新帧 return true; } }逻辑说明ConcurrentQueue负责线程安全接收线程往队列里塞点云帧主线程取帧时一口气清空队列只保留最新一帧。这样做的好处是主线程永远不会处理积压的旧数据交互延迟稳定在单帧级别。参数说明maxQueuedFrames设 4 是平衡值设太大会让交互延迟上升设太小在网络抖动时容易丢帧表现为鱼偶尔抽搐。点云点数也要控制常规单线雷达一圈约 1000 到 4000 个点全部转成Vector3[]每帧 48 字节一个点内存压力不大但后续聚类和碰撞检测会吃 CPU所以下一章的过滤逻辑必不可少。3. 工程拆解场景、鱼群AI和捕鱼交互是怎么组织的3.1 场景层级设计Canvas、地面层、鱼群系统、特效层拿到这个案例工程包先不要急着看脚本把场景层级面板捋一遍基本就能理解作者的思路。互动投影捕鱼的场景层级通常分成四大块。第一块是交互层挂雷达数据解析器和点云可视化组件负责把雷达点云转成世界坐标并缓存。第二块是投影地面层地面是一个平面接收投影纹理负责把 Unity3D 渲染结果投到地面这里的关键是地面模型不能有碰撞体否则会影响射线检测和触发事件。第三块是鱼群系统由一个FishManager空物体统一管理所有鱼个体的生成和销毁不采用每条鱼独立编写逻辑便于做对象池和群体行为控制。第四块是特效层捕鱼成功时的水花、波纹、得分飘字都用对象池管理不临时 Instantiate避免现场长时间运行内存碎片化。场景中的玩家“脚”不需要实体对象。雷达点云聚类后得到脚部坐标这些坐标被包装为一个透明的Transform列表在Update里刷新位置供鱼群 AI 和触发器读取。这样省去了给虚拟脚部加物理碰撞体的开销也方便切换输入源——某天你要把激光雷达换成 ToF 深度相机只需要替换点云解析脚本鱼群逻辑完全不用动。// FishManager.cs - 鱼群批次管理部分代码 public class FishManager : MonoBehaviour { public GameObject fishPrefab; public int fishCount 60; public float spawnRadius 3f; private ListFishAgent _fishes new ListFishAgent(); private ListTransform _threats new ListTransform(); void Start() { for (int i 0; i fishCount; i) { Vector3 pos Random.insideUnitCircle * spawnRadius; FishAgent fish Instantiate(fishPrefab, pos, Quaternion.identity).GetComponentFishAgent(); fish.Init(this); _fishes.Add(fish); } } public void RefreshThreats(ListVector3 footPositions) { _threats.Clear(); foreach (Vector3 foot in footPositions) { GameObject go new GameObject(VirtualFoot); go.transform.position foot; _threats.Add(go.transform); } foreach (FishAgent f in _fishes) f.SetThreatSources(_threats); } }逻辑说明RefreshThreats每帧由雷达数据处理脚本调用传入当前聚类出的所有脚部坐标。这里临时创建GameObject有性能消耗工程里实际优化做法是维护一个固定数量的虚拟脚对象池每帧只更新位置不反复创建销毁。参数说明fishCount决定画面热闹程度60 条在常规配置下帧率影响不大但若场景还叠加大量投影特效建议降到 40。spawnRadius要和投影区域匹配设得比投影画面大一点可以避免鱼群突然在画面边缘消失制造“鱼从画外游进来”的错觉。3.2 鱼群游动行为转向力模型让鱼不撞人也不聚堆鱼群 AI 如果只用随机游走画面会非常死板。工程里常用的是转向力模型每个个体同时受到三股力朝向目标点的方向力、避免拥挤的排斥力、与附近同伴对齐的向心力。互动投影场景里还要额外加入“畏惧力”——脚部的排斥力场。// FishAgent.cs - 鱼群个体行为简化版 using System.Collections.Generic; using UnityEngine; public class FishAgent : MonoBehaviour { public float wanderRadius 0.5f; public float fearRadius 1.5f; // 脚进入该半径开始逃窜 public float fearWeight 8f; public float maxSpeed 2f; private ListTransform _threats new ListTransform(); private Vector3 _velocity Vector3.zero; private float _nextWanderTime; void Update() { Vector3 force Vector3.zero; // 1. 漫游力每2秒随机挑一个新目标点 if (Time.time _nextWanderTime) { _nextWanderTime Time.time Random.Range(1.5f, 2.5f); _velocity (Random.insideUnitCircle * wanderRadius).normalized * maxSpeed; } // 2. 畏惧力脚部产生排斥场越近越强 foreach (Transform t in _threats) { float dist Vector3.Distance(transform.position, t.position); if (dist fearRadius) force (transform.position - t.position).normalized * (fearWeight * (1f - dist / fearRadius)); } _velocity force * Time.deltaTime; _velocity Vector3.ClampMagnitude(_velocity, maxSpeed); transform.position _velocity * Time.deltaTime; if (_velocity.sqrMagnitude 0.01f) transform.rotation Quaternion.LookRotation(_velocity); } public void SetThreatSources(ListTransform threats) _threats threats; }逻辑说明畏惧力按距离衰减脚离鱼越近排斥力越强效果是鱼在脚边快速让开离远了不受干扰。漫游力做了时间切片避免每帧随机转向导致鱼原地抖动。整体行为偏向“散漫但有警觉感”符合真实的鱼群观感。参数说明fearWeight调太大鱼会瞬间弹开画面显得突兀调太小人走到鱼身边鱼还无动于衷。我一般用 8 起步现场根据地面材料微调。maxSpeed和鱼影长度要匹配速度快但贴图不换就会出现“滑冰”感这个问题在投影鱼影上加一个简单的帧动画切换即可解决。3.3 捕鱼触发圆圈检测、状态机与对象池捕鱼判定是这个项目最容易被低估的环节。简单的做法是检测脚部坐标与鱼的碰撞一旦重叠就捕鱼成功。但实际体验很生硬——人只是正常走过鱼就被“踩死”没有博弈感小朋友也不会兴奋。工程里常用的做法是加一个捕获状态机。鱼有四个状态闲逛、警觉、挣扎、被捕获。脚部进入鱼的触发半径后鱼进入警觉状态开始快速游走如果在 0.6 秒内脚部持续追击并保持重叠才进入挣扎状态挣扎 0.3 秒后鱼被捕获播放特效并得分。如果脚离开鱼逐步回到闲逛状态。// CaptureTrigger.cs - 捕鱼判定以状态切换为核心 public enum FishState { Wandering, Alert, Struggling, Caught } void OnTriggerStay(Collider other) { if (!other.CompareTag(VirtualFoot)) return; switch (_state) { case FishState.Wandering: _state FishState.Alert; _alertTimer 0f; break; case FishState.Alert: _alertTimer Time.deltaTime; if (_alertTimer 0.6f) EnterStruggling(); break; case FishState.Struggling: _struggleTimer Time.deltaTime; if (_struggleTimer 0.3f) Capture(); break; } } void OnTriggerExit(Collider other) { if (other.CompareTag(VirtualFoot) _state FishState.Alert) _state FishState.Wandering; // 玩家放弃追击鱼逃走 }逻辑说明触发器的Stay事件在脚部停留时每帧调用Exit事件在脚离开时触发。状态切换分布在两个事件里实现“追击才捕获”的规则避免走路随手“踩死”鱼。鱼在被捕获后调用特效对象池并延迟 2 秒在随机位置重生维持画面鱼量稳定。参数说明_alertTimer的 0.6 秒决定了玩家需要多快追到鱼太短导致鱼几乎无法逃跑太长则小朋友追两条鱼会失去耐心。_struggleTimer0.3 秒是一个“胜利前摇”给特效预留播放时间。这套状态机逻辑换到其它互动题材里同样适用比如踩地鼠、踩气泡只需要换个特效和音效判定流程可以原样复用。4. 点云处理与参数调优让雷达数据变成游戏事件4.1 原始点云为什么不能直接用噪声、地面反射、动态物体雷达点云数据直接传给 Unity 做碰撞检测效果会很差。做过一次实地测试就明白扫描平面贴近地面时鞋子边缘、裤腿、地面拼接缝都可能产生孤立的反射点抛光的瓷砖地面在某些角度会产生镜面反射形成一串连续的“虚点”投影设备本身发出的光虽然不影响雷达但周围金属展架、不锈钢底座却会产生强反射噪点。如果直接用这些点去触发捕鱼判定会出现鱼突然四处乱窜、无人走过却触发特效的“鬼踩”现象。第一步处理是深度阈值过滤。雷达测距数据里超出投影区域范围的点毫无意义而且往往是最不稳定的远距离反射点。直接根据投影区域尺寸设一个最大距离杀掉超出点。第二步是离群点剔除。所谓离群点就是周围一定半径内没有其他点的孤立点。真实脚部点云在扫描切面上通常是一簇连续的弧段而噪点则是散布的杂点。离群点剔除能有效消除地面镜面反射产生的长尾虚点。第三步是聚类。把经过过滤的点按欧氏距离聚成团每个团对应一个脚部位置。聚类之后的数据量从几千个点骤降到几个坐标点后续处理性能压力瞬间小了一个量级。4.2 项目里最常用的三招过滤深度阈值、离群点剔除、聚类// LidarPointFilter.cs - 点云三维过滤与聚类简版实现 public class LidarPointFilter { public float maxDistance 5f; // 深度阈值超出投影区的点直接丢弃 public float clusterEpsilon 0.15f; // 聚类半径两个点距离小于该值归为一类 public int minClusterSize 3; // 聚类最少点数少于该值视为噪点 public ListVector3 FilterAndCluster(ListVector3 points) { // 1. 深度阈值过滤 ListVector3 afterDepth new ListVector3(); foreach (Vector3 p in points) if (Vector3.Magnitude(p) maxDistance) afterDepth.Add(p); // 2. 基于半径聚类的简化实现 ListListVector3 clusters new ListListVector3(); foreach (Vector3 p in afterDepth) { bool merged false; foreach (var cluster in clusters) { foreach (Vector3 q in cluster) { if (Vector3.Distance(p, q) clusterEpsilon) { cluster.Add(p); merged true; break; } } if (merged) break; } if (!merged) clusters.Add(new ListVector3 { p }); } // 3. 过滤小簇保留质心 ListVector3 result new ListVector3(); foreach (var cluster in clusters) { if (cluster.Count minClusterSize) { Vector3 centroid Vector3.zero; foreach (Vector3 p in cluster) centroid p; centroid / cluster.Count; result.Add(centroid); } } return result; } }逻辑说明这段代码里的聚类算法是最朴素的顺序合并适合在线处理几百个点。实际工程中如果点数较多建议改用空间哈希加速先把点按格子索引再就近合并复杂度从 O(n²) 降到接近 O(n)。参数说明clusterEpsilon取 0.15 米对应普通成年人鞋底宽度的一半左右既能区分双脚又不会把一只脚劈成两半。minClusterSize设 3 是为了过滤掉单点噪点但如果你用的雷达每圈扫描线很稀点数密集度低这个值要降到 2或者干脆不做最小点数限制改为依赖聚类个数上限来控制误判。4.3 交互参数调优经验表互动投影现场的参数调优实际上是在“反应灵敏”和“稳定不误触”之间找平衡。下面是这套捕鱼案例里我沉淀的一组默认参数可直接作为初版配置。参数默认值调优方向踩坑提示最大检测距离5 米投影区域越大越要加大超过雷达有效测距的 80% 后精度下降明显聚类半径0.15 米儿童脚小可缩到 0.1设太大双脚会被识别成一只脚触发半径0.4 米想让人更易追鱼可放大到 0.5设太小会出现“踩中了却没抓到”的挫败感捕鱼冷却时间0.8 秒亲子场景可缩短到 0.5太短会导致连续踩同一个点刷分最大目标数8 个超大场地可放宽到 12超过后聚类结果抖动明显鱼群表现不安定鱼群逃逸速度2.2 米/秒难易度与画面活力同时受它影响调太快鱼会瞬间飞出画面无法追击每到一个新现场我会先把这个表打印出来按顺序调一遍。调参不是盲调每个参数都要观察对应的现象触发半径脏了看鱼是否在无人区受惊冷却时间脏了看同一个人能不能连踩三次刷分最大目标数脏了看鱼群的集体逃逸会不会变得杂乱无章。4.4 性能视角最低几毫秒延迟不拖垮Unity主线程激光雷达的扫描频率一般在 10 到 20 赫兹也就是说每帧数据间隔 50 到 100 毫秒。把 2000 个点的点云在每帧都做一次过滤和聚类会给 Unity3D 主线程带来可感知的卡顿。常规做法是并行处理。首先在接收线程里直接执行深度阈值过滤把数据量从 2000 点压到 800 点以内。其次聚类计算放到Job System或ThreadPool里因为它不依赖主线程的 Transform 数据是多相机场景里最适合并行的计算段。最后抬升数据消费频率雷达 10 赫兹输出Unity 渲染 60 帧中间可以做一次数据插值让虚拟脚部的位置在雷达两帧之间平滑滑动画面就不会出现鱼群“一跳一跳”的追逃效果。我见过不少团队直接在主线程里跑全套点云算法结果鱼群 AI 只能跑到 20 帧画质和交互体验双双拉垮。记住一个原则雷达数据处理是数据管线任务不是渲染任务除非项目体量特别小否则不要让它和Update中的渲染逻辑抢时间。5. 现场部署避坑指南这五个坑我至少踩过两次5.1 现象鱼总是“无视”走近的人或者只对某个方向的人有反应原因雷达安装高度与俯仰角没配合好。雷达装得太高扫描面几乎平行地面人脚与地面反射混在一起无法分离雷达装得太低扫描面切到小腿点云聚类结果落在脚踝上方视觉上“脚在水面上”却检测不到。解决把雷达装到离地 2 米左右向下俯视 20 到 40 度现场用点云预览实时观察让扫描平面切到脚背而非小腿。如果只能装高就提高聚类半径下限只取扫描面最近的一簇点作为脚部候选牺牲一部分检测距离换取稳定性。5.2 现象投影画面边缘的鱼和人位置明显错位中间正常原因雷达标定矩阵是用几个点拟合出来的投影区边缘外插误差大另一方面短焦投影机本身有桶形畸变画面边缘的地面投影物理位置不呈线性分布。解决把标定点从 4 个增加到 9 个覆盖边缘和中心用 9 点标定算出的变换矩阵在边缘常能救回几十厘米的误差。还不行就给投影机做一次几何校正常见的中高端工程投影机内置 Warping 功能校完再采一次标定点。顺序很重要先校正投影机再标定雷达反过来会白算。5.3 现象白天阳光晒到投影区鱼群开始乱窜甚至无人时触发捕鱼原因激光雷达看似抗光但阳光里的红外分量会进入接收器产生大量杂散点部分雷达自动增益控制跟不上强光变化点云密度骤增聚类算法误把噪点聚成“目标”。解决雷达安装位置加遮光罩避免阳光直射接收窗口把深度阈值调小让远距离噪点直接被杀掉把聚类最少点数调高到 5 到 6强光下的虚点很难形成 5 个以上连续点簇误触会明显减少。现场光线复杂时我还会在软件里加一个“最小置信度”参数同一位置连续 3 帧都被检测到才认为有脚单帧噪点直接忽略。5.4 现象人多的时候鱼群反而定住不动像冻住一样原因多个脚部目标同时触发每条鱼 AI 中的畏惧力计算且每个威胁源在每帧都要做一次距离判断CPU 消耗随目标和鱼群数量乘积式增长。目标超过 6 个、鱼群超过 60 只时主线程被拖垮鱼的行为更新频率跌到个位数。解决对每个目标计算畏惧力时只取最近的两个脚部点不走全量遍历。具体做法是每帧先计算鱼到最近脚部点的距离如果超过畏惧半径就跳过其余点这样把单条鱼的计算量限制在常数级。同时把鱼群 AI 的更新频率降到 15 帧每秒用视觉上的运动模糊掩盖节拍感性能影响体感上几乎不可察觉。5.5 现象换了同批次不同型号的雷达后点云方向全反了原因不同型号雷达的 0 度方向定义不同有的是雷达正面有的是壳体侧边角度旋转方向也不同有的是逆时针有的是顺时针。代码里用的是上一台雷达的方向约定新雷达数据直接套进来点云被镜像翻转。解决换雷达后第一件事不是跑鱼群效果而是先开点云预览在雷达前挥挥手确认移动方向与画面方向一致。顺手写一个方向配置开关把正转反转做成一个枚举现场换雷达不用改代码改配置文件重启即可。这个问题看起来低级但在现场时间压力下很容易被忽略修复成本反而最高。6. 验证一套互动投影系统好不好用的三板斧压力测试、灰度走位、校准习惯项目开发完到交付前我会强制推进三轮验证每一轮都能提前暴露问题省掉不少第 N 次现场返工。第一板斧是压力测试。雷达点云不一定等到现场才有可以写一个编辑器脚本模拟十个目标同时进入投影区把虚拟脚部位置喂给鱼群系统观察帧率和交互响应时间。模拟数据里要额外加入高斯噪声和随机丢帧场景真实度会高不少。// SimulateStress.cs - 编辑器压力测试入口 // 放在 Assets/Editor 目录下使用 using UnityEngine; using UnityEditor; public class SimulateStress { [MenuItem(Tools/Simulate 10 Targets)] static void RunStressTest() { var mgr Object.FindObjectOfTypeFishManager(); ListVector3 fakeFeet new ListVector3(); for (int i 0; i 10; i) { Vector2 circle Random.insideUnitCircle * 2.5f; fakeFeet.Add(new Vector3(circle.x, 0f, circle.y)); } mgr.RefreshThreats(fakeFeet); Debug.Log($已注入 {fakeFeet.Count} 个模拟目标请观察帧率与交互); } }逻辑说明一条命令把十个目标注入鱼群系统重点看FishManager.RefreshThreats和FishAgent.Update的耗时是否在安全范围内。压力测试脚本不会进入正式构建只作为开发期验证工具存在。第二板斧是灰度走位。铺设地面投影后安排一位测试员按固定路线走先慢走一圈再快跑对角线再原地连续踏步最后弯腰假装捡东西。每个动作对应一种真实场景慢走对应正常互动快跑考验聚类延迟原地踏步考验同点位重复触发稳定性弯腰动作对应儿童身高差异。走位过程中记录三个指标鱼群响应延迟是否低于 0.3 秒、是否出现无人触发、是否出现踩中人却捕获失败的漏判。第三板斧是校准习惯。在现场待过的人都明白设备换场地、换投影机、甚至换一块地垫都可能让标定失效。从那以后我每次到现场做的第一件事永远是打开点云预览走三步画一个圈确认雷达方向和投影对齐没有漂移再进鱼群参数微调。整套流程走完不到五分钟但能避免后面一小时的翻车排查。这套 Unity3D 互动投影捕鱼案例资源里已经把雷达解析、点云过滤、鱼群 AI、捕鱼状态机和标定流程串成了一条完整链路拿到手改改参数就能跑按上面三板斧走一遍再交付效果会稳很多。希望帮到你。本文还有配套的精品资源点击获取