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

注:全文必须在官方SDK Rokid UXR SDK 3.0.3 已完成 接入指南 后,也就是完成如下章节后才可开始学习 custom.rokid.com/prod/rokid_...

三、核心功能拆解

步骤 1:创建场景基础

1.1 创建并打开场景
  • Assets/Scenes/BoneFollowHand创建好BoneFollowHand这个空场景
  • 拖入RKCameraRig[RKInput] ,配置如下
1.2 创建地面
  1. 在 Hierarchy 中右键 → 3D Object → Cube
  2. 命名为 "Plane",位置配置如下(因为此处需要考虑手掌和实际大小,故设置为长宽30cm的平面,高1cm)

*Unity中1u=实际1米

  1. Plane.mat 材质拖到 Ground 上

这里创建个材质给个颜色即可

步骤 2:设置手追

2.1 创建手部追踪管理器
  1. 在 Hierarchy 中创建空物体:GameObject → Create Empty
  2. 命名为 "HandBoneFollower26"
  3. 添加 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 : 选择 RightHandLeftHand

这里由于骨头模型默认是不带偏移和朝向的,因此为了贴合手掌,将脚本设置改为y轴和z轴旋转90度,这样可以不需要管预制体的旋转或偏移

为了完成只显示手骨,不显示原始手模,需按照官方sdk中如下部分内容复制好

custom.rokid.com/prod/rokid_...

本次要调整的是右手,这里直接把对应渲染器渲染图层设为UI层,即可隐藏渲染,这里放到UI层取决于摄影机设置

步骤 3:创建金币收集游戏

3.1 创建玩家对象
  1. 在 Hierarchy 中创建 Capsule:3D Object → Capsule
  2. 命名为 "Player"
  3. 设置 大小和旋转,因为也要考虑大小,因此这里的大小只有几cm
  1. 为了好看添加材质 Player.mat
3.2 添加玩家眼睛
  1. 创建 Sphere:3D Object → Cube
  2. 命名为 "PlayerEye"
  3. 设为 Player 的子物体
  4. 设置 Transform:
  1. 添加材质 PlayerEye.mat
3.3 配置玩家碰撞器
  1. 选中 Player 对象
  2. 确保有 Capsule Collider 组件
  3. 勾选 Is Trigger(重要!)
  4. 添加 Rigidbody 组件:
    • 这里只需要把位置锁,锁上x和z轴旋转即可,这样就不会东倒西歪了,同时还有重力
3.4 添加金币收集脚本
  1. 选中 Player 对象
  2. 添加 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 创建金币父物体
  1. 创建空物体:GameObject → Create Empty命名为 "CoinObject"
4.2 创建金币
  1. 创建一个圆柱,给压扁就是金币模型

将金币调整大小和位置,适配整体大小,并制作成预制体,如下只需要注意旋转和缩放即可,位置在场景自由拜访

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 开始按钮
  1. 选中 StartUI 下的 Button
  2. 在 Inspector 中找到 Button 组件
  3. 在 OnClick() 事件中:
    • 点击 +
    • 拖入 Player 对象
    • 选择函数:AutoCoinCollector.StartGame()
7.2 重新开始按钮
  1. 选中 EndUI 下的 Button
  2. 在 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 获取骨架关节数据并驱动场景对象;
  • 如何结合团结引擎完成动态物体生成与材质管理;
  • 如何构建一个完整的「开始 → 进行 → 结束」游戏流程。

希望通过这篇实战,你可以对手势追踪有个更好的了解

最终完成安装包

001.apk

工程源码

RokidHandDemoo.zip

相关推荐
晨星shine5 天前
GC、Dispose、Unmanaged Resource 和 Managed Resource
后端·c#
用户298698530145 天前
.NET 文档自动化:Spire.Doc 设置奇偶页页眉/页脚的最佳实践
后端·c#·.net
用户3667462526745 天前
接口文档汇总 - 2.设备状态管理
c#
用户3667462526745 天前
接口文档汇总 - 3.PLC通信管理
c#
Ray Liang6 天前
用六边形架构与整洁架构对比是伪命题?
java·python·c#·架构设计
Scout-leaf9 天前
WPF新手村教程(三)—— 路由事件
c#·wpf
用户298698530149 天前
程序员效率工具:Spire.Doc如何助你一键搞定Word表格排版
后端·c#·.net
mudtools10 天前
搭建一套.net下能落地的飞书考勤系统
后端·c#·.net
玩泥巴的11 天前
搭建一套.net下能落地的飞书考勤系统
c#·.net·二次开发·飞书