【多媒体交互】Unity Kinect实现UI控件的点击

发布于:2025-03-26 ⋅ 阅读:(25) ⋅ 点赞:(0)

在Unity中,通过Kinect实现UI控件的点击功能,主要涉及手部追踪、坐标映射和手势检测三个核心环节。

实现步骤

初始化Kinect与关节追踪

  • 使用KinectManager获取用户ID和手部关节点(如JointType.HandLeft)的坐标。
long userId = _manager.GetPrimaryUserID();
int jointIndex = (int)KinectInterop.JointType.HandLeft;
Vector3 leftHandPos = _manager.GetJointKinectPosition(userId, jointIndex);

坐标转换:从Kinect到UI空间

  • 将手部关节点的世界坐标转换为屏幕坐标,再通过RectTransformUtility转换为UI本地坐标。
Vector3 leftHandScreenPos = Camera.main.WorldToScreenPoint(leftHandPos);
RectTransformUtility.ScreenPointToLocalPointInRectangle(
    (RectTransform)canvas.transform, 
    leftHandScreenPos, 
    null, 
    out leftHandUguiPos
);

检测手部是否位于UI控件内

  • 使用RectTransformUtility.RectangleContainsScreenPoint判断手部是否在目标按钮的矩形区域内。
if (RectTransformUtility.RectangleContainsScreenPoint(btnImage.rectTransform, leftHandScreenPos))

手势触发点击事件

  • 通过KinectInterop.HandState检测握拳动作(HandState.Closed),触发按钮事件。
KinectInterop.HandState leftHandState = _manager.GetLeftHandState(userId);
if (leftHandState == KinectInterop.HandState.Closed) {
    // 触发点击事件,例如调用btnImage.GetComponent<Button>().onClick.Invoke();
}

完整代码

using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;

public class KinectUIController : MonoBehaviour
{
    // 必需组件引用
    [Header("Kinect References")]
    public KinectManager _kinectManager;
    public Camera _uiCamera; // 建议使用正交投影的专用UI相机

    [Header("UI Settings")]
    public Canvas _targetCanvas;
    public RectTransform _handCursor; // 可选的手部光标反馈

    // 手部状态参数
    private long _primaryUserId;
    private Vector3 _handScreenPos;
    private bool _isHandClosed;

    void Update()
    {
        if (_kinectManager == null || !_kinectManager.IsInitialized())
        {
            Debug.LogWarning("KinectManager未初始化");
            return;
        }

        // 1. 获取主用户ID
        _primaryUserId = _kinectManager.GetPrimaryUserID();
        if (_primaryUserId == 0) return;

        // 2. 获取手部坐标和状态
        TrackHand(KinectInterop.JointType.HandLeft);
        // TrackHand(KinectInterop.JointType.HandRight); // 如果需要支持右手
    }

    void TrackHand(KinectInterop.JointType handType)
    {
        // 获取手部骨骼坐标(Kinect原生坐标系)
        Vector3 handPos = _kinectManager.GetJointKinectPosition(_primaryUserId, (int)handType);

        // 转换为屏幕坐标
        _handScreenPos = _uiCamera.WorldToScreenPoint(handPos);

        // 更新手部光标位置(可选视觉反馈)
        if (_handCursor != null)
        {
            Vector2 localPos;
            RectTransformUtility.ScreenPointToLocalPointInRectangle(
                _targetCanvas.GetComponent<RectTransform>(),
                _handScreenPos,
                _uiCamera,
                out localPos
            );
            _handCursor.anchoredPosition = localPos;
        }

        // 3. 检测手部状态
        KinectInterop.HandState handState = (handType == KinectInterop.JointType.HandLeft) ?
            _kinectManager.GetLeftHandState(_primaryUserId) :
            _kinectManager.GetRightHandState(_primaryUserId);

        // 4. UI交互检测
        CheckUIInteraction(handState);
    }

    void CheckUIInteraction(KinectInterop.HandState handState)
    {
        // 手势状态变化检测
        bool isClosing = (handState == KinectInterop.HandState.Closed);
        bool stateChanged = (isClosing != _isHandClosed);
        _isHandClosed = isClosing;

        // 当手部握拳时执行点击检测
        if (isClosing && stateChanged)
        {
            // 获取所有按钮进行检测(实际项目建议使用对象池)
            Button[] buttons = _targetCanvas.GetComponentsInChildren<Button>();
            foreach (Button btn in buttons)
            {
                if (IsPointerOverUIElement(btn.GetComponent<RectTransform>()))
                {
                    // 触发点击事件
                    btn.onClick.Invoke();
                    Debug.Log($"点击按钮: {btn.name}");
                }
            }
        }
    }

    bool IsPointerOverUIElement(RectTransform rectTransform)
    {
        return RectTransformUtility.RectangleContainsScreenPoint(
            rectTransform,
            _handScreenPos,
            _uiCamera
        );
    }

    // 调试用Gizmos显示手部位置
    void OnDrawGizmos()
    {
        if (Application.isPlaying && _kinectManager != null)
        {
            Gizmos.color = Color.green;
            Gizmos.DrawSphere(_uiCamera.ScreenToWorldPoint(_handScreenPos), 0.1f);
        }
    }
}

使用说明

场景配置

  • 将脚本挂载到空物体(如KinectUIController)

  • 拖拽KinectManager实例到_kinectManager字段

  • 设置UI相机(建议新建正交相机专门用于UI渲染)

// 在Inspector面板建议设置:
_uiCamera.orthographic = true;
_uiCamera.transform.position = new Vector3(0, 0, -10);
_uiCamera.nearClipPlane = 0.1f;
_uiCamera.farClipPlane = 20f;
  • 指定目标Canvas(需要设置为Screen Space - Camera模式)
  • 添加手部光标预制体(如圆形Sprite)到_handCursor字段,增强交互反馈
  • 在按钮上添加悬停效果(通过EventTrigger组件实现)

关键注意事项

  • 坐标获取方法
    必须使用GetJointKinectPosition()而非GetJointPosition(),前者直接返回Kinect坐标系的数据,避免因骨骼平滑处理导致的误差。
  • 动态UI处理
    若需检测多个UI控件,可遍历所有按钮的RectTransform,或通过GameObject.Find动态获取UI元素。

优化

  • 使用协程优化检测频率
void Start()
{
    StartCoroutine(CheckButtonsCoroutine());
}

IEnumerator CheckButtonsCoroutine()
{
    while (true)
    {
        // 每0.1秒检测一次,降低性能消耗
        yield return new WaitForSeconds(0.1f);
        CheckUIInteraction();
    }
}
  • 多手势支持扩展
// 添加其他手势检测(如手掌张开)
if (handState == KinectInterop.HandState.Open)
{
    // 实现悬停效果
}

// 滑动手势检测
Vector3 handVelocity = _kinectManager.GetJointVelocity(_primaryUserId, (int)handType);
if (handVelocity.magnitude > 0.5f)
{
    // 处理滑动操作
}