Rokid UXR 的手势追踪虚拟中更真实的手实战开发【含 工程源码 和 最终完成APK】
一、项目介绍
本文将带你基于Rokid UXR SDK 3.0.3 + 团结引擎
开发一个沉浸式的 AR 空间Demo——虚拟中更真实的手。
在这个Demo中:
小人会自动寻路找金币
玩家移动整个手掌,即可挡住虚拟小人物体;
这是一个非常适合初学者上手的Rokid AR 实战案例。
二、环境准备
设备:Rokid AR Studio眼镜
SDK:Rokid UXR SDK 3.0.3
引擎:团结引擎 1.6.5
注:全文必须在官方SDKRokid UXR SDK 3.0.3 已完成接入指南后,也就是完成如下章节后才可开始学习(custom.rokid.com/prod/rokid_web/c88be4bcde4c42c0b8b53409e1fa1701/pc/cn/8ad2e74dbd7c4f5bb21a964dbdd5512d.html?documentId=965ccb56e0284976ab0c701346f5833e)
三、核心功能拆解
步骤 1:创建场景基础
1.1 创建并打开场景
在
Assets/Scenes/BoneFollowHand创建好BoneFollowHand这个空场景拖入
RKCameraRig和[RKInput],配置如下
1.2 创建地面
在 Hierarchy 中右键 → 3D Object → Cube
命名为 "Plane",位置配置如下(因为此处需要考虑手掌和实际大小,故设置为长宽30cm的平面,高1cm)
*Unity中1u=实际1米
将
Plane.mat材质拖到 Ground 上
这里创建个材质给个颜色即可
步骤 2:设置手追
2.1 创建手部追踪管理器
在 Hierarchy 中创建空物体:GameObject → Create Empty
命名为 "
HandBoneFollower26"添加
HandBoneFollower26.cs脚本
如下为脚本
using Rokid.UXR.Interaction; using Rokid.UXR.Module; using System.Collections.Generic; using UnityEngine; public class HandBoneFollower26 : MonoBehaviour { [Header("默认跟随预制体(Prefab 或 Scene 物体均可)")] public GameObject bonePrefab; [Header("目标手选择")] public HandType handType = HandType.RightHand; [Header("偏移设置")] public Vector3 offset = Vector3.zero; public Vector3 rotationOffsetEuler = Vector3.zero; [System.Serializable] public class CustomBoneObject { public SkeletonIndexFlag flag; public GameObject customPrefab; // 既支持Prefab,也支持Scene物体 } public List<CustomBoneObject> customBones = new List<CustomBoneObject>(); private Dictionary<SkeletonIndexFlag, GameObject> boneFollowers = new Dictionary<SkeletonIndexFlag, GameObject>(); private Dictionary<SkeletonIndexFlag, GameObject> customPrefabMap = new Dictionary<SkeletonIndexFlag, GameObject>(); void Awake() { foreach (var cb in customBones) { if (!customPrefabMap.ContainsKey(cb.flag)) customPrefabMap.Add(cb.flag, cb.customPrefab); } } void Start() { foreach (SkeletonIndexFlag flag in System.Enum.GetValues(typeof(SkeletonIndexFlag))) { GameObject targetPrefab = customPrefabMap.ContainsKey(flag) ? customPrefabMap[flag] : bonePrefab; GameObject obj; // 支持 Scene 物体(不实例化,直接使用原物体) if (targetPrefab.scene.IsValid()) { obj = targetPrefab; // 直接使用场景物体 Debug.Log($"[Follower] 使用场景物体作为骨骼节点: {flag}"); } else { obj = Instantiate(targetPrefab, transform); Debug.Log($"[Follower] 实例化 Prefab 用作骨骼节点: {flag}"); } obj.name = "Follower_" + flag; obj.SetActive(false); boneFollowers.Add(flag, obj); } } void Update() { if (GesEventInput.Instance == null) return; foreach (var pair in boneFollowers) { var flag = pair.Key; var obj = pair.Value; Pose pose = GesEventInput.Instance.GetSkeletonPose(flag, handType); bool detected = pose.position != Vector3.zero; obj.SetActive(detected); if (!detected) continue; Vector3 worldPos = pose.position + pose.rotation * offset; Quaternion worldRot = pose.rotation * Quaternion.Euler(rotationOffsetEuler); obj.transform.SetPositionAndRotation(worldPos, worldRot); } } }2.2 配置 HandBoneFollower26 脚本
基础设置:
Bone Prefab: 拖入
Default(默认骨骼显示物体)
此处由于是自定义手骨,需考虑正常手骨大小,因此修改缩放,下图1是默认手骨,但是掌心骨头是平面型较大,因此需要做个扁方块作为Palm掌心骨,配置如下图
Hand Type: 选择
RightHand或LeftHand
这里由于骨头模型默认是不带偏移和朝向的,因此为了贴合手掌,将脚本设置改为y轴和z轴旋转90度,这样可以不需要管预制体的旋转或偏移
为了完成只显示手骨,不显示原始手模,需按照官方sdk中如下部分内容复制好
custom.rokid.com/prod/rokid_web/c88be4bcde4c42c0b8b53409e1fa1701/pc/cn/8ad2e74dbd7c4f5bb21a964dbdd5512d.html?documentId=8869a42f3935436e90bead5e2c79fb73
本次要调整的是右手,这里直接把对应渲染器渲染图层设为UI层,即可隐藏渲染,这里放到UI层取决于摄影机设置
步骤 3:创建金币收集游戏
3.1 创建玩家对象
在 Hierarchy 中创建 Capsule:3D Object → Capsule
命名为 "Player"
设置 大小和旋转,因为也要考虑大小,因此这里的大小只有几cm
为了好看添加材质
Player.mat
3.2 添加玩家眼睛
创建 Sphere:3D Object → Cube
命名为 "PlayerEye"
设为 Player 的子物体
设置 Transform:
添加材质
PlayerEye.mat
3.3 配置玩家碰撞器
选中 Player 对象
确保有 Capsule Collider 组件
添加 Rigidbody 组件:
这里只需要把位置锁,锁上x和z轴旋转即可,这样就不会东倒西歪了,同时还有重力
3.4 添加金币收集脚本
选中 Player 对象
添加
AutoCoinCollector.cs脚本
using System.Collections.Generic; using UnityEngine; public class AutoCoinCollector : MonoBehaviour { public enum GameState { Start, Playing, End } [Header("UI")] public GameObject startUI; public GameObject endUI; [Header("金币父物体")] public List<Transform> coinParents = new List<Transform>(); [Header("金币Prefab")] public GameObject coinPrefab; [Header("当前金币")] public List<Transform> coins = new List<Transform>(); [Header("移动")] public float moveSpeed = 3f; public float rotateSpeed = 360f; public float arriveDistance = 0.1f; [Header("Debug")] public bool showDebug = true; private Transform currentTarget; private GameState state = GameState.Start; private List<Vector3> coinPositions = new List<Vector3>(); private List<Quaternion> coinRotations = new List<Quaternion>(); void Start() { RecordCoinData(); ShowStartUI(); } void Update() { if (state != GameState.Playing) return; if (currentTarget == null) { FindRandomCoin(); return; } MoveToTarget(); } // ===== UI 控制 ===== void ShowStartUI() { startUI.SetActive(true); endUI.SetActive(false); } void ShowEndUI() { startUI.SetActive(false); endUI.SetActive(true); } // ===== 按钮事件 ===== public void StartGame() { if (showDebug) Debug.Log("开始游戏"); startUI.SetActive(false); endUI.SetActive(false); state = GameState.Playing; FindRandomCoin(); } public void RestartGame() { if (showDebug) Debug.Log("重新开始"); RegenerateCoins(); startUI.SetActive(false); endUI.SetActive(false); state = GameState.Playing; FindRandomCoin(); } // ===== 金币数据 ===== void RecordCoinData() { coins.Clear(); coinPositions.Clear(); coinRotations.Clear(); foreach (Transform parent in coinParents) { foreach (Transform child in parent) { coins.Add(child); coinPositions.Add(child.position); coinRotations.Add(child.rotation); } } } void RegenerateCoins() { coins.Clear(); for (int i = 0; i < coinPositions.Count; i++) { GameObject coin = Instantiate( coinPrefab, coinPositions[i], coinRotations[i] ); coins.Add(coin.transform); } } // ===== AI 移动 ===== void MoveToTarget() { Vector3 dir = currentTarget.position - transform.position; dir.y = 0; float distance = dir.magnitude; if (distance <= arriveDistance) return; Vector3 moveDir = dir.normalized; Quaternion targetRot = Quaternion.LookRotation(moveDir); transform.rotation = Quaternion.RotateTowards( transform.rotation, targetRot, rotateSpeed * Time.deltaTime ); transform.position += transform.forward * moveSpeed * Time.deltaTime; } void FindRandomCoin() { if (coins.Count == 0) { GameOver(); return; } int index = Random.Range(0, coins.Count); currentTarget = coins[index]; if (showDebug) Debug.Log("目标金币: " + currentTarget.name); } void GameOver() { if (showDebug) Debug.Log("所有金币已吃完"); state = GameState.End; ShowEndUI(); } // ===== 吃金币 ===== private void OnTriggerEnter(Collider other) { if (coins.Contains(other.transform)) { if (showDebug) Debug.Log("吃掉金币: " + other.name); coins.Remove(other.transform); Destroy(other.gameObject); currentTarget = null; } } // ===== 绘制路线 ===== void OnDrawGizmos() { if (currentTarget == null) return; Gizmos.color = Color.red; Gizmos.DrawLine(transform.position, currentTarget.position); } }步骤 4:创建金币系统
4.1 创建金币父物体
创建空物体:GameObject → Create Empty命名为 "CoinObject"
4.2 创建金币
创建一个圆柱,给压扁就是金币模型
将金币调整大小和位置,适配整体大小,并制作成预制体,如下只需要注意旋转和缩放即可,位置在场景自由摆放
4.3 配置金币
每个金币需要:进行配置,主要是勾选触发器,同时为了美观,增加一个自转脚本,创建个金色的材质Coin.mat,拖入即可
using UnityEngine; public class CoinRotate : MonoBehaviour { [Header("旋转速度 (度/秒)")] public float rotateSpeed = 180f; [Header("旋转轴")] public Vector3 rotateAxis = Vector3.up; [Header("Debug")] public bool showDebug = false; void Update() { transform.Rotate(rotateAxis * rotateSpeed * Time.deltaTime); if (showDebug) { Debug.Log($"[CoinRotate] {gameObject.name} 正在旋转 速度:{rotateSpeed}"); } } }步骤 5:创建 UI 界面
5.1 创建 Canvas
使用官方预制体,拖入两个,一个命名为开始,一个命名为重新开始,在Canvas下各自新建一个按钮,命名为开始和重新开始即可
步骤 6:配置 AutoCoinCollector 脚本
选中 Player 对象,将脚本直接拖入进去,在 Inspector 中配置:
6.1 UI 设置
Start UI: 拖入 StartUI 整个物体
End UI: 拖入 RestartUI 整个物体
6.2 金币父物体
拖入场景中的CoinObject物体
6.3 金币 Prefab
Coin Prefab: 拖入文件中的预制体
Coin.prefab
6.4 移动参数
这里为了匹配小人,需考虑速度和靠近距离
Move Speed: 0.03(移动速度)
Rotate Speed: 360(旋转速度)
Arrive Distance: 0.001(到达距离)
6.5 调试
Show Debug: 勾选,会显示小人下一个吃哪个金币
步骤 7:连接 UI 按钮事件
7.1 开始按钮
选中 StartUI 下的 Button
在 Inspector 中找到 Button 组件
在 OnClick() 事件中:
点击 +
拖入 Player 对象
选择函数:
AutoCoinCollector.StartGame()
7.2 重新开始按钮
选中 EndUI 下的 Button
在 OnClick() 事件中:
点击 +
拖入 Player 对象
选择函数:
AutoCoinCollector.RestartGame()
完整脚本部分
只用到3个脚本,分别为AutoCoinCollectorCoinRotateHandBoneFollower26
用到4个材质,分别为CoinPlanePlayerPlayerEye
共3个预制体PalmDefaultCoin
四、打包与安装
当我们在编辑器里调试完成后,最后一步就是打包并安装到 Rokid AR Studio 眼镜上进行真机体验。
五 、结束语
到这里,我们已经完成了一个基于 Rokid UXR SDK 3.0.3 + 团结引擎的手追Demo。
这个项目虽然简单,但涵盖了 Rokid AR 开发的核心要点:
如何使用 UXR 获取骨架关节数据并驱动场景对象;
如何结合团结引擎完成动态物体生成与材质管理;
如何构建一个完整的「开始 → 进行 → 结束」游戏流程。
希望通过这篇实战,你可以对手势追踪有个更好的了解
最终完成安装包
001.apk
工程源码
RokidHandDemoo.zip
