
简介源自微软创新杯参赛作品这是一款基于C#与Unity 3D游戏引擎、集成Vuforia AR引擎的解密类AR小游戏完整源码。项目定位于计算机、软件、电子信息等专业学生的课程设计、期末大作业及毕业设计参考也适合具备一定Unity基础、希望学习AR交互与解密玩法实现的开发者。压缩包共543个文件约45.31MB核心内容包括44个C#脚本、19个Asset配置文件、Unity场景、FBX模型、材质与着色器、动画、图片以及Vuforia配置覆盖了从场景搭建到AR识别与游戏逻辑的完整工程结构部分目录还附带PDF说明便于对照理解。已有341人学习下载可作为完整参赛方案与实操演练的参考资料。代码结构清晰关键模块如动画控制、相机移动、Vuforia配置等均可在Unity中直接打开运行适合在此基础上二次开发。1. 解密类 AR 小游戏C#、Unity 3D、Vuforia 三层栈怎么分工拿一张印刷卡片对着手机摄像头屏幕上慢慢浮出一组密码锁只有把卡片转到特定角度才能看到藏在图案里的数字——这是「解密类 AR 小游戏」最典型的开局。这个参赛作品选 C#、Unity 3D、Vuforia 三层栈其实是同类作品的常见组合Unity 3D 负责场景、摄像机、UGUI 交互Vuforia 负责把现实里的卡片变成 3D 坐标系里的锚点C# 负责玩法逻辑识别到什么、状态怎么切、机关怎么开。适合准备交毕设、课程设计或者想在作品集里放一个完整 AR Demo 的开发者。下面按 Unity 2021 LTS 之后与 Vuforia Engine 的 API 命名来写你本地版本略旧也能对照。2. Vuforia 识别链路特征点、Image Target 与 C# 回调2.1 Vuforia 不是拍照比对是特征点跟踪很多人第一次做 Vuforia 项目会误以为识别是「摄像头截图后和原图做像素匹配」。实际分两个阶段离线阶段在 Target Manager 上传目标图引擎先对图片提取特征点生成描述文件打进 Unity 包在线阶段摄像头每帧画面做同样的特征提取再和当前加载的数据库比对匹配超过阈值就认定是同一个目标然后估算目标在相机坐标系里的位置和旋转。这个机制决定了选图规则对比度越高、细节越多、纹理重复越少特征点就越多识别越牢靠。白底上一个纯色圆形特征点少得可怜十有八九掉跟踪。Target Manager 给图片打的星级1~5 星旁边显示特征点数量就是对这事最直接的预估。2.2 Unity 侧配置从 License 到 Image Target我一般按五步把 Vuforia 接进场景在 Vuforia 开发者门户创建 License Key。免费的 Development License 只够本机调试打包发给别人装会初始化失败。Target Manager 新建 Database类型选 DeviceAdd Target 时 Type 选 Single Image上传图片后等几分钟看星级和特征点数量。下载该 Database 的 Unity 包并导入。删除场景默认的 Main Camera从 Vuforia 预制体里拖入 AR Camera 和 ImageTarget。在 Image Target Behaviour 组件里选 Database 和 Image Target再在 AR Camera 的 Vuforia Behaviour 里填 License Key。这里最容易漏的是第 4 步场景里留着默认相机会同时渲染两路画面表现就是视图叠了两层性能和显示都出问题。提示Target 的星级是离线特征点质量的预估不等于实机表现。光照一强四星图也可能丢跟踪选图时尽量留余量。2.3 别直接用 DefaultTrackableEventHandlerImageTarget 预制体上的 DefaultTrackableEventHandler 行为太简单识别到就 SetActive(true)丢失就 SetActive(false)。解密玩法需要在状态变化那一刻触发机关、播音效、切 UI所以要删掉它自己继承 ITrackableEventHandlerusing UnityEngine; using Vuforia; public class PuzzleTrackableHandler : MonoBehaviour, ITrackableEventHandler { [SerializeField] private GameObject cluePanel; // 识别成功后显示的线索面板 [SerializeField] private string puzzleId door_01; // 对应关卡配置里的 ID private TrackableBehaviour trackableBehaviour; void Start() { trackableBehaviour GetComponentTrackableBehaviour(); if (trackableBehaviour ! null) { trackableBehaviour.RegisterTrackableEventHandler(this); } } public void OnTrackableStateChanged( TrackableBehaviour.Status previousStatus, TrackableBehaviour.Status newStatus) { if (newStatus TrackableBehaviour.Status.DETECTED || newStatus TrackableBehaviour.Status.TRACKED || newStatus TrackableBehaviour.Status.EXTENDED_TRACKED) { OnTargetFound(); } else if (previousStatus TrackableBehaviour.Status.TRACKED newStatus TrackableBehaviour.Status.NO_POSE) { OnTargetLost(); } } private void OnTargetFound() { cluePanel.SetActive(true); GameEvents.RaisePuzzleActivated(puzzleId); } private void OnTargetLost() { cluePanel.SetActive(false); GameEvents.RaisePuzzleDeactivated(puzzleId); } }OnTrackableStateChanged 的第一个参数是上一帧状态第二个是当前状态。DETECTED 表示刚匹配上TRACKED 表示持续跟踪中EXTENDED_TRACKED 表示目标移出画面但引擎用惯性预测继续给姿态。真正判断「丢失」要看 NO_POSE。丢失分支里带 previousStatus TRACKED是防止目标还没出现过就误触发一次 OnTargetLost。触发机关时用 GameEvents 发事件而不是直接引 GameManager 单例多关卡扩展时不用回头改这个脚本。2.4 选图参数与识别距离的关系字段建议值说明目标图尺寸1024px 以上源图分辨率高离线特征点提取才充分图片评级4 星以上低于 4 星换图别赌实机表现纹理重复尽量避免瓷砖、条纹会产生歧义匹配对比度高对比优先深色物体配浅色背景边缘信息多Image Target 宽度填物理宽度米10cm 卡片填 0.1决定 3D 内容缩放比例最难控制的是打印尺寸和识别距离的关系。经验值A6 卡片约 10cm 宽配 15~25cm 识别距离表现最好放大到 A4识别距离能拉到 60cm 左右但角度一偏就丢。Width 参数不填对模型和卡片的贴合会明显错位。3. 解密玩法的 C# 状态机从识别到开锁的完整流程3.1 为什么流程需要一个状态机而不是一堆 if解密玩法天然线性等目标出现、拿线索、输答案、开锁。直接在 Update 里写判断场景里三个机关五个面板条件分支很快没法维护。AR 场景更麻烦识别状态随时被打断卡片一挪开流程得暂停回来要么恢复要么重置。状态机把「当前处于什么阶段」收敛成一个枚举变量所有转移都进 ChangeState。外部的识别回调、按钮事件只能请求转移不能直接改 UI 和机关。每个状态的进入和退出逻辑有唯一入口出 Bug 时看一眼枚举就知道流程卡在哪。顺带回应一个老问题游戏开发纠结 C 还是 C#在 Vuforia 这条链路里其实没得选Unity 脚本层就是 C#C 只出现在引擎和原生插件里玩法逻辑全写 C# 侧。3.2 状态机类和识别回调怎么接一个 PuzzleStateMachine 类配合上一章的识别回调public enum PuzzleState { Locked, // 目标未被识别 ClueRevealed, // 卡片识别成功线索可见 Solving, // 玩家正在输入答案 Solved // 密码正确机关已触发 } public class PuzzleStateMachine : MonoBehaviour { [SerializeField] private GameObject cluePanel; [SerializeField] private GameObject hintText; [SerializeField] private InputField answerInput; private PuzzleState current PuzzleState.Locked; private string correctAnswer 3179; public void ChangeState(PuzzleState next) { if (next current) return; ExitState(current); current next; EnterState(next); } private void EnterState(PuzzleState state) { switch (state) { case PuzzleState.ClueRevealed: cluePanel.SetActive(true); break; case PuzzleState.Solving: hintText.SetActive(false); answerInput.interactable true; break; case PuzzleState.Solved: answerInput.interactable false; GetComponentAnimator().SetTrigger(Open); break; } } private void ExitState(PuzzleState state) { switch (state) { case PuzzleState.ClueRevealed: cluePanel.SetActive(false); break; case PuzzleState.Solving: answerInput.interactable false; break; } } public void SubmitAnswer(string input) { if (current ! PuzzleState.Solving) return; if (input correctAnswer) { ChangeState(PuzzleState.Solved); } else { GameEvents.RaiseWrongAnswer(); } } }ExitState 先于 EnterState 执行保证界面状态不叠加。SubmitAnswer 第一行判断是防止玩家在 Locked 状态下直接点提交按钮很多 Bug 都出在「按钮可点但状态不对」这种边界。识别回调触发的 OnTargetLost 并不强制重置状态而是通过 GameEvents 通知状态机由状态机决定是暂停还是回退一个状态这样机关动画不会因为卡片短暂移开就永久卡死。3.3 关卡配置用 ScriptableObject 还是 JSON解密游戏一般 5~10 关每关包含目标图、线索文本、正确答案、关联动画。这个数据量用 ScriptableObject 在 Editor 里直接改最省事缺点是发布后想调答案必须重出包打算做每日一关这类运营活动就把配置放到 StreamingAssets 下的 JSON。{ puzzles: [ { id: door_01, targetImage: door_card, hint: 卡片左下角那组三位数字, answer: 3179, nextPuzzle: box_02, rewardItem: key_iron } ] }读取用 JsonUtility.FromJson 解析成 List 不需要引第三方库。注意 JsonUtility 对字段名大小写敏感JSON 里的键要和 C# 字段完全一致。状态机里的 correctAnswer 从配置填充写死在代码里后面改起来很痛苦。3.4 UGUI 交互层别让 Raycaster 挡住 AR 触摸实机常见现象识别正常但点按钮时卡片跟踪抖动。原因是 Canvas 上的 GraphicRaycaster 拦截了触摸。做法Canvas 的 Render Mode 用 Screen Space - Camera 挂在 AR Camera 下而不是 Overlay关掉不需要接收事件的全屏 Image处理触摸前先判断是否落在 UI 上bool IsTouchOnUI(Vector2 screenPos) { if (EventSystem.current null) return false; var data new PointerEventData(EventSystem.current) { position screenPos }; var results new System.Collections.Generic.ListRaycastResult(); EventSystem.current.RaycastAll(data, results); return results.Count 0; }这个方法的开销比 IsPointerOverGameObject 略高但能覆盖 ScrollRect、Button 这类复杂面板。想彻底搞明白 Raycast 为什么拦截触摸直接翻 UGUI 源码里 GraphicRaycaster 的实现比到处查资料管用。4. 真机调试Unity 打包、权限与 Vuforia 常见坑4.1 Android 打包的四个必设项Editor 里跑得好好的真机白屏或黑屏九成是打包配置。我每开一个 AR 项目先把这四项设好Project Settings → Player → Other SettingsMinimum API Level 设 24 以上太多旧 API 的设备跑不动 Vuforia。Scripting Backend 选 IL2CPPTarget Architectures 只勾 ARM64。Mono 编译快但 AR 多线程渲染容易出莫名崩溃。Graphics APIs 里 Vulkan 放第一位OpenGLES 留 fallback。部分国产机型 Vulkan 驱动有兼容问题启动后卡死的把 Vulkan 挪后就行。Active Input Handling 设 Both兼容新旧输入。Vuforia 触控事件和 UGUI 用的输入系统不一致时会出现点击无响应但按钮高亮正常的怪状。4.2 License、相机动态权限与初始化失败Vuforia initialization failed 头号原因是 License Key 没填或填错检查 AR Camera 上 Vuforia Behaviour 的 Key 有没有前后空格。Android 6.0 起是动态权限Unity 打包会自动把 CAMERA 权限写进 Manifest但首次启动仍要在代码里申请#if PLATFORM_ANDROID using UnityEngine.Android; #endif public class PermissionRequest : MonoBehaviour { void Start() { #if PLATFORM_ANDROID if (!Permission.HasUserAuthorizedPermission(Permission.Camera)) { Permission.RequestUserPermission(Permission.Camera); } #endif } }请求要在 Vuforia 初始化之前执行。用户拒绝相机权限后AR Camera 拿不到帧会一直黑屏且无提示最好在权限回调失败时弹说明面板引导去系统设置里开权限。注意真机出问题时先开 logcat 过滤 Vuforia 关键字初始化和 license 相关错误在里面直接可见别反复改 UI。4.3 Extended Tracking 的取舍Image Target 自带 Extended Tracking 开关开启后目标移出画面引擎靠陀螺仪和加速度计继续给姿态持续几百毫秒到几秒具体看设备。「移开卡片看 3D 机关展开」就靠它。但别依赖它做长时间脱离识别姿态会漂。维护多张卡片状态时我习惯用一个 TrackableBehaviour.Status 数组记录每张卡的当前状态每帧对比变化这样日志输出和状态机转移都走同一份数据排查「哪张卡在哪一秒丢了」非常直接。4.4 性能参数表参数推荐值说明Max Simultaneous Tracked Targets2~3同时跟踪目标越多CPU 开销越大Camera ModeOPTIMIZE_SPEED需要画面精细再换 OPTIMIZE_QUALITYWorld Center ModeFIRST_TARGET多目标场景按第一个识别到的目标定世界原点Frame Rate30~60 自适应发热降帧优先保识别别锁死 60同时跟踪目标数最容易踩一个场景挂 5 个 Image Target 全开跟踪老手机直接发热掉帧。解密关卡同时出现的卡片通常不超过两张把其余 Image Target 的跟踪能力调低或按激活时间一次只注册一个目标。5. 识别成功率验证三个能立刻复现的测试方法判断一个 AR 解密 Demo 合不合格不是「我手机能跑」而是换手机、换打印卡片也能跑。我收尾必做三件事。第一件给状态切换打日志。在 PuzzleTrackableHandler 里输出时间和状态名测试时能精确知道哪一帧丢跟踪而不是凭感觉说「好像闪了一下」。帧率高时拼好字符串再打一次避免每帧字符串拼接产生 GC。第二件固定距离测丢帧率。手机架在支架上从 10cm 到 50cm 每 5cm 停 3 秒统计每个距离的识别丢帧率。如果 30cm 附近丢帧率超 20%优先换更清晰的目标图其次提高打印卡片对比度最后才改摄像头曝光参数。第三件验证多目标互不干扰。两张卡片同时在画面里A 正常 B 丢失先查两张图特征构图是否太相似再确认两个 Image Target 是否同一个 Database。不同 Database 并发跟踪某些版本有优先级问题统一放一个 Database 最省事。最后是 Vuforia 特有的小技巧AR Camera 的 World Center Mode 保持 FIRST_TARGET并且让第一个被识别的永远是关卡入口卡片。这样多卡片切换时世界坐标系不跳变3D 机关不会瞬移。配合前面的状态机即使玩家把卡片转走再转回来机关也能在原位重新出现体验上就像整个解密过程从未被打断。本文还有配套的精品资源点击获取