ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Unity3D C# 基于Rokid AR眼镜的扫码识别实战解析

Unity3D C# 基于Rokid AR眼镜的扫码识别实战解析 简介面向Unity开发者的Rokid AR眼镜扫码识别功能源码包基于Unity 2022.3.56f1c1与UXR3.0.3 SDK适配Rokid Max Pro与Station Pro适合在工业、物流、医疗等专业场景中做快速无接触信息读取。实现思路是抓取眼镜端摄像头画面通过CameraPreview类获得预览纹理再借助ZXing插件解析二维码或条形码识别特定码值后触发对应业务逻辑可覆盖资产追踪、设备点检、仓库管理等常见AR应用。压缩包为7z格式共728个文件以C#脚本、Unity场景与Prefab、材质与Shader、纹理图片和配置文件为主整体15.87MB结构清晰便于按模块修改或调试。已有196人学习下载。资源内含可集成的完整C#源码、示例场景、配套说明文档及相关依赖同时保留贴图、动画、字体等素材既能快速验证扫码效果也适合在此基础上扩展完整Rokid AR业务。导入后可从CameraPreview与ZXing的调用关系入手快速上手。1. 在 Rokid 眼镜上做扫码识别先解决「视频流怎么进 Unity」再谈二维码解码标题里「Unity3d C# 基于 RokidAR 眼镜扫码识别功能源码」这个组合真正卡住新手的反直觉结论是二维码解码本身只占一小部分最难的是把 Rokid 眼镜或它连接的主机/手机摄像头画面稳定地变成 Unity 里一张 Texture2D再喂给 ZXing 解码库。如果你现在打算在 Rokid AR 眼镜上做扫码开锁、设备绑定、展厅打卡这类功能这篇文章就是照着落地用的。读者最好有 Unity 基础会打包 Android 工程不要求懂 Rokid 私有 SDK因为常见做法是用标准 Android 相机能力就能完成采集这套方案同样能复用到其他一体式 AR 设备上。2. 方案选型与工程骨架为什么自己搭 C# 视频帧管线而不是用现成插件2.1 三条路线对比官方 SDK、商店插件、自建 ZXing 管线做 Rokid 眼镜扫码大部分团队会先走三条路我按踩坑成本从高到低排一下你对照自己的工期选。第一条是 Rokid 官方 SDK。部分 Rokid 眼镜是分体式设计眼镜本体负责显示和传感器算力在 Rokid Station 或手机端。官方 SDK 确实提供了视频透传、SLAM 等能力但扫码识别这个需求通常在业务应用层官方不一定开放裸摄像头帧给三方应用即便开放不同型号的接口也有差异。如果只是「扫个码触发一个事件」为了接私有的视频流接口去啃文档交付风险偏大。第二条是 Unity 商店里的现成扫码插件。这些插件本质上是「ZXing 的 C# 移植 Unity 相机封装」打包出售看起来省事但典型问题有三个内部实现是个黑匣子横竖屏旋转、像素格式、纹理方向这些参数没法调Rokid 这类分体式设备上摄像头枚举顺序跟手机不一样插件往往写死了devices[0]结果扫的是错误摄像头最要命的是遇到识别率不达标你连改哪里都不知道。我自己用过两次后面全推倒重写了。第三条是自建管线用 Unity 的WebCamTexture采集摄像头画面每帧取像素字节直接传给 ZXing.Net 的RGBLuminanceSource解码结果通过线程安全队列抛回主线程更新 UGUI。这条路线代码量不大每一层都是你控制的出问题能看到是采集慢、字节布局错、还是解码超时。对扫码这种明确需求我一般直接选第三条。2.2 工程目录拆分采集、解码、UI 互相解耦自建管线最怕写成一个大类摄像头初始化、解码逻辑、UI 刷新全堆在MonoBehaviour里参数想调一个就得动别的。我常用的工程结构是这样Assets/ Scripts/ Capture/ RokidCameraController.cs // 摄像头初始化、暂停恢复、帧采样 Decoder/ CodeDecoder.cs // ZXing 封装输入字节数组输出结果 ScanStrategy.cs // ROI、冷却时间、降级策略 UI/ ScanOverlay.cs // 扫码框、结果文本、状态提示 Core/ ScanResultQueue.cs // 跨线程结果队列 Plugins/ Android/ // 如果后续要接原生 AAR 放在这里 ThirdParty/ ZXing/ // ZXing.Net 源码拷贝或 DLL这个拆法的核心是采集层不引用 UI 层解码层不知道摄像头长什么样。RokidCameraController只管把每一帧的Color32[]或原始字节交给CodeDecoderCodeDecoder返回Result放进队列ScanOverlay只在Update里排空队列。这样后期从WebCamTexture换成 Android 原生 Camera2 API 时只需要改RokidCameraController一个文件。ScanResultQueue用ConcurrentQueuestring而不是直接回调事件原因在 3.3 节详细说这里先记住Unity 的 API 不能跨线程调用队列是默认的解耦手段。2.3 Android 构建设置权限声明、IL2CPP 裁剪和 ARM64工程结构定了先别急着写代码构建配置有 3 个地方必须在动手前确认否则代码对了也会在真机上翻车。第一是权限声明。WebCamTexture在 Android 上需要相机权限Unity 打包时会在AndroidManifest.xml里自动合并但我建议你主动在Plugins/Android/AndroidManifest.xml里显式声明避免某些 Rokid 主机系统对权限合并结果敏感uses-permission android:nameandroid.permission.CAMERA / uses-feature android:nameandroid.hardware.camera android:requiredfalse / uses-feature android:nameandroid.hardware.camera.any android:requiredfalse /注意requiredfalse这表示没有摄像头也能安装防止某些纯显示模式的 Rokid 设备在应用市场里被过滤掉。第二是 IL2CPP 裁剪。ZXing 底层对条码格式的判断依赖反射Unity 在 IL2CPP 下默认的 Managed Stripping Level 如果设成 High/Medium会把 ZXing 里按字符串类型查找BarcodeFormat的代码剪掉真机上表现就是「扫码永远抛ReaderException或者直接提示TypeLoadException」。常见做法是把 Stripping Level 设为 Low同时在link.xml里保留 ZXing 程序集linker assembly fullnameZXing preserveall / /linker第三是 ARM64。现在 Rokid 主机基本都是 arm64 系统Player Settings Other Settings Target Architectures里勾上 ARM64。如果你在工程里引用了任何只有 armeabi-v7a 的原生库尽早换成带 arm64 的版本不要等真机跑起来出现DllNotFoundException再回头查。3. 从摄像头纹理到 ZXing 解码一套可直接复制的 C# 核心链路3.1 运行时权限与摄像头初始化兼容 Rokid 分体式主机WebCamTexture的初始化看起来只有几行但放到 Rokid 分体式设备上有两个坑一是主机可能挂了多个摄像头设备眼镜上的双目摄像头 主机自带摄像头枚举顺序不稳定二是 Android 6.0 以后必须运行时申请权限。先处理权限using UnityEngine; using UnityEngine.Android; public class RokidCameraController : MonoBehaviour { private WebCamTexture camTexture; private IEnumerator Start() { // 权限弹窗是异步的这里必须等待用户操作完成再启动相机 if (!Permission.HasUserAuthorizedPermission(Permission.Camera)) { Permission.RequestUserPermission(Permission.Camera); // 轮询等待避免立刻调用 WebCamTexture 导致黑屏 while (!Permission.HasUserAuthorizedPermission(Permission.Camera)) { yield return null; } } StartCamera(); } private void StartCamera() { WebCamDevice[] devices WebCamTexture.devices; if (devices.Length 0) { Debug.LogError(Rokid: 未检测到摄像头设备); return; } // 优先选非前置摄像头分体式主机上眼镜端设备名通常含 Camera 字样 int targetIndex -1; for (int i 0; i devices.Length; i) { if (devices[i].isFrontFacing) continue; targetIndex i; Debug.Log($Rokid: 选择摄像头 {i} {devices[i].name}, 前置{devices[i].isFrontFacing}); break; } if (targetIndex 0) targetIndex 0; camTexture new WebCamTexture(devices[targetIndex].name, 640, 480, 15); camTexture.Play(); } }这段代码有两个设计意图。第一个意图是「轮询等待权限」Permission.RequestUserPermission在 Android 上会弹系统对话框但 Unity 的异步回调比较隐晦直接用while轮询标志位最直观注意要放在协程里不能阻塞主线程。第二个意图是「摄像头选择尽量不硬编码」Rokid 设备上WebCamTexture.devices可能返回两三个设备isFrontFacing可以帮助排除前置但有些眼镜双目的两个设备都标记为非前置这就需要在 3.2 节的调试信息里确认设备名然后在代码里加白名单优先匹配CAM、Camera这类关键字。分辨率这里先写 640x480为什么不用 1080p第 4 章细讲。3.2 每帧采样用 BGRA32 字节直接喂 ZXing避免 GetPixels 拖垮帧率摄像头转起来之后每帧要做三件事取像素、转亮度、解码。网上的旧教程会写camTexture.GetPixels32()拿Color32[]再转成byte[]数组这个写法在 640x480 下勉强能跑但到了 1280x720 就会把主线程的帧耗时拉到 20 毫秒左右配合解码耗时整机帧率直降 10 帧。我改用Texture2D.GetRawTextureDatabyte()直接读底层字节using ZXing; using ZXing.Common; using UnityEngine; public class CodeDecoder : MonoBehaviour { [Header(解码参数)] public float decodeInterval 0.2f; // 相邻两次解码最小间隔秒 public bool useRoi true; public Rect roiRect new Rect(0.2f, 0.2f, 0.6f, 0.6f); // 默认取画面中心 60% private MultiFormatReader reader; private Texture2D frameTexture; private byte[] frameBytes; private float lastDecodeTime; private void Awake() { var hints new System.Collections.Generic.DictionaryDecodeHintType, object { { DecodeHintType.POSSIBLE_FORMATS, new System.Collections.Generic.ListBarcodeFormat { BarcodeFormat.QR_CODE, BarcodeFormat.CODE_128, BarcodeFormat.EAN_13 } }, { DecodeHintType.TRY_HARDER, true }, { DecodeHintType.CHARACTER_SET, UTF-8 } }; reader new MultiFormatReader(); reader.decode(Array.Emptybyte() null ? null : new BinaryBitmap(new HybridBinarizer(new RGBLuminanceSource(new byte[0], 1, 1))), hints); } }等等上面Awake里那段是我为了演示hints的构造写出来的不能直接这么用。正确的做法是每次解码时把hints传给decode(bitmap, hints)而且不要在Awake里用一个假亮度源去「预热」这是多余的。重新把解码封装写清楚private void DecodeFrame(byte[] rawPixels, int width, int height) { if (rawPixels null || rawPixels.Length width * height * 4) return; // Unity 在 Android 上 WebCamTexture 内部像素常见为 BGRA32 // ZXing 的 RGBLuminanceSource 支持直接按 BGRA32 读不需要手动转 RGB var luminance new RGBLuminanceSource(rawPixels, width, height, RGBLuminanceSource.BitmapFormat.BGRA32); LuminanceSource source luminance; if (useRoi) { int left Mathf.FloorToInt(width * roiRect.x); int top Mathf.FloorToInt(height * roiRect.y); int cropW Mathf.FloorToInt(width * roiRect.width); int cropH Mathf.FloorToInt(height * roiRect.height); // 防止越界 if (left cropW width) cropW width - left; if (top cropH height) cropH height - top; if (cropW 0 cropH 0) { source luminance.crop(left, top, cropW, cropH); } } var bitmap new BinaryBitmap(new HybridBinarizer(source)); try { var result reader.decode(bitmap, hintsDict); if (result ! null !string.IsNullOrEmpty(result.Text)) { // 结果进队列不在解码线程里直接操作 UI ScanResultQueue.Instance.Push(result.Text); } } catch (ReaderException) { // 没有识别出码正常现象解码失败不要抛异常影响 GC } }逻辑说明RGBLuminanceSource构造函数的第三个参数是像素格式枚举我这里用的是BGRA32与 Unity Android 端GetRawTextureData的常见布局对应。如果你在部分设备上发现识别率很低可以把枚举换成RGBA32对比测试这是因为部分 Rokid 主机系统对视频帧做了格式转换。参数说明useRoi默认开裁剪出的区域越小解码越快但注意crop的结果坐标是相对裁剪区域的最终拿到条码位置时要换算回全图坐标。decodeInterval是帧间隔下一节讲怎么调。3.3 解码结果回调跨线程投递与主线程刷新 UGUIZXing 的decode是 CPU 密集型同步操作把它放在Update里直接跑会卡 UI常见做法是丢到子线程或协程里但 ZXing 解密时需要读 Unity 纹理数据必须保证纹理不被销毁。我自己更稳妥的方案是在Update里取好像素字节丢到线程池解码结果进ConcurrentQueueUI 在下一帧排空队列。using System.Collections.Concurrent; using UnityEngine; using UnityEngine.UI; public class ScanResultQueue : MonoBehaviour { public static ScanResultQueue Instance { get; private set; } private readonly ConcurrentQueuestring codes new ConcurrentQueuestring(); [SerializeField] private Text resultText; [SerializeField] private GameObject scanDonePanel; private void Awake() { Instance this; } public void Push(string code) { codes.Enqueue(code); } private void Update() { // 每帧最多处理一条避免连续弹窗跟过 UGUI 源码就知道 // 频繁改 Text.text 会触发 Canvas 标记 dirty下一帧重建几何批量处理更稳 while (codes.TryDequeue(out string code)) { resultText.text code; scanDonePanel.SetActive(true); break; } } }这里有个细节ConcurrentQueue的TryDequeue出队之后如果 UI 还没消费完消息就丢了。所以我每帧只取一条并且立即刷新 UI如果扫到长串 JSON 文本Text组件有自动换行不用额外处理。另一个注意点是ScanResultQueue用单例模式但Awake里不能在类加载时就访问 UI 组件必须保证ScanOverlay等 UI 脚本先于它执行否则resultText是空的。解法是把resultText的赋值放到ScanOverlay的Start里或者用[DefaultExecutionOrder(-100)]控制脚本执行顺序。4. 解码参数与性能调节ROI、分辨率和帧间隔怎么配合4.1 分辨率不是越高越好720p 和 15fps 的默认组合很多新手第一反应是把摄像头分辨率拉满觉得 1080p 看得清楚就能扫得更快。实际在 Rokid 这类分体式设备上分辨率和帧率不是越高越好。扫码识别关注的是条码在图像中的像素高度一个 50x50 像素的二维码在 640x480 下占 1/9 面积在 1920x1080 下占 1/36 面积分辨率翻倍条码反而变小ZXing 的HybridBinarizer二值化更容易受噪声干扰。我从几个项目里沉淀出的默认值是640x480 15fps。这个组合下GetRawTextureData每帧数据量只有 1.2MB解码一次耗时大约 30~60 毫秒取决于 ROI 大小和条码密度帧间隔 200 毫秒留给解码充足时间不至于拖垮主线程。如果一定要用 720p那解码间隔必须拉到 300 毫秒以上否则 CPU 满载后摄像头帧率自己会掉下来反而更慢。各参数组合的适用场景我整理成表分辨率帧率推荐场景注意点320x24010~15快速原型、性能压测条码过小时识别率低只用于功能验证640x48015默认首选平衡解码耗时与识别率大多数单码场景够用1280x72015条码很小/带环境框解码间隔至少 0.3s注意帧数据拷贝耗时1920x108030基本不推荐每帧 CPU 负载过高Rokid 主机容易发热降频4.2 用「先粗扫后细扫」替代每一帧全力解码decodeInterval用来控制解码频率但更聪明的做法是分两级策略。第一级用关闭TRY_HARDER的低成本模式扫全图每秒扫 3 次如果连续 1.5 秒没结果进入第二级打开TRY_HARDER同时启用 ROI 裁剪扫描画面中心 60% 区域把解码频率降到每秒 2 次。这样环境里没有码时 CPU 开销很小而码出现在 ROI 边缘时也能通过粗扫兜底。实现上我在CodeDecoder里加了状态切换private enum ScanMode { Coarse, Fine } private ScanMode currentMode ScanMode.Coarse; private float modeSwitchTimer; private bool UpdateScanMode(float deltaTime) { modeSwitchTimer - deltaTime; if (currentMode ScanMode.Coarse modeSwitchTimer 0f) { currentMode ScanMode.Fine; modeSwitchTimer 1.5f; // 细扫维持 1.5 秒仍无结果则回到粗扫 return true; } if (currentMode ScanMode.Fine modeSwitchTimer 0f) { currentMode ScanMode.Coarse; modeSwitchTimer 1.0f; return true; } return false; }切换时重新构造hintsCoarse 模式下不加TRY_HARDERFine 模式下加上TRY_HARDER并启用 ROI。这个策略对「手机屏幕上的二维码」这类需要快速响应的场景特别有效粗扫先捕获Fine 模式用来确认减少误报。4.3 触发判定的 3 个参数消抖次数、冷却时间、最大重试解码出结果之后严谨的扫码交互还需要三个参数来防止「扫到一个码就触发十次事件」。第一个是消抖次数同一个文本内容连续识别 N 次后才向外抛一次事件。N 一般设 3N1 容易误触发N5 以上对快速扫码不友好。第二个是冷却时间抛完一次事件后同一内容在 2~3 秒内不再重复上报防止人眼还在看码时后台疯狂触发。第三个是最大重试次数如果扫码框已经对准目标但 5 秒内连续失败建议把 ROI 临时扩大到全屏扫一次并降低TRY_HARDER因为有可能条码在 ROI 边缘被截断。下面这个片段是消抖和冷却的实现骨架public class ScanEventDispatcher : MonoBehaviour { private string lastCode; private int sameCodeCount; private float cooldownRemain; public void Submit(string code) { if (cooldownRemain 0f code lastCode) return; if (code lastCode) { sameCodeCount; } else { lastCode code; sameCodeCount 1; } if (sameCodeCount 3) { cooldownRemain 2.5f; sameCodeCount 0; Dispatch(code); // 这里才真正调用业务逻辑比如设备绑定 API } } private void Update() { cooldownRemain - Time.deltaTime; } }这段逻辑可以独立于 UI 层存在方便后面接 WebSocket 或 REST 接口。实际项目中扫码结果常常是 JSON 字符串比如{deviceId:RK-001,token:abc}你在Dispatch里用JsonUtility解析时要注意Unity 的JsonUtility不直接支持 JSON 数组最外层需要先包一层对象。这一点在扫码场景里非常容易踩我放在第 5 章一起说。5. 避坑记录Rokid 眼镜扫码开发里的 5 个常见问题5.1 IL2CPP 裁剪把 ZXing 裁掉现象、原因与编译期规避现象电脑上编辑器里扫码一切正常打包成 APK 装到 Rokid 主机上一运行就抛TypeLoadException或NotSupportedException日志里指向ZXing.QrCode.Internal.QRCodeReader。原因IL2CPP 的托管代码裁剪器认为 ZXing 里大量通过字符串映射BarcodeFormat的代码没有被直接引用把它们从最终包里去掉了。ZXing 的源码里有个DecodeHintType.POSSIBLE_FORMATS分支运行期根据 List 里的类型去实例化对应 Reader裁剪器看不到这条动态调用链。解决Player Settings Managed Stripping Level改成 Low同时加link.xml保留 ZXing 程序集。如果不想保留整个程序集至少要保留ZXing.MultiFormatReader、ZXing.Common.HybridBinarizer和ZXing.QrCode.QRCodeReader这几个类型。5.2 选错摄像头分体式主机多摄设备的设备名过滤现象扫码画面黑屏或者画面显示的是主机前置镜头拍的地板而不是眼镜朝前看到的现实场景更有意思的画面是「左眼和右眼画面同时并排出现」。原因Rokid 眼镜如果带双目摄像头会在系统里注册两个摄像头节点Unity 的WebCamTexture.devices会列出它们主机自身通常还有一颗前摄。而devices[0]不稳定由驱动发现顺序决定。解决在初始化时把每个设备名打印出来真机跑一次后做白名单过滤。我在代码里用Debug.Log输出设备名和isFrontFacing然后按优先级匹配先找名字含CAM或Camera的再排除前置最后如果还有两个候选用WebCamTexture.GetDeviceName保存到 PlayerPrefs下一次启动直接按上次成功设备初始化。需要注意WebCamTexture.devices必须在Play()之前读取而且要在权限授予之后否则可能返回空数组。5.3 旋转角没处理横竖屏混合时的识别率暴跌现象在 Rokid 主机上应用是竖屏但眼镜光学显示是横向 16:9扫码框对准了二维码ZXing 却一直报失败偶尔成功了扫出来的内容带乱码或前面多了几个字符。原因摄像头 CMOS 的方向和屏幕方向不一致WebCamTexture有videoRotationAngle属性常见值是 0 或 90。当 angle 为 90 时原始图像是旋转过的条码在 ZXing 看到的像素矩阵里其实被拉伸成了斜向条二值化后阈值误判。解决在解码前读取camTexture.videoRotationAngle不为 0 时对原始字节做一次旋转拷贝或者直接使用 ZXing 的LuminanceSource.rotateCounterClockwise()。ZXing 的RGBLuminanceSource继承自带rotateCounterClockwise()方法不需要你手写像素旋转。代码这样处理int angle webCamTexture.videoRotationAngle; RGBLuminanceSource lumin new RGBLuminanceSource(rawPixels, w, h, BitmapFormat.BGRA32); if (angle 90) { lumin (RGBLuminanceSource)lumin.rotateCounterClockwise(); }这里要注意旋转后lumin的宽高会互换后面crop的 ROI 坐标也必须跟着变否则会裁剪到图像边界外。5.4 后台恢复黑屏OnApplicationPause 的摄像头状态机现象应用在 Rokid 主机上切换到后台比如弹出系统权限对话框、通知栏下拉再回前台画面黑屏扫码没反应或者画面冻结在切出去之前的那一帧。原因Android 系统在应用进入后台后回收了摄像头资源WebCamTexture没有自动恢复。Unity 不会替你Play()需要自己监听生命周期。解决实现一个状态机收到OnApplicationPause(true)就camTexture.Stop()清零解码结果收到OnApplicationPause(false)重新Play()并且重置lastDecodeTime防止恢复后第一帧就解码导致纹理还没准备好。private void OnApplicationPause(bool pause) { if (pause) { camTexture.Stop(); reader.reset(); } else { camTexture.Play(); lastDecodeTime 0f; } }注意reader.reset()一定要调用。ZXing 的MultiFormatReader内部缓存了上一次解码状态不重置的话恢复后台后第一次decode可能直接失败。5.5 眼镜光学反光导致误识别ROI、闪光灯和阈值现象在室内灯光下扫手机屏幕上的二维码扫码框在白屏反光区域反复触发或者扫码结果里偶发乱码。原因Rokid 眼镜的光学方案是波导/半透膜环境光会在眼镜前方形成一层反光手机屏幕本身也有摩尔纹干扰。ZXing 的HybridBinarizer对这类「局部过曝」很敏感会把亮斑误判成条码边界。解决先缩小 ROI 到画面中心 50% 区域避开边缘反光带然后把条码格式白名单收紧比如只保留QR_CODE能显著降误报。如果目标条码在深色材质上还可以临时把RGBLuminanceSource的亮度算法从默认改为只取绿色通道——ZXing 的灰度公式是0.3R0.59G0.11B但对某些偏蓝屏的手机屏幕直接取 G 通道反而更稳定。具体做法是给RGBLuminanceSource传一个只有 G 通道的byte[]byte[] gray new byte[rawPixels.Length / 4]; for (int i 0, j 0; i rawPixels.Length; i 4, j) { gray[j] rawPixels[i 1]; // BGRA 的 G 在索引 1 } var lumin new RGBLuminanceSource(gray, w, h, RGBLuminanceSource.BitmapFormat.Gray8);这个是血泪经验纯白手机屏在眼镜里看是带彩色边缘的G 通道比 RGB 加权灰度更抗反光。代价是彩色条码识别率下降但正常二维码是黑白两色不受影响。6. 进阶技巧多码识别、连续扫码防抖和场景验证方法多码识别和连续扫码是扫码功能从「能用」走向「好用」的分水岭。ZXing 的MultiFormatReader每帧只返回一个结果要一次识别画面里的多个二维码得用GenericMultipleBarcodeReader包装一下它会把视图按不同裁剪区域重扫多次结果是一个Result[]var multiReader new GenericMultipleBarcodeReader(reader); Result[] results multiReader.decodeMultiple(bitmap, hints); for (int i 0; i results.Length; i) { ScanResultQueue.Instance.Push(results[i].Text); }这里注意decodeMultiple比普通decode慢 3~5 倍真机上不要每帧都调建议decodeInterval提升到 0.5 秒并且只在你明确需要同时绑定多个设备时才开启。单个二维码识别场景用MultiFormatReader就够了。连续扫码还有一个隐藏需求同一个码在镜头前停留 2 秒不能触发 5 次绑定请求。我在 4.3 节写的ScanEventDispatcher就是这个作用实际项目里再加一步把结果里的 JSON 用JsonUtility解析之后跟本地deviceList.json做匹配。但要注意JsonUtility直接从字符串解析会出现「顶层数组不支持」的报错这是我踩过最隐蔽的坑之一。正确写法是先把 JSON 包到对象里再解析[Serializable] public class DeviceResponse { public string code; public string message; } var wrapper JsonUtility.FromJsonDeviceResponse({\code\:\OK\,\message\:\success\});如果扫码得到的是原生 JSON 数组就在内存里拼一个{list:[...]}再解析或者改用Newtonsoft.Json。不过引入一个 JSON 库之前先评估有没有必要——只有 3~5 个字段的扫码结果JsonUtility完全够用。验证扫码延迟和稳定性的方法我会在工程里保留一个调试面板显示三组数据当前摄像头分辨率、最近一次解码耗时、连续失败次数。把 Rokid 眼镜戴好把手机屏幕调成最强亮度分别测试「1 米外扫码」「50 厘米内扫码」「屏幕倾斜 30 度扫码」三种距离记录从出现到触发事件的时间。如果近距离识别不稳定优先查旋转角如果远距离扫不到先查 ROI 是否把条码截断了。编码上还有一个习惯我一直保留解码抛ReaderException时不打印Exception堆栈打印一次Debug.LogWarning作为计数即可因为每帧失败都打堆栈日志系统本身会成为性能瓶颈也会把 Rokid 主机上的 logcat 撑爆。这个习惯让我在多个 AR 扫码项目里少走了不少弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表