Unity 2020.3.33集成Manomotion SDK:Android平台AR手势识别实战教程
1. 项目概述与核心价值
最近在捣鼓一个AR小项目,想实现不依赖任何额外硬件,只用手机摄像头就能玩转手势交互。市面上方案不少,但要么收费不菲,要么配置复杂得让人头大。直到我发现了Manomotion SDK,一个宣称免费、支持Unity、能直接在Android和iOS上跑的手势识别方案。这听起来简直是为独立开发者和中小团队量身定做的。但说实话,第一次接触时,看着官方文档和零散的社区帖子,心里还是有点打鼓,尤其是在Unity 2020.3.33这个LTS版本和Android平台上的配置,总怕哪里一步没对就掉坑里。所以,我花了几天时间,把从零开始到成功在真机上跑通手势识别的完整流程彻底走了一遍,过程中踩的坑、总结的技巧,都在这篇保姆级教程里了。无论你是想快速做个AR手势demo验证想法,还是打算在成熟项目里集成低成本的手势功能,这篇基于Unity 2020.3.33和Android平台的实战记录,应该都能让你少走不少弯路。
Manomotion SDK的核心价值在于它把复杂的手势识别算法打包成了一个即插即用的Unity插件。你不需要从头研究计算机视觉和机器学习,只需要在Unity里导入插件、配置几个场景、写点简单的逻辑,就能实时获取到手掌、手指的3D位置、姿态以及预定义的手势(比如握拳、比耶、点赞)。这对于开发教育类AR应用、虚拟试戴、手势控制的游戏或者无障碍交互应用来说,是一个快速启动的利器。而且它支持单双手跟踪,精度和响应速度在主流手机上实测下来,对于很多非极致要求的场景已经足够用了。
2. 环境准备与SDK获取
2.1 基础环境清单
在开始导入SDK之前,确保你的开发环境是干净且匹配的,这能避免至少50%的莫名错误。以下是经过验证的稳定组合:
- Unity编辑器:Unity 2020.3.33f1c2 (LTS)。这是关键,不同的小版本号(如2020.3.30和2020.3.33)在Android构建管线或包管理上可能有细微差别,强烈建议使用这个确切版本。你可以通过Unity Hub进行安装。
- Android开发环境:
- JDK:安装Oracle JDK 8 或 OpenJDK 8。Unity 2020.3版本对JDK 11及以上版本的支持有时会有问题。我使用的是OpenJDK 8 (AdoptOpenJDK)。
- Android SDK & NDK:最简单的方式是在Unity编辑器的
Preferences -> External Tools中,勾选Android部分下的JDK, SDK, NDK的“Install with Unity”选项,让Unity自动安装匹配的版本。这能保证最大兼容性。手动配置路径容易出错。 - Gradle:Unity通常会内置一个Gradle版本用于构建。保持默认即可,除非你有特殊需求。
- 测试设备:准备一部Android手机(建议Android 9.0及以上),并开启开发者选项和USB调试。这是后续真机测试的必需品。
注意:千万不要在环境没配好时就急着导入SDK。我曾经试过先导SDK再配Android环境,结果Unity项目设置里一堆红色错误,清理起来非常麻烦。正确的顺序是:装好Unity -> 配置好External Tools里的Android路径 -> 再处理SDK。
2.2 获取Manomotion SDK
Manomotion SDK目前主要通过其官方网站提供下载。你需要注册一个免费账户,登录后才能在开发者后台下载SDK包。通常下载下来的是一个.unitypackage文件,这是Unity的标准插件格式。这里有个小技巧:留意你下载的SDK版本号,最好去官方文档或下载页面看一眼它声明的兼容Unity版本。虽然Manomotion对2020.3.x系列支持较好,但确认一下能更安心。
2.3 创建与设置Unity项目
打开Unity Hub,创建一个新的3D项目。项目名称和位置按你喜欢来定。创建完成后,第一件事不是导入SDK,而是先设置项目的基本构架:
- 目标平台切换:在
File -> Build Settings中,选择Android平台,然后点击Switch Platform。这个过程可能会花点时间,Unity需要重新导入一些资源以适应Android。 - Player Settings关键配置:切换到Android平台后,点击
Player Settings,以下几个设置必须检查:- Other Settings 部分:
- Minimum API Level:设置为
Android 7.0 ‘Nougat’ (API level 24)或更高。Manomotion需要一些较新的系统API。 - Target API Level:设置为与你测试手机系统相匹配的API级别,或直接选择
Automatic。 - Scripting Backend:选择
IL2CPP。这是发布到移动平台(尤其是64位)的推荐且更稳定的后端。 - Target Architectures:勾选
ARM64。现在的手机基本都是64位系统,勾选这个能确保性能和应用商店的兼容性。
- Minimum API Level:设置为
- XR Settings 部分(如果存在):由于我们做的是基于摄像头的AR,并非必须依赖Unity的AR Foundation等XR插件,所以这部分通常保持默认即可。但如果你后续要结合ARCore使用,则需要在这里启用。
- Other Settings 部分:
3. SDK导入与基础配置
3.1 导入Unity Package
环境准备就绪后,就可以导入SDK了。在Unity编辑器中,选择Assets -> Import Package -> Custom Package...,然后找到你下载的.unitypackage文件。导入时,会弹出一个窗口,显示包内所有文件。通常,全部勾选并导入即可。导入过程可能会提示一些关于API兼容性或命名空间的警告,只要不是错误(Error),可以先忽略。
导入完成后,你的Project窗口的Assets文件夹下,应该会出现一个名为ManoMotion或类似的文件夹。这就是SDK的核心所在。里面通常包含Scripts(核心脚本)、Prefabs(预制体)、Materials(材质)、Shaders(着色器)以及Documentation(文档)等子文件夹。
3.2 配置第一个手势识别场景
Manomotion SDK的设计思路是提供即用的预制件(Prefab)和管理器(Manager)。配置场景最快的方法如下:
- 清空示例场景:创建一个新的场景或使用默认场景,删除场景中自带的
Main Camera和Directional Light(我们稍后会用SDK的预制体替代)。 - 拖入核心预制体:在
ManoMotion/Prefabs文件夹下,找到名为ManoMotion或ManoManager的预制体,将其拖入场景(Hierarchy)中。这个预制体是手势识别系统的中枢。 - 检查自动生成的摄像机:拖入预制体后,你可能会发现场景里自动出现了一个新的摄像机(比如叫
ManoCamera),或者原预制体下包含一个摄像机子物体。这个摄像机会被脚本用来渲染手机摄像头画面。 - 配置ManoManager脚本:选中场景中的ManoMotion预制体,在Inspector面板中,你会看到挂载的
ManoManager脚本。这里有一些关键参数:- Camera:通常会自动关联上场景中那个专用的摄像机。如果没有,手动拖拽赋值。
- Target Framerate:建议设置为
60,以获得更流畅的手部跟踪体验。 - 其他高级设置(如手势平滑度、检测置信度阈值):首次运行时可以先保持默认,后续根据效果微调。
3.3 添加可视化反馈(可选但推荐)
为了直观地看到手势识别结果,SDK通常提供一些可视化预制体:
- 手部网格/骨架:在
ManoMotion/Prefabs/Visualization路径下,寻找类似Hand Mesh、Hand Skeleton或Wrist Information的预制体。将其拖入场景。 - 关联到管理器:通常你需要将这个可视化预制体拖拽到
ManoManager脚本的某个公开字段上,例如Hand Visualization Prefab。这样,当SDK检测到手部时,就会在对应的3D位置实例化这个可视化模型。 - 手势状态UI:SDK可能还包含一个用于在屏幕上显示当前识别到的手势(如“FIST”、“PINCH”)的UI预制体。同样找到并拖入场景的Canvas下,并按照脚本要求进行关联。
完成以上步骤后,点击Unity的播放按钮,在Game视图里,你应该就能看到模拟的手机摄像头视图(可能是测试图像或颜色背景)。但目前还看不到手势识别效果,因为我们需要构建到真机。
4. Android平台构建与部署
4.1 项目构建设置复查
在打包前,再次确认Build Settings和Player Settings:
- Build Settings:确保当前场景已被添加到
Scenes In Build列表中(拖入或点击Add Open Scenes)。 - Player Settings -> Other Settings:
- Package Name:修改为一个唯一的反向域名格式,如
com.yourcompany.handdemo。 - Version:设置应用版本。
- Write Permission:确保
External (SDCard)写入权限被勾选。虽然手势识别本身不一定需要,但SDK或你的应用可能需要临时存储数据。 - Internet Access:如果需要,可以勾选
Required。
- Package Name:修改为一个唯一的反向域名格式,如
4.2 解决可能的编译错误与警告
首次为Android构建时,可能会遇到一些错误或警告,常见的有:
- .NET版本/API兼容性警告:如果SDK使用了较新的C#特性,而项目设置是较旧的
.NET Standard 2.0,可能会报警告。在Player Settings -> Other Settings -> Configuration中,将Api Compatibility Level暂时改为.NET 4.x通常可以解决。但要注意,这可能会增加包体大小。 - AndroidManifest冲突:如果导入的SDK自带
AndroidManifest.xml文件,可能会与Unity自动生成的冲突。Manomotion SDK通常以插件形式提供,其Manifest配置会通过Plugins/Android文件夹自动合并。如果构建失败提示Manifest错误,可以尝试删除项目中的Assets/Plugins/Android/AndroidManifest.xml(如果有),让Unity重新生成,但更安全的方法是检查SDK文档,看是否需要手动合并某些权限(如摄像头权限)。 - 缺失的Gradle依赖:某些SDK版本可能需要额外的Gradle仓库或依赖。这通常会在
Assets/Plugins/Android下的mainTemplate.gradle或baseProjectTemplate.gradle文件中配置。Manomotion一般不需要,但如果构建失败提示找不到类,可以检查这些文件。
4.3 构建APK与真机安装
- 用USB数据线连接你的Android手机,并确保手机已开启“USB调试”(在开发者选项中)。
- 在Unity的
Build Settings窗口中,点击Build And Run。选择一个位置保存APK文件。 - Unity会开始编译、打包,并自动将APK安装到已连接的手机上。第一次构建可能会花费几分钟时间。
- 安装完成后,手机会自动启动应用。请立即在手机上授予应用“相机”权限,这是手势识别能工作的前提。
5. 核心功能实现与脚本交互
5.1 理解SDK的数据流
当应用在手机上运行,你把手放在摄像头前时,Manomotion SDK内部的工作流程大致如下:
- 图像采集:
ManoManager控制摄像头,获取实时视频帧。 - 手部检测与跟踪:SDK的核心算法在后台运行,从视频帧中检测手部区域,并持续跟踪其运动。
- 数据解析:算法会计算出丰富的骨骼信息,包括手掌中心(Wrist)、21个手部关键点(类似MediaPipe的手部模型)的3D位置、手的整体边界框、手势分类结果等。
- 事件派发:这些数据被封装成结构体(如
HandInfo),并通过C#事件或回调函数的方式,提供给开发者使用。
你的代码主要就是订阅这些事件,然后根据获取到的HandInfo数据来驱动游戏逻辑。
5.2 编写第一个手势交互脚本
让我们创建一个简单的脚本,当用户做出“握拳”手势时,让场景中的一个物体变色。
- 创建脚本:在Project窗口中右键,选择
Create -> C# Script,命名为GestureController。 - 编写核心逻辑:双击打开脚本,进行编辑。
using UnityEngine; using Manomotion; // 引入Manomotion的命名空间 public class GestureController : MonoBehaviour { // 一个可拖拽赋值的目标物体,比如一个Cube public GameObject targetObject; // 用于存储物体原始颜色的材质 private Material originalMaterial; // 定义握拳手势对应的枚举值(需参考Manomotion文档) private const ManoGestureContinuous FIST_GESTURE = ManoGestureContinuous.CLOSED_HAND_GESTURE; void Start() { if (targetObject != null) { // 获取物体材质并保存原始颜色 originalMaterial = targetObject.GetComponent<Renderer>().material; } // 订阅Manomotion的手势信息更新事件 // 这是关键步骤!当SDK处理完一帧数据后,会触发此事件 ManomotionManager.OnManoMotionFrameProcessed += HandleManoMotionFrameProcessed; } void OnDestroy() { // 非常重要!在脚本销毁时取消订阅,防止内存泄漏 ManomotionManager.OnManoMotionFrameProcessed -= HandleManoMotionFrameProcessed; } // 事件处理方法 private void HandleManoMotionFrameProcessed() { // 从ManomotionManager的单例中,获取当前帧的手部信息 HandInfo handInfo = ManomotionManager.Instance.Hand_infos[0]; // 通常索引0代表第一只手 // 检查是否检测到了手 if (handInfo.gestureInfo.manoClass != ManoClass.NO_HAND) { // 检查持续手势(如握拳、张开手)是否为握拳 if (handInfo.gestureInfo.manoGestureContinuous == FIST_GESTURE) { ChangeObjectColor(Color.red); } else { // 如果不是握拳,恢复原色 ResetObjectColor(); } // 你也可以获取离散手势(如捏合、滑动) // ManoGestureTrigger triggerGesture = handInfo.gestureInfo.manoGestureTrigger; // 或者获取手部3D位置(手掌中心) // Vector3 wristPosition = handInfo.trackingInfo.skeleton.joints[0]; // 0号关节通常是手腕 } else { // 没有检测到手时,也恢复原色 ResetObjectColor(); } } void ChangeObjectColor(Color newColor) { if (targetObject != null) { targetObject.GetComponent<Renderer>().material.color = newColor; } } void ResetObjectColor() { if (targetObject != null && originalMaterial != null) { targetObject.GetComponent<Renderer>().material.color = originalMaterial.color; } } }- 应用脚本:将
GestureController脚本拖拽到场景中的任意GameObject上(比如一个空的GameObject,命名为GameManager)。 - 关联物体:在Inspector面板中,将场景中你想要变色的那个物体(比如一个Cube)拖拽到脚本的
Target Object字段上。 - 构建并测试:再次构建APK到手机。运行后,当你对着摄像头握拳时,指定的物体应该会变成红色,松开手则恢复原色。
5.3 实现更复杂的交互:手势控制物体移动
理解了基础的数据获取,我们可以更进一步,用手势控制物体的3D位置。例如,用手掌的屏幕坐标来控制一个UI元素的移动。
using UnityEngine; using UnityEngine.UI; // 如果需要控制UI using Manomotion; public class HandUIFollower : MonoBehaviour { public RectTransform uiElementToMove; // 要移动的UI元素 public Camera mainCamera; // 用于坐标转换的摄像机(应该是ManoCamera) void Start() { ManomotionManager.OnManoMotionFrameProcessed += UpdateUIFromHand; if (mainCamera == null) { // 尝试自动查找ManoCamera mainCamera = Camera.main; } } void OnDestroy() { ManomotionManager.OnManoMotionFrameProcessed -= UpdateUIFromHand; } private void UpdateUIFromHand() { HandInfo handInfo = ManomotionManager.Instance.Hand_infos[0]; if (handInfo.gestureInfo.manoClass != ManoClass.NO_HAND && uiElementToMove != null) { // 获取手掌中心的屏幕坐标(归一化,0到1) Vector3 normalizedWristPosition = handInfo.trackingInfo.palmCenter; // 将归一化坐标转换为屏幕像素坐标 float screenX = normalizedWristPosition.x * Screen.width; float screenY = normalizedWristPosition.y * Screen.height; // 将屏幕坐标转换为UI的局部坐标(假设Canvas是Screen Space - Overlay) Vector2 localPos; RectTransformUtility.ScreenPointToLocalPointInRectangle( uiElementToMove.parent as RectTransform, // UI元素的父级RectTransform new Vector2(screenX, screenY), null, // 对于Overlay Canvas,相机参数为null out localPos ); uiElementToMove.localPosition = localPos; } } }这个脚本展示了如何将SDK输出的归一化手部位置,转换到UI坐标系中,实现一个简单的“手指点哪,UI跟到哪”的效果。
6. 性能优化与调试技巧
6.1 性能调优参数
在真机上运行时,你可能会关心帧率和耗电。ManoManager脚本上的一些参数可以帮助你平衡效果和性能:
- Target Framerate:前面提到设为60。如果觉得发热或耗电快,可以尝试降到30。
- Processing Resolution:这是最重要的性能参数之一。它决定了送给手势识别算法的图像分辨率。
HIGH精度最好但最耗性能,LOW最省电但可能丢失细节。对于大多数手机,MEDIUM是一个不错的起点。你可以在脚本运行时动态调整它,观察效果和帧率的变化。 - Smoothing Filter:对手部位置和手势进行平滑滤波。值越大越平滑,但延迟感也会增加。对于需要快速响应的游戏,可以调低;对于需要稳定性的UI控制,可以调高。
- 多手跟踪:默认可能只跟踪一只手。如果你需要同时跟踪两只手,确保在设置中启用了相关选项,但这会显著增加计算量。
6.2 真机调试与日志查看
在真机上调试AR应用,查看日志是定位问题的关键。
- 使用Android Logcat:Unity与Android设备连接时,可以通过
Window -> Analysis -> Android Logcat打开Logcat窗口。确保设备已连接,并在Logcat窗口中选择你的设备。运行应用,所有Unity的Debug.Log以及系统日志都会在这里打印。这是查找崩溃原因、查看SDK内部警告的必备工具。 - 在脚本中添加调试信息:在你的手势脚本中,可以添加
Debug.Log来输出关键数据,比如检测到的手势类型、手部坐标等。这能帮你确认数据流是否正常。Debug.Log($"Detected Gesture: {handInfo.gestureInfo.manoGestureContinuous}, Hand Position: {normalizedWristPosition}"); - 处理权限拒绝:如果你的应用启动后一片黑屏,首先检查手机是否弹出了相机权限请求并被你不小心拒绝了。可以在脚本的
Start方法中,检查应用是否有相机权限,如果没有,可以提示用户或引导用户去设置中开启。Unity的Application.HasUserAuthorization可以用来检查,但在Android上更可靠的方式是尝试访问摄像头,如果失败则给出提示。
6.3 常见问题与解决方案速查表
以下是我在配置和开发过程中遇到的一些典型问题及解决方法:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 构建失败,提示Gradle错误 | JDK、SDK、NDK路径错误或版本不兼容;Gradle版本冲突。 | 1. 检查UnityPreferences -> External Tools中Android路径是否正确且已安装。2. 尝试在 Player Settings -> Publishing Settings中勾选Custom Base Gradle Template,并编辑生成的baseProjectTemplate.gradle文件,调整Gradle版本或仓库源(此操作需谨慎,建议先备份)。3. 最彻底的方法:关闭Unity,删除项目根目录下的 Library、Temp、Obj文件夹,以及[项目名].csproj文件,然后重新打开Unity,让它重新生成。 |
| 应用安装后打开立即闪退 | 缺少必要的Android权限;SDK与目标设备ABI不兼容;脚本存在运行时错误。 | 1. 确认AndroidManifest.xml中已声明相机权限 (<uses-permission android:name="android.permission.CAMERA" />)。2. 检查 Player Settings -> Other Settings -> Target Architectures,确保至少勾选了ARMv7和ARM64。3. 通过Android Logcat查看崩溃瞬间的日志,寻找 NullReferenceException或MissingMethodException等错误信息。 |
| 摄像头画面黑屏,但应用未崩溃 | 相机权限未授予;ManoManager的Camera字段未正确赋值;手机摄像头被其他应用占用。 | 1. 确保手机已为应用开启相机权限。 2. 检查Hierarchy中 ManoMotion预制体上ManoManager脚本的Camera字段是否绑定了场景中的摄像机。3. 杀掉后台可能占用摄像头的应用(如其他相机App、微信视频等)。 |
| 手势检测不稳定或延迟高 | 光线环境太暗;手距离摄像头太近或太远;手机性能不足;Processing Resolution设置过高。 | 1. 确保在光线充足的环境下使用。 2. 让手与摄像头保持约30-80厘米的距离,并尽量在画面中央。 3. 在 ManoManager上将Processing Resolution从HIGH调至MEDIUM或LOW。4. 关闭手机上的省电模式。 |
| 可视化手部模型位置漂移或抖动 | 缺乏平滑滤波;摄像头画面存在运动模糊。 | 1. 适当增加ManoManager上的Smoothing Filter值。2. 尝试在更稳定的环境下操作,避免手部或手机快速移动。 |
脚本中获取不到HandInfo数据 | 事件订阅代码写在了Update中导致重复订阅/取消订阅;ManoMotionManager.Instance为空。 | 1. 确保事件订阅 (OnManoMotionFrameProcessed +=) 只在Start或Awake中执行一次。2. 检查Manomotion SDK的核心管理器是否已正确初始化。通常拖入预制体后会自动完成。可以在 Start中加Debug.Log(ManomotionManager.Instance)来检查。 |
7. 项目打包与发布注意事项
当你完成了开发和测试,准备发布应用时,还有最后几关要过:
- 图标与闪屏:在
Player Settings -> Icon和Splash Image中设置好你的应用图标和启动画面。 - 版本号与包名:再次确认
Player Settings中的Version和Package Name是最终发布版本。 - 减少APK大小:
- 在
Build Settings中点击Export Project而不是直接构建APK,然后用Android Studio打开进行更精细的优化和打包(如启用ProGuard代码混淆和压缩)。 - 在Unity的
Project Settings -> Editor中,可以为Asset Serialization选择Force Text,这有时能略微减小包体,但主要影响的是项目文件本身。 - 检查
Player Settings -> Publishing Settings,如果不需要,可以取消勾选Split APKs by target architecture,但注意兼容性。
- 在
- Keystore签名:发布到应用商店(如Google Play)需要你自己的密钥库(Keystore)文件对APK进行签名。务必妥善保管这个文件和密码,丢失后将无法更新同一个应用。
- 隐私政策:因为你的应用使用了摄像头权限,在大多数应用商店上架时,都需要提供隐私政策链接,说明你如何收集和使用摄像头数据。Manomotion SDK本身可能也要求你在应用中声明其使用。
走完这套流程,从环境搭建、SDK配置、功能开发、调试优化到最终打包,一个基于Manomotion SDK的AR手势交互应用就真正从想法变成了手机上的可运行产品。整个过程最磨人的往往不是代码本身,而是环境配置和真机调试时那些意想不到的兼容性问题。希望这篇超详细的记录,能帮你把路上的坑填平一大半。