Vision Pro/Unity/Poly Spatial开发笔记整理【五】(交互篇)

DEMO和官方文档示例

关于Touch.activeTouches属性

activeTouches属性是Unity增强触摸输入系统的一部分,它提供了一个包含当前帧中所有活跃触摸的只读数组。这个属性允许开发者访问和处理正在进行的触摸事件,无论这些触摸是新开始的、正在移动的,还是在当前帧结束的。使用这个属性,开发者可以检测触摸的开始和结束,从而在游戏或应用程序中实现基于触摸的交互。

源码中的相关注释

/// <summary>
/// 所有在当前帧中正在进行或已结束的触摸。
/// </summary>
/// <remarks>
/// 即使在同一帧中触摸事件也发生了移动(或者甚至是结束/取消),
/// 在一帧内开始的触摸始终会将其阶段设置为 <see cref="TouchPhase.Began"/>。
///
/// 如果一个触摸动作在同一帧内开始并结束,它将在该帧内标记为 <see cref="TouchPhase.Began"/> 阶段,
/// 然后在下一帧中标记为 <see cref="TouchPhase.Ended"/> 阶段。
/// 这种逻辑意味着,活动的触摸数量可能会超过硬件/平台所支持的同时触摸数量。
///
/// 如果一个触摸动作在同一帧内开始并移动,它将在该帧内被记录为 <see cref="TouchPhase.Began"/> 阶段,
/// 然后在下一帧中被记录为带有屏幕移动信息的 <see cref="TouchPhase.Moved"/> 阶段,
/// 除非该触摸动作也在同一帧内结束
///(如果是这种情况,那么 <see cref="phase"/> 将被标记为 <see cref="TouchPhase.Ended"/> 
/// 而不是 <see cref="TouchPhase.Moved"/>)。
///
/// 请注意,此API报告的触摸事件并不一定与 [UnityEngine.Input.touches] 的内容完全一致。
/// 这是因为 `UnityEngine.Input` API和输入系统API在不同的时间点清空它们的输入队列,因此可能对可用的输入有不同的视角。
/// 特别是输入系统事件队列在帧的后期清空,因此可能有更新的输入可用。
/// 例如,在Android上,触摸输入是从单独的UI线程收集的,
/// 并通过一个“后台”事件队列输入到输入系统中,这个队列可以异步收集输入。
/// 由于这种设置,可能在下一帧才会到达 `UnityEngine.Input` 的触摸事件,可能已经到达了输入系统。
///
/// <代码示例>
/// <code>
/// void Awake()
/// {
///     // Enable EnhancedTouch.
///     EnhancedTouchSupport.Enable();
/// }
///
/// void Update()
/// {
///     foreach (var touch in Touch.activeTouches)
///         if (touch.began)
///             Debug.Log($"Touch {touch} started this frame");
///         else if (touch.ended)
///             Debug.Log($"Touch {touch} ended this frame");
/// }
/// </code>
/// </代码示例>
/// </remarks>
/// <exception cref="InvalidOperationException"><c>EnhancedTouch</c> has not been enabled via <see cref="EnhancedTouchSupport.Enable"/>.</exception>
/// <seealso cref="activeFingers"/>
public static ReadOnlyArray<Touch> activeTouches
{
	get
	{
		EnhancedTouchSupport.CheckEnabled();
		// We lazily construct the array of active touches.
		s_GlobalState.playerState.UpdateActiveTouches();
		return new ReadOnlyArray<Touch>(s_GlobalState.playerState.activeTouches, 0, s_GlobalState.playerState.activeTouchCount);
	}
}

可以根据这段代码熟悉一下具体传入参数的不同,可以将Log内容显示到UI上,方便佩戴设备后进行测试。

void Awake()
{
	// Enable EnhancedTouch.
	EnhancedTouchSupport.Enable();
}
      
void Update()
{
	foreach (var touch in Touch.activeTouches)
	if (touch.began)
		Debug.Log($"Touch {touch} started this frame");
	else if (touch.ended)
		Debug.Log($"Touch {touch} ended this frame");
}

UnityEngine.InputSystem.EnhancedTouchUnityEngine.Input的对比

下面是UnityEngine.InputSystem.EnhancedTouch(增强触摸API)和UnityEngine.Input(传统输入API)之间的一些主要区别:

