Unity二维码生成全攻略:免费本地化实现与性能优化实践
1. 项目概述:为什么Unity开发者需要一个二维码工具
在Unity项目开发中,无论是制作一个需要分享下载链接的移动端游戏,还是一个需要引导用户访问特定网页的AR应用,二维码都是一个极其便捷的交互入口。然而,很多开发者,尤其是刚入门的同学,在面对“生成二维码”这个需求时,往往会陷入一个误区:要么去网上找一些付费的插件,要么尝试调用外部API,不仅增加了项目成本和复杂度,还可能引入网络依赖和隐私风险。
我自己在开发一个线下活动签到应用时就遇到过这个问题。当时需要为每个参会者动态生成包含个人信息的签到二维码,如果使用在线API,不仅速度慢、有并发限制,在无网络环境下更是直接“罢工”。于是,我决定自己动手,封装一个完全免费、纯本地运行、且功能足够强大的Unity二维码生成工具。这个工具的核心目标就三个:免费、易用、功能全。它不仅能将文本(URL、字符串)转换成二维码,还能将图片、甚至视频的访问路径或缩略信息编码进去,满足绝大多数Unity项目的日常需求。
今天,我就把这个工具的完整实现思路、核心代码以及我踩过的坑,毫无保留地分享出来。无论你是想为你的游戏添加一个分享功能,还是为你的企业应用制作资产标签,这篇内容都能让你在10分钟内,拥有一个属于自己的、高性能的二维码生成模块。
2. 核心需求解析与方案选型
在动手编码之前,我们必须先明确工具要解决的具体问题,并选择最合适的技术路径。盲目开始只会导致代码重构甚至推倒重来。
2.1 功能需求拆解
基于“文本、图片、视频二维码”这个标题,我们可以将需求分解为三个层次:
- 基础文本编码:这是核心功能。能够将任意字符串(如
https://your-game.com,PLAYER_ID:1001)编码成标准的QR Code二维码图像。这是所有功能的基础。 - 图片信息嵌入:这里的“图片二维码”通常不是指将整张图片的像素数据编码进二维码(那样会超出二维码的容量限制,导致无法识别),而是指两种实用场景:
- 场景一:生成一个二维码,扫描后直接显示一张网络图片。这本质上是将图片的URL地址作为文本进行编码。
- 场景二:生成一个二维码,扫描后跳转到一个展示该图片的网页,或触发应用内显示该图片的逻辑。这需要编码一个带有特定参数的URL或自定义协议字符串。
- 视频信息嵌入:与图片类似,直接编码视频文件数据不现实。更实用的场景是:
- 编码视频流地址(如
rtmp://live.example.com/stream)或视频文件URL。 - 编码一个跳转到视频播放页面的链接。
- 在离线场景下,编码一个视频的标识ID,由本地应用解析后从本地存储加载播放。
- 编码视频流地址(如
所以,我们的工具本质上是一个灵活的字符串编码器,只是输入的字符串来源和格式需要针对“图片”和“视频”场景进行友好封装。
2.2 技术方案对比与选型
在Unity中生成二维码,主流方案有以下几种:
| 方案 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 外部插件 (如 ZXing.Net) | 引入成熟的第三方 .NET 二维码库。 | 功能强大、稳定、支持多种编码格式(QR Code, Data Matrix等)。 | 需要导入DLL或源码,可能增加包体大小;部分高级插件收费。 | 追求稳定、功能全面,且不介意引入外部依赖的项目。 |
| 纯C#实现 (如 QRCoder) | 使用纯C#编写的开源二维码生成库。 | 完全免费、开源、可定制性强;无原生依赖。 | 性能可能略低于高度优化的C++库;需要自行处理与Unity纹理的转换。 | 希望完全掌控代码、避免任何版权风险的项目。 |
| 系统API调用 (移动平台) | 调用Android的ZXing库或iOS的CoreImage。 | 直接使用系统能力,可能更省电。 | 平台相关,代码不统一;需要写平台桥接;功能受系统限制。 | 对包体大小极度敏感,且功能需求简单的平台特定应用。 |
| 在线API生成 | 将数据发送到云端服务器,返回二维码图片。 | 无需本地计算资源,实现简单。 | 严重依赖网络,有延迟、并发限制和隐私泄露风险;离线不可用。 | 基本不推荐用于生产环境,仅用于原型验证。 |
我的选择与理由:我选择了纯C#实现的 QRCoder 库。原因如下:
- 完全免费与开源:这符合我们“亲测免费”的标题核心,没有任何法律或财务风险。源码在GitHub上,可以随意查看、修改和分发。
- 零依赖与高可控性:它不依赖任何Unity未包含的本地库,跨平台(PC、移动端、WebGL)兼容性极好。所有代码都在掌控之中,调试和优化方便。
- 功能足够强大:QRCoder支持QR Code的所有标准版本和纠错等级,可以生成位图数据,这正好满足我们将数据转换为Unity
Texture2D的需求。- 性能可接受:对于生成频率不高(如每秒不超过10次)的场景,其纯C#的性能完全足够。如果遇到性能瓶颈,我们可以对其进行针对性优化(如对象池、异步生成)。
因此,本工具将以QRCoder为核心引擎,围绕它构建一个易于Unity开发者使用的工具类。
3. 工具核心实现与代码详解
接下来,我们进入实战环节。我会分步骤讲解如何将QRCoder集成到Unity中,并封装成方便调用的工具。
3.1 环境准备与QRCoder导入
首先,你需要获取QRCoder。最推荐的方式是通过Unity的Package Manager使用Git URL安装,这样可以方便地更新。
- 打开你的Unity项目。
- 打开
Window -> Package Manager。 - 点击左上角的
+号,选择Add package from git URL...。 - 输入QRCoder的Git仓库地址:
https://github.com/codebude/QRCoder.git - 点击
Add。Unity会自动下载并导入这个库。
如果Git方式失败,你也可以直接从GitHub Releases页面下载QRCoder.dll文件,将其放入你项目的Assets/Plugins文件夹下。但Package Manager方式是首选,便于管理。
3.2 核心工具类封装
我们创建一个名为QRCodeTool.cs的脚本。这个类将提供静态方法,方便在任何地方调用。
using UnityEngine; using QRCoder; using System.Drawing; // 注意:QRCoder使用System.Drawing,但Unity中需要转换 using System.IO; using System; public static class QRCodeTool { /// <summary> /// 将文本生成二维码纹理 /// </summary> /// <param name="plainText">要编码的文本</param> /// <param name="pixelsPerModule">每个模块的像素大小(影响清晰度和纹理尺寸)</param> /// <param name="eccLevel">纠错等级 (L, M, Q, H)</param> /// <returns>生成的Texture2D</returns> public static Texture2D GenerateQRCodeTexture(string plainText, int pixelsPerModule = 10, QRCodeGenerator.ECCLevel eccLevel = QRCodeGenerator.ECCLevel.M) { if (string.IsNullOrEmpty(plainText)) { Debug.LogError("生成二维码的文本内容不能为空!"); return CreateBlankTexture(); } try { // 1. 使用QRCoder生成二维码数据 using (QRCodeGenerator qrGenerator = new QRCodeGenerator()) using (QRCodeData qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel)) using (PngByteQRCode qrCode = new PngByteQRCode(qrCodeData)) { // 2. 获取PNG格式的字节流 byte[] pngBytes = qrCode.GetGraphic(pixelsPerModule); // 3. 将字节流加载到Unity的Texture2D中 Texture2D tex = new Texture2D(2, 2); // 临时尺寸,LoadImage会覆盖 if (tex.LoadImage(pngBytes)) { tex.filterMode = FilterMode.Point; // 使用点过滤,保持像素清晰 tex.Apply(); return tex; } else { Debug.LogError("从字节流加载纹理失败。"); return CreateBlankTexture(); } } } catch (Exception e) { Debug.LogError($"生成二维码时发生异常: {e.Message}"); return CreateBlankTexture(); } } /// <summary> /// 为图片URL生成二维码纹理 /// </summary> /// <param name="imageUrl">网络图片的完整URL</param> /// <param name="pixelsPerModule">每个模块的像素大小</param> /// <returns>生成的Texture2D</returns> public static Texture2D GenerateQRCodeForImageUrl(string imageUrl, int pixelsPerModule = 10) { // 直接将URL作为文本编码 return GenerateQRCodeTexture(imageUrl, pixelsPerModule); } /// <summary> /// 为视频URL生成二维码纹理(编码视频链接) /// </summary> /// <param name="videoUrl">网络视频的URL或视频页面链接</param> /// <param name="pixelsPerModule">每个模块的像素大小</param> /// <returns>生成的Texture2D</returns> public static Texture2D GenerateQRCodeForVideoUrl(string videoUrl, int pixelsPerModule = 10) { // 直接将视频URL作为文本编码 return GenerateQRCodeTexture(videoUrl, pixelsPerModule); } /// <summary> /// 生成一个自定义协议二维码(用于触发应用内特定行为,如打开本地视频) /// </summary> /// <param name="scheme">协议头,如 myapp://</param> /// <param name="action">行为,如 openVideo</param> /// <param name="parameters">参数字典</param> /// <param name="pixelsPerModule">每个模块的像素大小</param> /// <returns>生成的Texture2D</returns> public static Texture2D GenerateQRCodeForCustomScheme(string scheme, string action, System.Collections.Generic.Dictionary<string, string> parameters, int pixelsPerModule = 10) { // 构建自定义URL字符串,例如:myapp://openVideo?id=123&path=local/video.mp4 string url = $"{scheme}{action}"; if (parameters != null && parameters.Count > 0) { url += "?"; bool isFirst = true; foreach (var kvp in parameters) { if (!isFirst) url += "&"; url += $"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(kvp.Value)}"; isFirst = false; } } return GenerateQRCodeTexture(url, pixelsPerModule); } /// <summary> /// 将二维码纹理保存为PNG文件(用于编辑器扩展或运行时保存) /// </summary> /// <param name="texture">二维码纹理</param> /// <param name="filePath">完整文件路径</param> public static void SaveTextureToFile(Texture2D texture, string filePath) { if (texture == null) { Debug.LogError("纹理为空,无法保存。"); return; } byte[] bytes = texture.EncodeToPNG(); System.IO.File.WriteAllBytes(filePath, bytes); Debug.Log($"二维码已保存至: {filePath}"); } // 创建一个空纹理,用于错误处理 private static Texture2D CreateBlankTexture(int size = 256) { Texture2D tex = new Texture2D(size, size); Color[] colors = new Color[size * size]; for (int i = 0; i < colors.Length; i++) { colors[i] = Color.white; } tex.SetPixels(colors); tex.Apply(); return tex; } }代码关键点解析:
using System.Drawing:QRCoder内部使用了System.Drawing的Bitmap等类来构建图像。但在Unity环境下,我们最终需要的是Texture2D。所以我们的流程是:QRCoder生成PNG字节流 -> Unity用Texture2D.LoadImage加载。这避免了直接操作Bitmap带来的兼容性问题。pixelsPerModule:这是控制二维码“密度”的关键参数。一个“模块”对应二维码中的一个黑白小方块。这个值越大,生成的图片分辨率越高,越清晰,但纹理尺寸也越大。通常设置为5-20之间,在移动设备屏幕上,10是一个兼顾清晰度和性能的常用值。ECCLevel(纠错等级):这是二维码的容错能力。分为L(约7%)、M(约15%)、Q(约25%)、H(约30%)。等级越高,二维码能被遮挡或污损的面积越大,但编码相同数据所需的模块数也越多(二维码会更复杂)。对于普通文本,使用 M (Medium) 级别是一个很好的平衡。FilterMode.Point:这是非常重要的一步!二维码是像素艺术,必须使用FilterMode.Point(点过滤)来确保渲染时像素边缘锐利,不使用任何插值模糊。否则二维码会变得难以识别。- 自定义协议:
GenerateQRCodeForCustomScheme方法展示了如何生成一个用于触发应用内行为的二维码。例如,扫描后可以打开应用内的某个界面、播放指定的本地视频文件等。这需要你的应用注册相应的URL Scheme(移动端)或自定义协议(桌面端)。
3.3 在Unity中快速使用与测试
创建一个测试脚本QRCodeTester.cs,挂载到任意GameObject上(如Main Camera)。
using UnityEngine; using UnityEngine.UI; // 如果需要显示在UI上 public class QRCodeTester : MonoBehaviour { public RawImage qrCodeDisplay; // 在UI上显示 public Renderer targetRenderer; // 在3D物体上显示(可选) [Header("测试内容")] public string testText = "Hello, Unity QR Code!"; public string testImageUrl = "https://example.com/my-image.jpg"; public string testVideoUrl = "https://example.com/my-video.mp4"; [Header("生成设置")] public int pixelsPerModule = 10; void Start() { // 示例1:生成文本二维码并显示在UI上 if (qrCodeDisplay != null) { Texture2D tex = QRCodeTool.GenerateQRCodeTexture(testText, pixelsPerModule); qrCodeDisplay.texture = tex; } // 示例2:生成图片URL二维码并应用到3D物体材质上 if (targetRenderer != null) { Texture2D tex = QRCodeTool.GenerateQRCodeForImageUrl(testImageUrl, pixelsPerModule); targetRenderer.material.mainTexture = tex; } // 示例3:保存视频URL二维码到本地(在编辑器下运行) #if UNITY_EDITOR Texture2D videoQRCode = QRCodeTool.GenerateQRCodeForVideoUrl(testVideoUrl, pixelsPerModule); string savePath = Application.dataPath + "/../SavedQRCode.png"; // 保存到项目根目录的同级 QRCodeTool.SaveTextureToFile(videoQRCode, savePath); #endif // 示例4:生成自定义协议二维码 var parameters = new System.Collections.Generic.Dictionary<string, string> { {"id", "1001"}, {"type", "video"}, {"path", "StreamingAssets/intro.mp4"} }; Texture2D customQR = QRCodeTool.GenerateQRCodeForCustomScheme("myunityapp://", "openAsset", parameters, pixelsPerModule); // 可以将customQR赋值给另一个RawImage或Renderer进行查看 } }将QRCodeTester脚本挂载到场景中,并将一个带有RawImage组件的UI对象拖拽到qrCodeDisplay字段。运行游戏,你就能立刻在UI上看到生成的“Hello, Unity QR Code!”二维码了。用手机扫码软件(如微信、支付宝)扫一下,应该能正确识别出这段文本。
4. 高级功能与性能优化实战
基础功能跑通后,我们需要考虑更复杂的生产环境需求,比如生成大量二维码、动态更新内容、以及优化性能。
4.1 动态更新与异步生成
在VR展厅应用中,每个展品都需要一个实时更新的二维码(内容可能是当前展品的详细介绍页URL)。我们不可能每帧都生成,需要动态更新且避免卡顿。
using System.Collections; using UnityEngine; public class DynamicQRCodeDisplay : MonoBehaviour { public RawImage displayImage; public string baseUrl = "https://my-exhibition.com/item?id="; public int currentItemId = 1; public float updateInterval = 5.0f; // 每5秒更新一次 private Texture2D _currentQRTexture; private Coroutine _updateCoroutine; void Start() { StartUpdatingQRCode(); } void StartUpdatingQRCode() { if (_updateCoroutine != null) StopCoroutine(_updateCoroutine); _updateCoroutine = StartCoroutine(UpdateQRCodeRoutine()); } IEnumerator UpdateQRCodeRoutine() { while (true) { string fullUrl = baseUrl + currentItemId; // 使用协程进行异步生成,避免在主线程进行大量计算导致卡顿 yield return StartCoroutine(GenerateQRCodeAsync(fullUrl)); yield return new WaitForSeconds(updateInterval); currentItemId++; // 模拟切换到下一个展品 } } IEnumerator GenerateQRCodeAsync(string text) { // 在实际项目中,对于非常复杂的二维码或低端设备,可以将生成逻辑放到另一个线程。 // 这里使用协程模拟一个“耗时”操作,并在一帧后应用结果。 Texture2D newTex = null; bool isDone = false; // 在实际实现中,这里可以调用一个在后台线程运行的方法 // 为了示例,我们直接在主线程生成,但用yield return null来分割帧 System.Threading.Tasks.Task.Run(() => { newTex = QRCodeTool.GenerateQRCodeTexture(text); isDone = true; }); while (!isDone) { yield return null; // 等待后台任务完成 } // 销毁旧的纹理,避免内存泄漏 if (_currentQRTexture != null) Destroy(_currentQRTexture); _currentQRTexture = newTex; displayImage.texture = _currentQRTexture; Debug.Log($"二维码已更新为: {text}"); } void OnDestroy() { if (_updateCoroutine != null) StopCoroutine(_updateCoroutine); if (_currentQRTexture != null) Destroy(_currentQRTexture); } }注意:Unity的
Texture2D不是线程安全的。上述示例中,我们在另一个线程(Task.Run)中调用QRCodeTool.GenerateQRCodeTexture,是因为该工具内部最终在GetGraphic和LoadImage阶段会涉及大量计算和内存分配,这些操作在非主线程执行可以避免帧率下降。但是,对Texture2D的赋值(displayImage.texture = ...)必须在主线程进行。这是一种简化的异步模式,对于更复杂的场景,可能需要使用UnityWebRequest或Job System进行更精细的线程控制。
4.2 性能优化关键:对象池与纹理复用
如果你需要在同一帧或短时间内生成大量二维码(例如,为游戏中的100个道具批量生成唯一二维码),频繁创建和销毁Texture2D会导致GC(垃圾回收)频繁触发,引起卡顿。这时,对象池是必备的优化手段。
using System.Collections.Generic; using UnityEngine; public class QRCodeTexturePool { private Queue<Texture2D> _texturePool; private int _textureSize; private FilterMode _filterMode; public QRCodeTexturePool(int defaultSize = 256, int preWarmCount = 5, FilterMode filterMode = FilterMode.Point) { _textureSize = defaultSize; _filterMode = filterMode; _texturePool = new Queue<Texture2D>(); // 预热,预先创建一些纹理对象 for (int i = 0; i < preWarmCount; i++) { _texturePool.Enqueue(CreateNewTexture()); } } public Texture2D GetTexture() { if (_texturePool.Count > 0) { return _texturePool.Dequeue(); } // 池中无可用纹理,创建新的 Debug.LogWarning("QR纹理池为空,创建新纹理。考虑增加预热数量。"); return CreateNewTexture(); } public void ReturnTexture(Texture2D texture) { if (texture == null) return; // 可选:将纹理重置为白色或其他默认状态 Color[] resetColors = new Color[texture.width * texture.height]; for (int i = 0; i < resetColors.Length; i++) resetColors[i] = Color.white; texture.SetPixels(resetColors); // texture.Apply(); // 如果立即需要空白状态,可以Apply _texturePool.Enqueue(texture); } private Texture2D CreateNewTexture() { Texture2D tex = new Texture2D(_textureSize, _textureSize, TextureFormat.RGBA32, false); tex.filterMode = _filterMode; tex.wrapMode = TextureWrapMode.Clamp; return tex; } public void ClearPool() { while (_texturePool.Count > 0) { Texture2D tex = _texturePool.Dequeue(); if (tex != null) Object.Destroy(tex); } } } // 使用对象池的工具类方法 public static Texture2D GenerateQRCodeTextureWithPool(string plainText, QRCodeTexturePool pool, int pixelsPerModule = 10, QRCodeGenerator.ECCLevel eccLevel = QRCodeGenerator.ECCLevel.M) { Texture2D targetTex = pool.GetTexture(); // ... 使用QRCoder生成pngBytes ... byte[] pngBytes = GenerateQRCodeBytes(plainText, pixelsPerModule, eccLevel); // 假设这个方法返回字节 // 重用纹理,而不是创建新的 if (targetTex.LoadImage(pngBytes)) { targetTex.Apply(); return targetTex; } // 如果失败,归还纹理并返回null或创建新的 pool.ReturnTexture(targetTex); return null; }通过对象池,我们显著减少了动态内存分配和GC压力。在需要高频生成二维码的场合(如列表界面),性能提升会非常明显。
4.3 生成“图片/视频”二维码的进阶思路
之前提到,直接编码文件数据不现实。但我们可以通过编码“索引”来实现更丰富的功能。例如,在一个博物馆导览App中:
- 离线内容包:将所有图片、视频、音频资源打包,并建立一个索引文件(如JSON)。
- 生成二维码:二维码内容不是文件本身,而是一个唯一ID,如
asset://painting/蒙娜丽莎。 - 应用解析:App扫描二维码后,解析出协议
asset://和路径painting/蒙娜丽莎,然后根据这个路径从本地索引中查找并加载对应的图片资源进行展示。
// 生成指向本地资源的二维码 string localAssetCode = "myapp://openLocalAsset?type=image&id=2024_expo_001"; Texture2D localQR = QRCodeTool.GenerateQRCodeTexture(localAssetCode); // 在App的二维码解析模块中 void OnQRCodeScanned(string result) { if (result.StartsWith("myapp://")) { // 解析URL参数 Uri uri = new Uri(result); string action = uri.Host; // "openLocalAsset" var queryParams = ParseQueryString(uri.Query); // 自定义解析函数 string assetType = queryParams["type"]; string assetId = queryParams["id"]; // 根据assetType和assetId从本地数据库或Resources/StreamingAssets加载资源 LoadLocalAsset(assetType, assetId); } else { // 处理普通网页URL等 Application.OpenURL(result); } }这种方式将二维码变成了一个轻量的“触发器”,真正的内容存储在本地,完美解决了离线、大容量媒体内容的关联问题。
5. 常见问题、排查技巧与避坑指南
在实际使用中,你肯定会遇到各种各样的问题。下面是我在多个项目中总结出来的“血泪经验”。
5.1 二维码生成失败或内容错误
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 生成的纹理全白或全黑。 | 1. 输入的文本为空或为null。 2. Texture2D.LoadImage失败。3. QRCoder生成字节流时出错。 | 1. 在生成前检查字符串是否有效。 2. 检查 pngBytes数组是否为空。尝试将字节流保存到文件,看是否是有效的PNG。3. 用try-catch包裹生成代码,查看具体异常信息。 |
| 二维码可以生成,但扫码软件无法识别。 | 1.纹理过滤模式不是FilterMode.Point,导致像素模糊。2. 二维码尺寸太小,模块数低于识别下限。 3. 纠错等级太低,有轻微污损。 4. 渲染到屏幕时被拉伸或压缩变形。 | 1.务必设置texture.filterMode = FilterMode.Point。2. 增大 pixelsPerModule或确保最终显示尺寸足够大(物理尺寸大于2cm x 2cm)。3. 提高 ECCLevel到Q或H。4. 检查UI Image或3D材质的缩放比例,确保是1:1。 |
| 扫描出的内容与预期不符,多了乱码或少了字符。 | 字符串编码问题。QRCoder默认使用UTF-8。如果包含特殊字符(如Emoji、中文),需要确保编码一致。 | 在生成前,对字符串进行URL编码或确保其是纯ASCII字符。对于复杂文本,可以先用System.Text.Encoding.UTF8.GetBytes检查。 |
| 在WebGL平台上无法生成。 | System.Drawing命名空间在WebGL中不可用。但我们通过PNG字节流绕开了它。如果还有问题,可能是由于QRCoder内部使用了WebGL不支持的API。 | 确保使用的是纯C#版本的QRCoder,并且我们只使用了PngByteQRCode这个类。如果仍失败,考虑寻找一个完全兼容WebGL的纯C#二维码库分支。 |
5.2 性能与内存问题
- 问题:在同一帧生成几十个二维码,游戏明显卡顿。
- 排查:使用Unity Profiler查看CPU开销和GC Alloc。你会发现大量的时间花在
GetGraphic和LoadImage上,并且每帧都有可观的GC内存分配。 - 解决:
- 对象池:如上文所述,这是解决GC问题的首要方案。
- 分帧生成:不要在同一帧完成所有生成。使用协程,每帧生成1-2个。
- 降低质量:在可以接受的范围内,减小
pixelsPerModule。从20降到10,纹理像素数变为1/4,生成和加载速度会快很多。 - 预生成:对于静态内容(如固定的URL),在加载场景时或资源打包阶段就生成好二维码纹理,运行时直接使用。
5.3 平台特定问题
- iOS/Android:确保
Texture2D的格式和读写权限正确。如果需要在运行时保存到相册,需要申请相应的存储权限。 - IL2CPP Stripping:如果发布时遇到因为代码裁剪导致QRCoder功能丢失,需要在
Project Settings -> Player -> Other Settings -> Managed Stripping Level中尝试降低级别(如从High降到 Medium),或者为QRCoder相关代码添加[Preserve]属性。 - 中文内容:包含中文的文本生成二维码,在部分扫码器上可能出现乱码。这是因为不同扫码器对文本编码的解读不同。一个稳妥的做法是,在生成前将字符串进行URL编码:
string encodedText = Uri.EscapeDataString(yourChineseText);。这样二维码里存储的就是百分号编码,绝大多数扫码器都能正确还原。
5.4 一个关于“清晰度”的终极技巧
即使设置了FilterMode.Point,有时在低分辨率屏幕上或UI缩放后,二维码边缘依然会模糊。这是因为纹理像素与屏幕像素没有完美对齐。
解决方案:确保你的二维码显示在Canvas Scaler设置为Scale With Screen Size或Constant Pixel Size的UI下,并且RawImage的RectTransform尺寸是pixelsPerModule * 模块数量的整数倍。例如,你的二维码是25x25个模块,pixelsPerModule=10,那么纹理尺寸是250x250。你最好将RawImage的宽高也设置为250。这样可以最大程度避免缩放带来的模糊。
6. 工具扩展与编辑器集成
为了让这个工具在开发过程中更加顺手,我们可以为其创建简单的编辑器扩展。
6.1 自定义Inspector工具
创建一个编辑器脚本QRCodeToolEditor.cs,放在Assets/Editor/文件夹下。
using UnityEditor; using UnityEngine; [CustomEditor(typeof(QRCodeTester))] // 为我们之前的测试脚本定制Inspector public class QRCodeToolEditor : Editor { public override void OnInspectorGUI() { DrawDefaultInspector(); // 绘制默认字段 QRCodeTester tester = (QRCodeTester)target; GUILayout.Space(10); if (GUILayout.Button("立即生成文本二维码")) { if (tester.qrCodeDisplay != null) { Texture2D tex = QRCodeTool.GenerateQRCodeTexture(tester.testText, tester.pixelsPerModule); tester.qrCodeDisplay.texture = tex; EditorUtility.SetDirty(tester.qrCodeDisplay); // 标记为已修改 } } if (GUILayout.Button("保存当前文本二维码到项目根目录")) { Texture2D tex = QRCodeTool.GenerateQRCodeTexture(tester.testText, tester.pixelsPerModule); string path = EditorUtility.SaveFilePanel("保存二维码", "", "MyQRCode", "png"); if (!string.IsNullOrEmpty(path)) { QRCodeTool.SaveTextureToFile(tex, path); AssetDatabase.Refresh(); // 刷新Unity项目窗口 } // 注意:这里生成的tex需要销毁,避免内存泄漏,简单示例中省略 } } }这样,在Inspector面板上就会多出两个按钮,方便你在编辑模式下快速测试和保存二维码,无需运行游戏。
6.2 创建编辑器窗口批量生成
对于需要批量生成大量二维码(如为游戏内所有物品生成ID二维码)的需求,一个独立的编辑器窗口非常有用。
using UnityEditor; using UnityEngine; using System.Collections.Generic; public class QRCodeBatchGeneratorWindow : EditorWindow { [MenuItem("Tools/二维码批量生成器")] public static void ShowWindow() { GetWindow<QRCodeBatchGeneratorWindow>("批量生成二维码"); } private List<string> sourceTexts = new List<string>() { "Item_001", "Item_002", "Item_003" }; private string exportFolder = "Assets/GeneratedQRCode/"; private int pixelsPerModule = 10; private Vector2 scrollPos; void OnGUI() { GUILayout.Label("批量生成设置", EditorStyles.boldLabel); pixelsPerModule = EditorGUILayout.IntField("模块像素大小", pixelsPerModule); exportFolder = EditorGUILayout.TextField("导出文件夹", exportFolder); GUILayout.Space(10); GUILayout.Label("二维码内容列表 (每行一个)", EditorStyles.boldLabel); EditorGUILayout.BeginVertical(GUI.skin.box); scrollPos = EditorGUILayout.BeginScrollView(scrollPos, GUILayout.Height(150)); string combinedText = EditorGUILayout.TextArea(string.Join("\n", sourceTexts)); sourceTexts = new List<string>(combinedText.Split(new[] { '\n', '\r' }, System.StringSplitOptions.RemoveEmptyEntries)); EditorGUILayout.EndScrollView(); EditorGUILayout.EndVertical(); GUILayout.Space(20); if (GUILayout.Button("生成并导出所有二维码", GUILayout.Height(30))) { if (!System.IO.Directory.Exists(exportFolder)) { System.IO.Directory.CreateDirectory(exportFolder); } for (int i = 0; i < sourceTexts.Count; i++) { string text = sourceTexts[i]; if (string.IsNullOrWhiteSpace(text)) continue; Texture2D tex = QRCodeTool.GenerateQRCodeTexture(text, pixelsPerModule); string fileName = $"QR_{i:000}_{text.Replace("/", "_").Replace(":", "_")}.png"; string fullPath = System.IO.Path.Combine(exportFolder, fileName); QRCodeTool.SaveTextureToFile(tex, fullPath); EditorUtility.DisplayProgressBar("批量生成中", $"正在生成 {fileName}", (float)i / sourceTexts.Count); DestroyImmediate(tex); // 及时销毁临时纹理 } EditorUtility.ClearProgressBar(); AssetDatabase.Refresh(); EditorUtility.DisplayDialog("完成", $"已生成 {sourceTexts.Count} 个二维码到 {exportFolder}", "确定"); } } }这个窗口工具允许你粘贴一列文本(比如从Excel复制的物品ID),然后一键生成所有对应的二维码图片并保存到指定文件夹,极大提升了生产效率。
走到这里,你已经拥有了一个从核心生成、到性能优化、再到编辑器集成的完整Unity二维码解决方案。它免费、强大、且完全受你控制。你可以根据自己项目的具体需求,对这个工具进行任意裁剪和扩展,比如增加Logo中心图、颜色定制等功能。希望这个详尽的实现过程能帮助你,在未来的项目中,轻松扫除二维码相关的所有障碍。
