在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)
{
// 处理滑动操作
}