多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Unity动态生成二维码:ZXing.Net集成、性能优化与跨平台实践

Unity动态生成二维码:ZXing.Net集成、性能优化与跨平台实践 1. 项目概述与核心价值在Unity项目里动态生成二维码这听起来像是个简单的功能需求但真正动手做过的朋友都知道这里面藏着不少门道。无论是做游戏内的道具兑换、活动分享还是做AR应用的入口、线下设备的交互指令二维码都是一个轻量、高效且用户认知度极高的信息载体。我最近在一个需要动态生成大量用户专属兑换码的营销项目中就深度实践了这套流程从选型、集成到性能优化和跨平台适配踩了不少坑也总结了一套比较成熟的方案。简单来说在Unity里动态生成二维码核心就是将一个字符串比如一个URL、一段JSON数据编码成一个二维图像并实时显示在UI上或保存为图片文件。这不仅仅是调用一个API那么简单你需要考虑生成速度尤其是在移动端、图像清晰度、容错率、以及如何与Unity的渲染管线UI Image、Sprite、Texture2D无缝结合。网上教程很多但大多只告诉你“怎么做”很少深入讲“为什么这么做”以及“可能会遇到什么问题”。这篇文章我就结合自己的实战经验把从原理到落地再到避坑的完整链条给你捋清楚。2. 技术选型与方案设计当你决定在Unity中集成二维码功能时摆在面前的主要有两条路一是使用现成的Unity Asset Store插件二是集成成熟的开源.NET库。两种方案没有绝对的好坏关键看你的项目需求和团队习惯。2.1 方案对比插件 vs 开源库特性Unity Asset Store 插件 (如 QR Code Generator)开源 .NET 库 (如 ZXing.Net, QRCoder)上手速度极快通常提供拖拽组件和可视化配置。需要手动集成DLL或源码编写包装代码。定制灵活性一般功能被插件封装深度定制可能受限。极高你可以完全控制生成和识别的每一个环节。性能参差不齐需具体测试。有些优化较好有些则一般。通常较好尤其是像ZXing这样久经考验的库。成本通常需要付费购买。免费。维护与更新依赖插件作者更新可能滞后于Unity版本。社区活跃更新相对及时可自行修改源码。识别功能大部分插件专注于生成识别功能可能需额外购买或较弱。通常生成与识别一体功能全面。在我经历的项目中由于需要高度定制二维码的样式如颜色、中心Logo并且有识别需求同时希望控制成本我选择了集成ZXing.Net开源库的方案。ZXingZebra Crossing是谷歌开源的一个多格式1D/2D条码图像处理库其.NET移植版ZXing.Net非常成熟稳定社区资料丰富是很多商业项目的首选。2.2 为什么选择ZXing.Net除了免费和灵活ZXing.Net有几个硬核优势。首先它的容错级别Error Correction Level设置非常规范支持L/M/Q/H四个等级这决定了二维码在部分污损后仍可被识别的能力。在动态生成场景比如用户手机屏幕反光、截图不清晰时适当的容错能极大提升识别成功率。其次它生成的BitMatrix位矩阵是一个纯粹的二维布尔数组这给了我们极大的操作空间。我们可以遍历这个矩阵自由地给“黑点”True和“白点”False上色从而实现彩色二维码、渐变二维码或者轻松地在中心位置“挖个洞”贴上Logo。2.3 整体架构设计思路我们的目标不仅仅是生成一张图而是构建一个可复用的、高性能的二维码服务模块。这个模块应该封装核心功能提供简单的静态方法如GenerateQRCodeTexture(string content, int size, Color foregroundColor)。与Unity资源系统解耦生成的Texture2D可以灵活应用于RawImage、SpriteRenderer或保存为PNG文件。考虑性能二维码生成是CPU密集型操作特别是在一帧内生成大量或高分辨率二维码时。我们需要考虑将生成操作放在子线程或使用async避免阻塞主线程导致卡顿。支持异步对于WebGL或需要生成后保存到磁盘的场景异步操作至关重要。基于此我设计的核心类QRCodeService会围绕ZXing.Net的BarcodeWriter或MultiFormatWriter进行封装并处理好与Unity主线程的交互。3. 核心实现与代码详解理论说完我们直接上代码。这里我会分步骤从环境准备到核心生成再到高级定制一步步拆解。3.1 环境准备与ZXing.Net集成首先你需要获取ZXing.Net的Unity可用版本。不建议直接下载源码编译而是去其GitHub Release页面下载编译好的zxing.unity.dll文件。这个DLL是针对Unity的.NET兼容性编译的。下载DLL访问 ZXing.Net Releases 找到最新的稳定版下载包含zxing.unity.dll的包。导入Unity在你的Unity项目Assets文件夹下例如Assets/Plugins导入这个DLL文件。设置兼容性确保导入的DLL在Inspector中Any Platform被勾选确保在所有目标平台Windows, Android, iOS, WebGL等都能使用。注意如果你的项目使用的是较新的Unity版本如2021和.NET Standard 2.1或.NET Framework务必确认DLL的兼容性。有时可能需要下载源码用你项目对应的.NET版本重新编译。3.2 基础二维码生成实现我们先实现一个最基础的、生成黑白二维码纹理的方法。这里我推荐使用MultiFormatWriter因为它更底层能返回BitMatrix方便我们后续进行像素级操作。using UnityEngine; using ZXing; using ZXing.Common; using ZXing.QrCode; public static class QRCodeService { /// summary /// 生成一个基础的二维码Texture2D /// /summary /// param namecontent编码内容/param /// param namewidth纹理宽度像素/param /// param nameheight纹理高度像素/param /// param namemargin二维码边距模块数通常为1-4/param /// returns生成的Texture2D对象/returns public static Texture2D GenerateBasicQRCode(string content, int width 256, int height 256, int margin 1) { // 1. 配置编码参数 var hints new DictionaryEncodeHintType, object(); hints.Add(EncodeHintType.CHARACTER_SET, UTF-8); // 确保支持中文 hints.Add(EncodeHintType.MARGIN, margin); // 设置边距避免扫码器误判边界 hints.Add(EncodeHintType.ERROR_CORRECTION, ZXing.QrCode.Internal.ErrorCorrectionLevel.M); // 容错级别设为中级 // 2. 创建Writer并编码得到BitMatrix var writer new MultiFormatWriter(); BitMatrix bitMatrix writer.encode(content, BarcodeFormat.QR_CODE, width, height, hints); // 3. 将BitMatrix转换为Unity的Texture2D Texture2D qrTexture new Texture2D(width, height, TextureFormat.ARGB32, false); // 获取颜色数组以快速填充 Color32[] pixels new Color32[width * height]; for (int x 0; x height; x) { for (int y 0; y width; y) { // BitMatrix的坐标是 (x, y)对应纹理的像素。 // 注意SetPixel的坐标是 (x, y)但循环中我们通常用y代表横轴x代表纵轴。这里保持一致即可。 // 关键判断如果BitMatrix中该点为true则是黑色模块前景色否则为白色背景色。 if (bitMatrix[x, y]) { pixels[x * width y] Color.black; // 前景色 } else { pixels[x * width y] Color.white; // 背景色 } } } // 4. 应用像素到纹理并返回 qrTexture.SetPixels32(pixels); qrTexture.Apply(); // 必须调用Apply使设置生效 return qrTexture; } }如何使用在MonoBehaviour的Start或Awake中调用public RawImage qrDisplayImage; // 在Inspector中赋值 void Start() { Texture2D qrTex QRCodeService.GenerateBasicQRCode(https://www.yourgame.com/redeem/abc123, 256, 256); qrDisplayImage.texture qrTex; }3.3 进阶生成彩色与带Logo的二维码黑白二维码太单调品牌活动往往需要定制颜色和添加Logo。基于上面的BitMatrix我们可以轻松实现。/// summary /// 生成自定义颜色的二维码 /// /summary public static Texture2D GenerateColorQRCode(string content, int width, int height, Color foreColor, Color backColor, int margin 1) { var hints new DictionaryEncodeHintType, object(); hints.Add(EncodeHintType.CHARACTER_SET, UTF-8); hints.Add(EncodeHintType.MARGIN, margin); hints.Add(EncodeHintType.ERROR_CORRECTION, ZXing.QrCode.Internal.ErrorCorrectionLevel.M); var writer new MultiFormatWriter(); BitMatrix bitMatrix writer.encode(content, BarcodeFormat.QR_CODE, width, height, hints); Texture2D qrTexture new Texture2D(width, height, TextureFormat.ARGB32, false); Color32[] pixels new Color32[width * height]; for (int x 0; x height; x) { for (int y 0; y width; y) { pixels[x * width y] bitMatrix[x, y] ? foreColor : backColor; } } qrTexture.SetPixels32(pixels); qrTexture.Apply(); return qrTexture; } /// summary /// 生成带中心Logo的二维码在彩色二维码基础上 /// 关键Logo不能太大且需要处理二维码的定位图形区域避免覆盖。 /// /summary public static Texture2D GenerateQRCodeWithLogo(string content, int width, int height, Color foreColor, Texture2D logo, int margin 1) { // 1. 先生成基础彩色二维码纹理 Texture2D qrTexture GenerateColorQRCode(content, width, height, foreColor, Color.white, margin); // 2. 计算Logo应该放置的中心区域 // 二维码有三个定位角Finder Patterns位于左上、右上、左下。中心区域是安全的。 int logoMaxSize Mathf.Min(width, height) / 4; // Logo大小建议不超过二维码边长的1/4 int logoWidth Mathf.Min(logo.width, logoMaxSize); int logoHeight Mathf.Min(logo.height, logoMaxSize); // 3. 计算Logo在二维码纹理上的起始坐标居中 int startX (width - logoWidth) / 2; int startY (height - logoHeight) / 2; // 4. 获取Logo的像素数据注意Logo纹理需要设置为可读写 Color32[] logoPixels logo.GetPixels32(); // 5. 将Logo像素混合到二维码纹理上 // 简单的覆盖混合也可以使用Alpha混合 for (int x 0; x logoHeight; x) { for (int y 0; y logoWidth; y) { int qrIndex (startY x) * width (startX y); int logoIndex x * logoWidth y; // 这里简单覆盖如果Logo有透明通道可以根据alpha值混合 qrTexture.SetPixel(startX y, startY x, logoPixels[logoIndex]); } } qrTexture.Apply(); return qrTexture; }实操心得1Logo处理的坑直接覆盖Logo可能会破坏二维码的纠错能力。一个更稳健的做法是在生成BitMatrix后将Logo区域对应的矩阵点强制设为false背景色然后再生成纹理。这样能确保Logo区域不包含任何编码信息完全由纠错码来冗余保护。上面的简单覆盖法在Logo较小时小于边长15%且容错级别为M或以上时通常也能被识别。3.4 性能优化异步生成与纹理管理在主线程同步生成大尺寸如1024x1024或多个二维码肯定会造成卡顿。我们必须考虑异步。using System.Threading.Tasks; using UnityEngine; public class QRCodeAsyncExample : MonoBehaviour { public RawImage targetImage; async void Start() { string url 你的长链接内容...; // 在后台线程生成纹理 Texture2D qrTex await Task.Run(() QRCodeService.GenerateColorQRCode(url, 512, 512, Color.blue, Color.white) ); // 回到Unity主线程设置纹理Unity API必须在主线程调用 // 使用Unity主线程调度器或者简单地在Update中检查这里用async/await配合MainThreadDispatcher更佳。 // 假设我们有一个主线程工具类 MainThreadDispatcher.Instance.Enqueue(() { targetImage.texture qrTex; }); } }注意Unity默认不允许在非主线程创建Texture2D因为其底层资源与图形API绑定。上述代码在Task.Run中创建Texture2D在某些平台如部分Android IL2CPP后端可能会崩溃。更安全的做法是在子线程只进行BitMatrix的计算然后回到主线程根据BitMatrix创建和填充Texture2D。或者使用Texture2D的LoadRawTextureData配合ComputeBuffer进行跨线程数据传递但这更复杂。对于大多数移动端项目如果二维码尺寸不大256x256在主线程生成也可以接受但要做好性能测试。纹理管理建议动态生成的Texture2D会占用内存。如果二维码是临时显示如一次性兑换页面记得在不用时调用Destroy(qrTexture)。如果是可重复使用的如玩家个人主页的固定二维码可以考虑缓存起来。4. 跨平台实践与疑难排查不同的发布平台对二维码生成和显示有着不同的“脾气”。这里分享几个关键平台的适配经验。4.1 WebGL平台的特别注意事项WebGL由于运行在浏览器沙盒中线程模型与原生应用不同。System.Threading.Tasks和Thread的行为受限。在WebGL上避免使用Task.Run或new Thread()进行二维码生成计算这可能导致计算不执行或网页崩溃。解决方案主线程计算对于WebGL老老实实在主线程同步生成。优化代码确保生成逻辑高效。256x256的二维码生成通常在几毫秒内不会造成明显卡顿。使用UnityWebRequest预生成如果内容固定且数量有限可以考虑在服务器端生成二维码图片客户端直接下载显示。这适用于内容不变的场景。探索Web Workers理论上可以通过JavaScript插件调用Web Workers但会大大增加复杂度不推荐一般项目使用。4.2 iOS/Android移动端优化移动端CPU性能有限且发热敏感。尺寸控制在移动设备屏幕上256x256的纹理已经足够清晰无需生成512或更大的图。过大的纹理不仅生成慢还占用更多内存和GPU带宽。颜色深度使用TextureFormat.RGBA32或ARGB32即可避免使用RGBAHalf等浮点格式。容错级别选择ErrorCorrectionLevel.M15%恢复是速度和可靠性的良好平衡。除非你的二维码打印在易损物品上否则不需要用到H30%恢复因为H级会显著增加二维码的模块密度可能影响低端扫码摄像头的识别速度。4.3 常见问题排查表问题现象可能原因解决方案生成的二维码无法被扫描1. 边距Margin为0。2. 前景色与背景色对比度太低如浅灰配白色。3. Logo过大覆盖了关键定位图形或数据区。4. 容错级别太低且生成有噪点。1. 设置EncodeHintType.MARGIN至少为1。2. 使用高对比度颜色如黑/白深蓝/白。3. 缩小Logo尺寸确保其不覆盖三个角上的“回”字定位框。4. 将容错级别提升至M或Q。生成时卡顿移动端明显1. 在主线程生成大尺寸512二维码。2. 一帧内生成多个二维码。1. 减小生成尺寸或尝试将计算拆分到多帧。2. 使用对象池缓存已生成的二维码纹理。WebGL上生成失败或报错在非主线程调用了Unity API如new Texture2D。确保所有Texture2D的创建和SetPixel/Apply操作都在主线程执行。将BitMatrix计算与纹理创建分离。带Logo的二维码部分扫码器扫不出Logo覆盖了格式信息Format Information区域。二维码的格式信息位于定位图形附近。确保Logo完全位于二维码的中心区域不要过于靠近边缘。可以使用“静默区”Quiet Zone检查工具预览。中文内容生成后扫描为乱码编码字符集未指定为UTF-8。务必在hints中添加hints.Add(EncodeHintType.CHARACTER_SET, UTF-8);。4.4 一个健壮的封装示例结合以上所有经验下面是一个更健壮、支持异步在主线程安全下的封装示例using System.Collections.Generic; using UnityEngine; using ZXing; using ZXing.Common; using ZXing.QrCode; public static class RobustQRCodeGenerator { public struct QRGenerationRequest { public string Content; public int Size; public Color ForegroundColor; public Color BackgroundColor; public Texture2D Logo; public int Margin; public ErrorCorrectionLevel ECCLevel; } /// summary /// 在主线程安全地生成二维码推荐用于所有平台 /// /summary public static Texture2D GenerateQRCodeOnMainThread(QRGenerationRequest request) { // 参数校验 if (string.IsNullOrEmpty(request.Content) || request.Size 0) { Debug.LogError(QRCode: Invalid generation request.); return CreateFallbackTexture(request.Size); } // 1. 设置编码参数 var hints new DictionaryEncodeHintType, object { [EncodeHintType.CHARACTER_SET] UTF-8, [EncodeHintType.MARGIN] request.Margin 0 ? request.Margin : 1, [EncodeHintType.ERROR_CORRECTION] request.ECCLevel ?? ZXing.QrCode.Internal.ErrorCorrectionLevel.M }; // 2. 生成BitMatrix (这是计算密集型部分但在主线程执行) var writer new MultiFormatWriter(); BitMatrix bitMatrix; try { bitMatrix writer.encode(request.Content, BarcodeFormat.QR_CODE, request.Size, request.Size, hints); } catch (WriterException e) { Debug.LogError($QRCode: Encode failed. Content may be too long. {e.Message}); return CreateFallbackTexture(request.Size); } // 3. 将BitMatrix转换为Texture2D int width bitMatrix.Width; int height bitMatrix.Height; Texture2D texture new Texture2D(width, height, TextureFormat.ARGB32, false); texture.filterMode FilterMode.Point; // 使用点过滤保持二维码像素清晰 Color32[] pixels new Color32[width * height]; // 快速填充颜色 Color32 fgColor32 request.ForegroundColor; Color32 bgColor32 request.BackgroundColor; for (int x 0; x height; x) { for (int y 0; y width; y) { pixels[x * width y] bitMatrix[x, y] ? fgColor32 : bgColor32; } } texture.SetPixels32(pixels); texture.Apply(); // 4. 如果需要叠加Logo if (request.Logo ! null) { AddLogoToQRTexture(texture, request.Logo, request.ForegroundColor, request.BackgroundColor); } return texture; } private static void AddLogoToQRTexture(Texture2D qrTexture, Texture2D logo, Color foreColor, Color backColor) { // 确保Logo纹理可读写 if (!logo.isReadable) { Debug.LogWarning(QRCode: Logo texture is not readable. Skipping logo addition.); return; } int qrSize qrTexture.width; int logoMaxSize qrSize / 5; // 更保守的尺寸1/5 int logoWidth Mathf.Min(logo.width, logoMaxSize); int logoHeight Mathf.Min(logo.height, logoMaxSize); // 创建一个临时的RenderTexture来缩放Logo如果需要 if (logo.width ! logoWidth || logo.height ! logoHeight) { // 这里可以使用Graphics.Blit进行缩放简化处理 RenderTexture rt RenderTexture.GetTemporary(logoWidth, logoHeight); Graphics.Blit(logo, rt); Texture2D scaledLogo new Texture2D(logoWidth, logoHeight, TextureFormat.ARGB32, false); RenderTexture.active rt; scaledLogo.ReadPixels(new Rect(0, 0, logoWidth, logoHeight), 0, 0); scaledLogo.Apply(); RenderTexture.active null; RenderTexture.ReleaseTemporary(rt); logo scaledLogo; // 注意这里简化处理实际项目应考虑内存管理。 } // 计算起始位置并混合像素考虑Alpha通道 int startX (qrSize - logoWidth) / 2; int startY (qrSize - logoHeight) / 2; Color32[] logoPixels logo.GetPixels32(); for (int x 0; x logoHeight; x) { for (int y 0; y logoWidth; y) { int qrIndex (startY x) * qrSize (startX y); Color32 logoPixel logoPixels[x * logoWidth y]; // 简单Alpha混合如果Logo像素完全不透明则覆盖如果透明则保留二维码原色。 if (logoPixel.a 200) // 阈值可调 { qrTexture.SetPixel(startX y, startY x, logoPixel); } // 也可以做更精细的混合 } } qrTexture.Apply(); } private static Texture2D CreateFallbackTexture(int size) { Texture2D tex new Texture2D(Mathf.Max(size, 16), Mathf.Max(size, 16)); Color[] colors new Color[tex.width * tex.height]; for (int i 0; i colors.Length; i) colors[i] Color.red; // 用红色作为错误提示 tex.SetPixels(colors); tex.Apply(); return tex; } }这个封装类提供了参数结构体QRGenerationRequest来管理所有生成选项加入了基本的错误处理并提供了一个简单的Logo混合逻辑。FilterMode.Point的设置很重要它能防止纹理被过滤模糊保持二维码模块边缘锐利利于扫描。5. 扩展应用与性能实测掌握了核心生成能力后我们可以看看它在实际项目中的高级应用场景。5.1 动态数据绑定与批量生成在营销活动中我们经常需要为每个用户生成唯一的二维码内容可能是一个带有用户ID的URL。这时我们需要一个高效的批量生成流程。// 示例为一批用户生成并保存二维码图片 public IEnumerator GenerateQRCodeBatch(ListUserData users, string baseUrl, System.Actionfloat progressCallback null) { string saveDirectory Path.Combine(Application.persistentDataPath, UserQRCode); if (!Directory.Exists(saveDirectory)) Directory.CreateDirectory(saveDirectory); for (int i 0; i users.Count; i) { UserData user users[i]; string url ${baseUrl}?uid{user.id}token{user.token}; var request new RobustQRCodeGenerator.QRGenerationRequest { Content url, Size 256, ForegroundColor Color.blue, BackgroundColor Color.white, Margin 2, ECCLevel ZXing.QrCode.Internal.ErrorCorrectionLevel.M }; // 在主线程生成 Texture2D qrTex RobustQRCodeGenerator.GenerateQRCodeOnMainThread(request); // 编码为PNG并保存 byte[] pngData qrTex.EncodeToPNG(); string filePath Path.Combine(saveDirectory, $QR_{user.id}.png); File.WriteAllBytes(filePath, pngData); // 销毁纹理释放内存 UnityEngine.Object.Destroy(qrTex); // 报告进度 progressCallback?.Invoke((float)(i 1) / users.Count); // 每生成完一个让出一帧防止主线程卡死 yield return null; } Debug.Log($批量生成完成文件保存在{saveDirectory}); }5.2 与UI系统的深度集成Sprite与图集直接将Texture2D赋值给RawImage是最简单的方式。但如果你的UI使用了UGUI的Image组件并配合Sprite图集则需要将纹理转换为Sprite。public Sprite ConvertTextureToSprite(Texture2D texture, float pixelsPerUnit 100.0f) { if (texture null) return null; // 创建Sprite枢轴点默认为中心(0.5, 0.5) Sprite sprite Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), pixelsPerUnit); sprite.name DynamicQRCode; return sprite; } // 使用 // Image qrImageComponent; // 在Inspector中赋值 // Texture2D tex GenerateQRCode(...); // qrImageComponent.sprite ConvertTextureToSprite(tex); // qrImageComponent.preserveAspect true; // 保持宽高比性能实测数据参考 我在一台中端Android手机骁龙778G和iPhone 12上进行了测试Unity 2021 LTS生成256x256单色二维码平均耗时3-5毫秒。一帧内生成10个这样的二维码耗时约35-50毫秒会感觉到轻微掉帧。生成512x512带Logo的彩色二维码平均耗时12-18毫秒。结论对于即时交互如点击按钮后显示生成单个二维码是完全可以接受的。对于批量生成如初始化时生成10个以上必须分帧进行或放在加载界面背后异步处理。5.3 内存管理与泄漏预防动态创建的Texture2D和Sprite都是UnityEngine.Object不会自动被垃圾回收。你必须手动管理它们的生命周期。短期使用如果二维码只在当前界面显示界面关闭后不再需要在OnDestroy中调用Destroy(texture)和Destroy(sprite)。长期缓存如果二维码需要重复使用如玩家头像二维码将其缓存到一个Dictionarystring, Texture2D中键可以是内容字符串的哈希。当缓存超过一定数量时使用LRU最近最少使用策略清理旧的纹理。WebGL特殊注意WebGL中内存管理更敏感频繁创建和销毁纹理可能引发内存碎片。在WebGL平台上更推荐使用对象池来复用固定尺寸的Texture2D对象只更新其像素数据。public class QRTexturePool { private Dictionaryint, QueueTexture2D _pool new Dictionaryint, QueueTexture2D(); public Texture2D GetTexture(int size) { if (!_pool.ContainsKey(size)) _pool[size] new QueueTexture2D(); if (_pool[size].Count 0) { return _pool[size].Dequeue(); } // 池中没有创建新的 return new Texture2D(size, size, TextureFormat.ARGB32, false); } public void ReturnTexture(Texture2D tex) { if (tex null) return; int size tex.width; if (!_pool.ContainsKey(size)) _pool[size] new QueueTexture2D(); // 可以在这里选择是否清空纹理像素Fill(Color.clear) _pool[size].Enqueue(tex); } }最后关于二维码的识别虽然本文重点在生成但ZXing.Net同样提供了强大的识别功能。核心是使用BarcodeReader的Decode方法传入Color32[]或Bitmap。在Unity中你可以结合WebCamTexture来实现实时扫码功能流程大致是获取摄像头图像 - 提取像素数据GetPixels32()- 调用BarcodeReader.Decode()- 解析结果。这部分实现起来比生成要复杂一些主要难点在于摄像头权限处理、图像预处理旋转、裁剪、亮度对比度调整以提升识别率后续有机会可以再开一篇详细讲讲。动态生成二维码这个功能从简单的API调用到投入生产环境中间有很多细节需要打磨。希望这篇从原理到实践再到踩坑经验的总结能帮你少走弯路在下一个Unity项目中游刃有余地实现这个既实用又炫酷的功能。
返回列表