一、项目介绍
本文将带你基于 Rokid UXR SDK 3.0.3 + 团结引擎
开发一个沉浸式的 AR 空间Demo------虚拟中更真实的手。
在这个Demo中:
- 小人会自动寻路找金币
- 玩家移动整个手掌,即可挡住虚拟小人物体;
这是一个非常适合初学者上手的 Rokid AR 实战案例。
二、环境准备
- 设备:Rokid AR Studio眼镜
- SDK:Rokid UXR SDK 3.0.3
- 引擎:团结引擎 1.6.5
注:全文必须在官方SDK Rokid UXR SDK 3.0.3 已完成 接入指南 后,也就是完成如下章节后才可开始学习 ( custom.rokid.com/prod/rokid_... )
三、核心功能拆解
步骤 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脚本
如下为脚本
ini
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_...
本次要调整的是右手,这里直接把对应渲染器渲染图层设为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 组件
- 勾选
Is Trigger(重要!) - 添加 Rigidbody 组件:
-
- 这里只需要把位置锁,锁上x和z轴旋转即可,这样就不会东倒西歪了,同时还有重力

3.4 添加金币收集脚本
- 选中 Player 对象
- 添加
AutoCoinCollector.cs脚本
ini
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,拖入即可

csharp
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个脚本,分别为AutoCoinCollector CoinRotate HandBoneFollower26
用到4个材质,分别为 Coin Plane Player PlayerEye
共3个预制体 Palm Default Coin
四、打包与安装
当我们在编辑器里调试完成后,最后一步就是 打包并安装到 Rokid AR Studio 眼镜上进行真机体验。
五、结束语
到这里,我们已经完成了一个基于 Rokid UXR SDK 3.0.3 + 团结引擎的手追Demo。
这个项目虽然简单,但涵盖了 Rokid AR 开发的核心要点:
- 如何使用 UXR 获取骨架关节数据并驱动场景对象;
- 如何结合团结引擎完成动态物体生成与材质管理;
- 如何构建一个完整的「开始 → 进行 → 结束」游戏流程。
希望通过这篇实战,你可以对手势追踪有个更好的了解
最终完成安装包
工程源码