特性/方面增强触摸API (EnhancedTouch)传统输入API (UnityEngine.Input)
触摸历史保留详细的触摸历史记录不保留详细的触摸历史记录
触摸阶段区分"触摸"和"手指",提供丰富的触摸阶段信息提供基本的触摸阶段信息
并发触摸支持查询所有可能的并发触摸接触主要关注当前活跃的触摸
事件系统提供onFingerDownonFingerUponFingerMove事件不直接提供事件系统
触摸ID为每个触摸记录分配唯一ID为每个触摸分配ID,但可能在触摸结束后续用
触摸半径支持获取触摸接触的大小通常不支持触摸接触大小信息

点击/触摸交互

发现问题:

  • 这种方式交互对象会根据手的旋转来控制对象的旋转,所以旋转的时候操作很奇怪
  • 这种旋转适合对象也是任意方向旋转来做,如果对象是需要锁轴的,还是采用其他方式较好

EnhancedSpatialPointerSupport的介绍

旋转示例代码

using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using Unity.PolySpatial.InputDevices;
using UnityEngine;
using UnityEngine.InputSystem.EnhancedTouch;
using Touch = UnityEngine.InputSystem.EnhancedTouch.Touch;
using TouchPhase = UnityEngine.InputSystem.TouchPhase;

public class TargetRotationInput : MonoBehaviour
{
    [SerializeField] private TextMeshPro _textSelectObjectMessage;

    private GameObject _selectedObject;

    void OnEnable()
    {
        EnhancedTouchSupport.Enable();
    }

    private void Update()
    {
        var activeTouches = Touch.activeTouches;
        
        //获取当前的触摸状态
        if (activeTouches.Count > 0)
        {
            var primaryTouchData = EnhancedSpatialPointerSupport.GetPointerState(activeTouches[0]);

            //获取到触摸状态后的对象赋值,根据代码也能看出只能控制单个对象,后续再尝试看能不能对多对象双手控制
            if (activeTouches[0].phase == TouchPhase.Began)
            {
                _selectedObject = primaryTouchData.targetObject != null ? primaryTouchData.targetObject : null;
            }

            //获取到手部交互信息的持续状态,并传入输入的数据进行交互对象的位移和旋转操作
            if (activeTouches[0].phase == TouchPhase.Moved)
            {
                if (_selectedObject != null)
                {
                    _textSelectObjectMessage.text = _selectedObject.name;
                    if (_selectedObject.GetComponent<InteractTarget>() == null) return;
                    var interactTarget = _selectedObject.GetComponent<InteractTarget>();
                    interactTarget.SetPositionAndRotation(
                        primaryTouchData.interactionPosition,
                        primaryTouchData.inputDeviceRotation);
                }
            }
            
            //触摸结束后对交互对象的置空
            if (activeTouches[0].phase == TouchPhase.Ended || activeTouches[0].phase == TouchPhase.Canceled)
            {
                _selectedObject = null;
            }
        }
    }
}

多点触控

SpatialPointerState

要想知道我们能用来做那些截胡,先了解返回的数据能如何使用:
以下是SpatialPointerState结构体的成员列表,以表格形式展示:

成员名称类型描述
LayoutNamestring布局名称,值为 “SpatialPointer”。
kSizeInBytesint结构体大小,值为 100 字节。
interactionIdint交互 ID,使用 InputControl 属性标记为 “Interaction ID”。
interactionPositionVector3交互位置,使用 InputControl 属性标记为 “Interaction Position”。
deltaInteractionPositionVector3交互位置变化量,使用 InputControl 属性标记为 “Delta Interaction Position”。
startInteractionPositionVector3开始交互时的位置,使用 InputControl 属性标记为 “Start Interaction Position”。
startInteractionRayOriginVector3开始交互时的射线起点,使用 InputControl 属性标记为 “Start Interaction Ray Origin”。
startInteractionRayDirectionVector3开始交互时的射线方向,使用 InputControl 属性标记为 “Start Interaction Ray Direction”。
inputDevicePositionVector3输入设备的位置,使用 InputControl 属性标记为 “Input Device Position”。
inputDeviceRotationQuaternion输入设备的旋转,使用 InputControl 属性标记为 “Input Device Rotation”。
targetIdint目标 ID,使用 InputControl 属性标记为 “Target ID”。
modifierKeysushort修饰键,使用 InputControl 属性标记为 “Modifier Keys”。
kindIdbyte指针类型 ID,使用 InputControl 属性标记为 “Kind”。
phaseIdbyte交互阶段 ID,使用 InputControl 属性标记为 “Phase”。
FormatFourCC格式标识符,值为 ‘S’, ‘P’, ‘O’, ‘I’。
targetObjectGameObject目标对象,通过 targetId 获取。
KindSpatialPointerKind指针类型,通过 kindId 获取或设置。
isModifierKeyPressedbool检查是否按下了指定的修饰键。
phaseSpatialPointerPhase交互阶段,通过 phaseId 获取或设置。
isNoneEndedOrCanceledbool判断交互是否处于未结束、结束或取消状态。
isInProgressbool判断交互是否正在进行中。

