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.EnhancedTouch
和UnityEngine.Input
的对比
下面是UnityEngine.InputSystem.EnhancedTouch
(增强触摸API)和UnityEngine.Input
(传统输入API)之间的一些主要区别:
特性/方面 | 增强触摸API (EnhancedTouch) | 传统输入API (UnityEngine.Input) |
---|---|---|
触摸历史 | 保留详细的触摸历史记录 | 不保留详细的触摸历史记录 |
触摸阶段 | 区分"触摸"和"手指",提供丰富的触摸阶段信息 | 提供基本的触摸阶段信息 |
并发触摸 | 支持查询所有可能的并发触摸接触 | 主要关注当前活跃的触摸 |
事件系统 | 提供onFingerDown 、onFingerUp 和onFingerMove 事件 | 不直接提供事件系统 |
触摸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
结构体的成员列表,以表格形式展示:
成员名称 | 类型 | 描述 |
---|---|---|
LayoutName | string | 布局名称,值为 “SpatialPointer”。 |
kSizeInBytes | int | 结构体大小,值为 100 字节。 |
interactionId | int | 交互 ID,使用 InputControl 属性标记为 “Interaction ID”。 |
interactionPosition | Vector3 | 交互位置,使用 InputControl 属性标记为 “Interaction Position”。 |
deltaInteractionPosition | Vector3 | 交互位置变化量,使用 InputControl 属性标记为 “Delta Interaction Position”。 |
startInteractionPosition | Vector3 | 开始交互时的位置,使用 InputControl 属性标记为 “Start Interaction Position”。 |
startInteractionRayOrigin | Vector3 | 开始交互时的射线起点,使用 InputControl 属性标记为 “Start Interaction Ray Origin”。 |
startInteractionRayDirection | Vector3 | 开始交互时的射线方向,使用 InputControl 属性标记为 “Start Interaction Ray Direction”。 |
inputDevicePosition | Vector3 | 输入设备的位置,使用 InputControl 属性标记为 “Input Device Position”。 |
inputDeviceRotation | Quaternion | 输入设备的旋转,使用 InputControl 属性标记为 “Input Device Rotation”。 |
targetId | int | 目标 ID,使用 InputControl 属性标记为 “Target ID”。 |
modifierKeys | ushort | 修饰键,使用 InputControl 属性标记为 “Modifier Keys”。 |
kindId | byte | 指针类型 ID,使用 InputControl 属性标记为 “Kind”。 |
phaseId | byte | 交互阶段 ID,使用 InputControl 属性标记为 “Phase”。 |
Format | FourCC | 格式标识符,值为 ‘S’, ‘P’, ‘O’, ‘I’。 |
targetObject | GameObject | 目标对象,通过 targetId 获取。 |
Kind | SpatialPointerKind | 指针类型,通过 kindId 获取或设置。 |
isModifierKeyPressed | bool | 检查是否按下了指定的修饰键。 |
phase | SpatialPointerPhase | 交互阶段,通过 phaseId 获取或设置。 |
isNoneEndedOrCanceled | bool | 判断交互是否处于未结束、结束或取消状态。 |
isInProgress | bool | 判断交互是否正在进行中。 |
此外,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
相关的枚举和工具类的成员,以表格形式展示:
类/枚举 | 成员 | 描述 |
---|---|---|
XRHandJointTrackingState | None | 没有数据正在被追踪。 |
Radius | 当前关节的半径。 | |
Pose | 当前关节的姿态。 | |
LinearVelocity | 当前关节的线性速度。 | |
AngularVelocity | 当前关节的角速度。 | |
WillNeverBeValid | 关节被标记为当前提供商的手部布局中不包含的部分。 | |
Handedness | Invalid | 无效的手。 |
Left | 左手。 | |
Right | 右手。 | |
XRHandFingerID | Thumb | 拇指。 |
Index | 食指。 | |
Middle | 中指。 | |
Ring | 无名指。 | |
Little | 小指。 | |
XRHandJointIDUtility | ToIndex(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);
参考文档
更多推荐
所有评论(0)