此外,SpatialPointerState结构体还包含以下方法:

  • SetModifierKey:设置或清除修饰键的状态。
  • GameObject targetObject { get; }:获取与 targetId 关联的游戏对象。
  • SpatialPointerKind Kind { get; set; }:获取或设置指针类型。
  • bool isModifierKeyPressed(SpatialPointerModifierKeys key):检查是否按下了指定的修饰键。
  • SpatialPointerPhase phase { get; set; }:获取或设置交互阶段。
  • bool isNoneEndedOrCanceled { get; }:判断交互是否未结束、结束或取消。
  • bool isInProgress { get; }:判断交互是否正在进行中。

手势交互

XRHand的介绍

以下是Unity中XRHand相关的枚举和工具类的成员,以表格形式展示:

类/枚举成员描述
XRHandJointTrackingStateNone没有数据正在被追踪。
Radius当前关节的半径。
Pose当前关节的姿态。
LinearVelocity当前关节的线性速度。
AngularVelocity当前关节的角速度。
WillNeverBeValid关节被标记为当前提供商的手部布局中不包含的部分。
HandednessInvalid无效的手。
Left左手。
Right右手。
XRHandFingerIDThumb拇指。
Index食指。
Middle中指。
Ring无名指。
Little小指。
XRHandJointIDUtilityToIndex(XRHandJointID jointId)XRHandJointID转换为关节数据数组中的索引。
FromIndex(int index)将索引转换为对应的XRHandJointID
GetFrontJointID(XRHandFingerID fingerId)获取给定XRHandFingerID的掌骨关节ID。
GetBackJointID(XRHandFingerID fingerId)获取给定XRHandFingerID的指尖关节ID。

使用到的相关的类

XRGeneralSettings

以下是XRGeneralSettings类的成员,这个类是Unity XR插件管理的一部分,用于存储和管理XR设置和加载器实例。它提供了启动和停止XR SDK的方法,以及检查是否在启动时自动初始化XR管理器的属性。此外,它还包含了一些编辑器专用的方法,用于处理Unity编辑器的播放模式变化。以表格形式展示:

成员类型成员名称描述
字段k_SettingsKey获取当前加载器设置的键。
字段s_RuntimeSettingsInstance运行时设置实例的引用。
字段m_LoaderManagerInstance管理XR生命周期的XRManagerSettings实例。
字段m_InitManagerOnStart启动时是否自动启动XR管理器的布尔值。
属性Manager获取或设置当前活动的XR管理器。
字段m_XRManager当前活动的XR管理器。
字段m_ProviderIntialized是否初始化了XR提供程序的布尔值。
字段m_ProviderStarted是否启动了XR提供程序的布尔值。
属性Instance获取当前设置实例。
属性AssignedSettings获取或设置分配的XR管理器设置。
属性InitManagerOnStart获取或设置是否在启动时初始化XR管理器。
方法Start启动XR SDK。
方法AttemptInitializeXRSDKOnLoad尝试在加载后初始化XR SDK。
方法AttemptStartXRSDKOnBeforeSplashScreen尝试在启动画面前启动XR SDK。
方法InitXRSDK初始化XR SDK。
方法StartXRSDK启动XR SDK。
方法StopXRSDK停止XR SDK。
方法DeInitXRSDK反初始化XR SDK。

XRHandSubsystem

以下是XRHandSubsystem类的成员,XRHandSubsystem是Unity中用于检测和追踪手及其对应关节姿势数据的子系统。它提供了左手和右手的追踪数据,以及关于哪些关节被支持的信息。通过updatedHands事件,开发者可以接收到手部数据更新的回调,以便在游戏中实现基于手部追踪的交互。此外,XRHandSubsystem允许注册和注销处理器,以便对关节数据进行进一步的处理和分析。以表格形式展示:

类型成员名称描述
字段m_LeftHand被此子系统追踪的左手。
字段m_RightHand被此子系统追踪的右手。
字段m_JointsInLayout指示当前手数据提供者支持的关节。
属性updateSuccessFlags描述最近一次手部更新期间更新了哪些数据。
枚举UpdateSuccessFlags描述在调用期间更新了哪只手的数据。
枚举UpdateType描述一次手部更新的时间。
事件updatedHands每次手部更新时调用的回调。
事件trackingAcquired开始追踪手的根姿势和关节时调用的回调。
事件trackingLost停止追踪手的根姿势和关节时调用的回调。
方法TryUpdateHands请求手数据提供者的更新。
方法RegisterProcessor注册手关节数据处理器。
方法UnregisterProcessor注销手关节数据处理器。
字段m_Processors已注册的手部处理器列表。

XRHandJoint

XRHandJoint结构体提供了一种表示XRHand上的一个关节的方法。它包含了关节的ID、所属手的信息、跟踪状态以及尝试获取关节的半径、姿态、线性速度和角速度的方法。此外,它还提供了相等性测试和哈希代码计算的方法,以及内部用于存储关节数据的字段。

类型成员名称描述
公共属性id获取此关节的ID。
公共属性handedness表示此关节位于哪只手上。
公共属性trackingState表示哪些跟踪数据是有效的。
公共方法TryGetRadius(out float radius)尝试获取关节的半径,如果可用。
公共方法TryGetPose(out Pose pose)尝试获取关节的姿态,如果可用。
公共方法TryGetLinearVelocity(out Vector3 linearVelocity)尝试获取关节的线性速度向量,如果可用。
公共方法TryGetAngularVelocity(out Vector3 angularVelocity)尝试获取关节的角速度向量,如果可用。
公共方法ToString()返回XRHandJoint的字符串表示。
公共方法Equals(XRHandJoint other)测试两个XRHandJoint之间的相等性。
公共方法Equals(object obj)测试对象是否等于此XRHandJoint
公共方法GetHashCode()计算此XRHandJoint的所有字段的HashCode
内部字段m_IdAndHandedness内部表示关节ID和手的标识。
内部字段m_Pose存储关节的姿态。
内部字段m_Radius存储关节的半径。
内部字段m_LinearVelocity存储关节的线性速度。
内部字段m_AngularVelocity存储关节的角速度。
内部字段m_TrackingState存储关节的跟踪状态。
内部常量k_IsRightHandBit用于标识右手的位标志。

XRHandJointID

下面的表格列出了XRHandJointID枚举的所有成员,它定义了手部模型中每个特定关节的标识符。这些标识符用于在Unity XR手部追踪系统中引用和操作手部关节。

成员描述
Invalid无效的ID。
BeginMarker关节的开始标记。
Wrist手腕关节。
Palm手掌。
ThumbMetacarpal拇指掌骨关节。
ThumbProximal拇指近端关节。
ThumbDistal拇指远端关节。
ThumbTip拇指尖。
IndexMetacarpal食指掌骨关节。
IndexProximal食指近端关节。
IndexIntermediate食指中端关节。
IndexDistal食指远端关节。
IndexTip食指尖。
MiddleMetacarpal中指掌骨关节。
MiddleProximal中指近端关节。
MiddleIntermediate中指中端关节。
MiddleDistal中指远端关节。
MiddleTip中指尖。
RingMetacarpal无名指掌骨关节。
RingProximal无名指近端关节。
RingIntermediate无名指中端关节。
RingDistal无名指远端关节。
RingTip无名指尖。
LittleMetacarpal小指掌骨关节。
LittleProximal小指近端关节。
LittleIntermediate小指中端关节。
LittleDistal小指远端关节。
LittleTip小指尖。
EndMarker关节的结束标记。

如何获取XRHandSubsystem和XRHandJoint

如何获取XRHandSubsystem

       _xrHandSubsystem = XRGeneralSettings.Instance?.Manager?.activeLoader?.GetLoadedSubsystem<XRHandSubsystem>();

如何获取XRHandJoint,根据XRHandJointID进行获取指定的手指关节数据

      _leftThumbTipJoint = _xrHandSubsystem.leftHand.GetJoint(XRHandJointID.ThumbTip);

参考文档

Unity Poly Spatial Input开发文档
Unity XR Hands开发文档

  • 26
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值