参考资料
XR Interaction Toolkit
https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.1/manual/index.html
概述
InputDevices系列是比较低级的API,XRInteractionToolkit则是对InputDevices的封装,提供了更易用的方式。
命名空间:UnityEngine.XR.Interaction.Toolkit
XRInteractionToolkit的意义
如果直接使用InputDevices写交互式逻辑,就感觉像写汇编语言一样。
XR 交互工具包是一个高级的、基于组件的交互系统,用于创建 VR 和 AR 体验。它提供了一个框架,使 Unity 输入事件中的 3D 和 UI 交互可用。该系统的核心是一组基本的 Interactor 和 Interactable 组件,以及将这两种类型的组件联系在一起的 Interaction Manager。它还包含可用于运动和绘制视觉效果的组件。
XR 交互工具包包含一组支持以下交互任务的组件:
- 
跨平台 XR 控制器输入:Meta Quest (Oculus)、OpenXR、Windows Mixed Reality 等。 
- 
基本对象悬停、选择和抓取 
- 
通过 XR 控制器进行触觉反馈 
- 
视觉反馈(色调/线条渲染)以指示可能的和活跃的交互 
- 
与 XR 控制器的基本画布 UI 交互 
- 
用于与 XR Origin 交互的实用程序,这是一种用于处理静止和房间规模 VR 体验的 VR 摄像机装置 
架构
交互系统的三种状态:悬停hovered、选择select、激活activated。这三种状态同时涉及Interactor和interactable,interactable是被交互对象,interactor是触发交互的对象(可以理解为是手柄)。
hover:射线指向了物体
select:例如grip按下
activated:例如trigger按下
Interactable和Interactor
Interactable和Interactor都会在InteractionManager里面进行注册。
ActionBased和DeviceBased
一些行为,例如Snap Turn Provider,有两种变体:基于动作的行为和基于设备的行为。基于动作的行为使用动作间接读取来自一个或多个控件的输入。基于设备的行为用于[InputDevice.TryGetFeatureValue](https://docs.unity3d.com/ScriptReference/XR.InputDevice.TryGetFeatureValue.html)直接从[InputDevice](https://docs.unity3d.com/ScriptReference/XR.InputDevice.html)行为本身配置的特定控件中读取输入。
ActionBased:基于参考的推荐输入类型行动及其在输入系统中的控制器绑定。
DeviceBased:使用InputDevice
建议您使用基于操作的变体而不是基于设备的变体,以利用输入系统包提供的优势。
使用基于操作的变体就是说绑定输入源的时候使用XRI系列。
使用基于设备的变体就是说绑定输入源的时候直接使用Controller。
版本问题
Pico Integration SDK中XR SDK的Interaction Toolkit版本是0.9-preview版,目前最新的interaction toolkit版本是2.2。所以导入Pico Integration SDK之后,建议升级一下Interaction SDK。
InGameDebugConsole目前似乎不支持新版的InputManager。
在低版本的XR Interaction Toolkit下,有一些组件是不包含的,例如ContinuousMove和ContinuousTurn。
如何升级XR Interaction Toolkit?
在PackageManager里面搜索XR Interaction Toolkit,升级即可,升级完成之后还要安装它提供的一些样例。
升级XR Interaction Toolkit的过程中,注意不要使用新的输入系统(否则会导致很多常用库用不了,现在使用新的输入系统有点早,各个开源库还没有及时跟上)。
包含的组件
https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@2.1/manual/components.html
Controller
手柄相关
XR Controller:包括ActionBased和DeviceBased
XR Controller Recorder
能够把XR控制器的输入转换为事件。
交互
Interactable
为GameObject添加可交互属性
XR Grab Interactable
XR Simple Interactable
AR Interactables
与AR相关的可交互属性
Interactors
AR Gesture Interactor
XR Direct Interactor:直接触摸式交互
XR RayInteractor:射线交互
XR Socket Interactor:
Locomotion
- 
CharactorControllerDriver 
- 
Continuous (Move+Turn) Provider*(Action Based + Device Based) 
- 
LocomotionSystem 
- 
Snap Turn Provider*(ActionBased + Device Based) 
- 
TeleportationProvider 
Visuals
- 
XR Interactor Line Visual:射线形状 
- 
XR Interactor Reticle Visual:准星 
- 
XR Tint Interactable Visual 
其它组件
- 
InputActionManager 
- 
XR Device Simulator 
- 
XR Interaction Manager 
- 
XR Target Filter 
Interactor&Interactable
Interaction Manager
是交互管理器,全局唯一。
Interactable和Interactor之间的交互就是由InteractorManager进行管理的。
在一个场景里面Interactor和Interactable都存在很多个,当Interactor发出事件的时候,这个事件应该交给哪一个Interactable来处理,这就是由Interactor Manager处理的。
InteractionManager所面临的一个算法问题:如何快速地匹配Interactor和Interactable?这看上去跟二分图匹配有点像,其实也是要解决一个连线题。

XRBaseInteractable:一切Interactable的祖先
移动类型MovementType
- 
VelocityTracking 
- 
Kinematic 
- 
Instantaneous 
XRBaseInteractable的成员
- 
InteractionManager:全局Manager,如果没有设置Manager,则自动寻找全局Manager;如果全局没找到,则创建一个InteractionManager。 
- 
colliders:一个Interactable的碰撞器列表,一个Interactable首先应该包含一个collider列表。当手柄与colliders碰撞的时候,才表示交互发生。如果没有给Interactable设置colliders列表,则默认取所有children中的Collider。 
- 
hoveringInteractors:哪些Interactor在我头上晃悠 
- 
isHovered:是否有人在我头上晃悠 
- 
isSelected:当前interactor是否处于选中状态 
事件
- 
OnFirstHoverEnter 
- 
OnHoverEnter 
- 
OnHoverExit 
- 
OnLastHoverExit 
- 
OnSelectEnter 
- 
OnSelectExit 
- 
OnActivate 
- 
OnDeactivate 
方法:
- GetDistanceSqrToInteractor(XRBaseInteractor):获取interactor到我的距离(我是interactable),我身上挂着多个collider,取interactor到我的collider的最短距离。
所有的XRBaseInteractable都会往InteractionManager里面注册Interactable,相当于告诉Manager说:我是可交互对象,当光线射到我身上的时候,把事件告诉我。
当XRBaseInteractable析构的时候,会从InteractionManager里面取消注册这个Interactable。
常见的Interactable
GrableInteractable:可被抓握的对象
BaseTeleportationInteractable:可以去往的对象,与位置相关,下面Locomotion系列会对它进行详细解读。
XRBaseInteractor:一切Interactor都继承它
XRBaseInteractor是所有Interactor的基类。
Interactor是什么呢?手柄就应该具有Interactor组件,这样手柄才能够触发Interactable。
3D空间中的交互是基于Interactor、Interactable、InteracionManager三个东西的,3D作为一种新的交互形式,Unity走在了时代的前列。
成员变量
- 
InteractionManager:全局的交互管理器,它需要在Manager里面注册Interactor。 
- 
InteractionLayerMask:LayerMask 
- 
attachTransform 
- 
XRBaseInteractable selectTarget:当前选中的对象 
- 
XRBaseInteractable startingSelectedInteractable :最初选中的interactable,表示在组件Start的时候默认选中的对象 
- 
AllowHover 
- 
AllowSelect 
事件
- 
HoverEnter 
- 
HoverExit 
- 
SelectEnter 
- 
SelectExit 
常见的Interactor
- 
XRBaseControllerInteractor:基本的手柄Interactor - 
Ray Interactor:可以发射射线的Interactor 
- 
Direct Interactor:必须接触的Interactor 
 
- 
- 
SocketInteractor:? 
XR Grab Interactable:可以被抓握的对象
使用GrabInteractable可以实现隔空取物。隔空取物的两个关键类:
- 
XR Grab Interactable 
- 
XR Ray Interactor 
XR Simple Interactable:简单交互器
一个空的交互器,啥都没有,仅仅是继承了XRBaseInteractable。
public class XRSimpleInteractable : XRBaseInteractable
{
}
XRBaseControllerInteractor:基本的手柄交互器
手柄交互器主要设置select、hover时候的一些音效、触感等,至于具体怎么样才能触发select、hover,则交给Ray Interactor和DirectInteractor去处理。
SelectActionTriggerType:以什么方式触发Selection
bool HideControllerOnSelect:在选中的时候是否隐藏Controller
音效
bool playAudioClipOnSelectEnter:在选中的时候是否播放音频
AudioClip AudioClipForOnSelectEnter:在选中的时候播放什么音频
bool playAudioClipOnSelectExit:在退出选中的时候是否播放音频
AudioClip AudioClipForOnSelectExit :在退出选中的时候播放什么音频
bool playAudioClipOnHoverEnter:
AudioClip AudioClipForOnHoverEnter
bool playAudioClipOnHoverExit
AudioClip AudioClipForOnHoverExit
触觉
- 
触觉的强度、触觉的时长 
- 
selectEnter、selectExit、hoverEnter、hoverExit 
XR Ray Interactor
光线交互器
XR Direct Interactor
DirectInteractor持有一个Interactable数组,并且记录了每个Interactable到它的距离。
这个Interactable数组表示与这个Interactor正在发生交互的对象。
// reusable list of valid targetsList<XRBaseInteractable> m_ValidTargets = new List<XRBaseInteractable>();// reusable map of interactables to their distance squared from this interactor (used for sort)Dictionary<XRBaseInteractable, float> m_InteractableDistanceSqrMap = new Dictionary<XRBaseInteractable, float>();
XR Direct Interactor的函数主要在维护它的这个Interactable数据库:当triggerEnter时,把Interactable添加进去;当triggerExit时,把Interactable移除掉。
CanHover、CanSelect两个函数则为这个Interactor定制它所能交互的对象。
Locomotion 运动系列详解
Locomotion系列的主要作用是控制XRRig的移动,使用一个全局的LocomotionSystem来管理XRRig的位置。
LocomotionSystem持有一个LocomotionProvider,LocomotionProvider提供了XRRig的移动方式。
传送的基本用法
- 
创建一个Plane作为地面 
- 
为地面添加Teleportation Area组件 
- 
全局添加LocomotionSystem,它负责管理全局的移动, 
- 
为XR-Rig添加TeleportationProvider 
- 
为地面的TeleportationArea添加LocomotionSystem 
- 
为LocomotionSystem添加XR-Rig,需要告知它移动哪个物体,应该移动XR-Rig 
- 
为TeleportationProvider指定LocomotionSystem,这一步其实可以省略,因为它会默认寻找全局的LocomotionSystem 
Teleportation Anchor
为地面添加Teleportation Anchor即可,其它步骤同上。
TeleportationArea可以到达任意一个位置,TeleportationAnchor只能到达一个固定位置。
LocomotionSystem:互斥地管理当前的LocomotionProvider
有一个当前的LocomotionProvider,就像搜狗输入法持有一个当前的输入框一样。
当一个LocomotionProvider持有的时间超过了timeOut时间,则清空互斥性(清空provider成员和超时时间)。
在启动的时候自动寻找XROrigin
protected void Awake()
{
    if (m_XROrigin == null)        m_XROrigin = FindObjectOfType<XROrigin>();
}
provider可以向LocomotionSystem请求锁,然后LocomotionSystem会返回请求锁是否成功。
LocomotionSystem的设计非常值得学习,试想,如果没有LocomotinoSystem,任何脚本都能够随意修改Camera的位置,那么很有可能出现Camera一会儿被A修改,一会儿被B修改,会导致XR-Rig的位置忽左忽右、闪闪烁烁。Unity虽然是单线程执行MonoBehavior,但是依旧需要考虑并发问题。
LocomotionProvider:提供BeginLocomotion和EndLocomotion的封装
需要为LocomotionProvider指定LocomotionSystem。即便不设置,也会从全局去自动获取。
一般来说,全局只需要有一个LocomotionSystem。
protected virtual void Awake()
{
    if (m_System == null)        m_System = FindObjectOfType<LocomotionSystem>();
}
LocomotionProvider实现了BeginLocomotion和EndLocomotion两个函数用于回调处理,这两个函数会被LocomotionSystem调用。
protected bool BeginLocomotion()
{
    if (m_System == null)        return false;    var success = m_System.RequestExclusiveOperation(this) == RequestResult.Success;    if (success)        beginLocomotion?.Invoke(m_System);    return success;
}
LocomotionProvider的常见用法如下,使用BeginLocomotion和EndLocomotion来获取锁。
class MyMoveClass:LocomotionProvider{    void myMove(){        if(BeginLocomotion()){            ....            system.xrRig.MoveCamera().....            ....            EndLocomotion();        }    }}
LocomotionProvider的子类:
- 
SnapTurnProvider:使用一个2d轴移动头戴 
- 
TeleportationProvider:实现位置移动 
- 
ContinuousMoveProvider:位置连续移动 
- 
ContinuousTurnProvider:连续转动 
TeleportationProvider:处理TeleportationRequest
继承自LocomotionProvider,这个类只有短短的65行代码。
建议一切的对XRRig的移动都通过TeleportationProvider。
TeleportProvider持有一个TeleportRequest,在Update的时候执行这个TeleportRequest。
public struct TeleportRequest
{
  public Vector3 destinationPosition;//目标位置  public Quaternion destinationRotation;//目标旋转  public Vector3 destinationUpVector;  public Vector3 destinationForwardVector;  public float requestTime;  public MatchOrientation matchOrientation;//是摄像机匹配还是整个Rig都匹配
}
当处理这个请求的时候
switch (m_CurrentRequest.matchOrientation)
{
    case MatchOrientation.None:
        xrRig.MatchRigUp(m_CurrentRequest.destinationUpVector);
        break;
    case MatchOrientation.Camera:
        xrRig.MatchRigUpCameraForward(m_CurrentRequest.destinationUpVector, m_CurrentRequest.destinationForwardVector);
        break;
    //case MatchOrientation.Rig:
    //    xrRig.MatchRigUpRigForward(m_CurrentRequest.destinationUpVector, m_CurrentRequest.destinationForwardVector);
    //    break;
}
Vector3 heightAdjustment = xrRig.rig.transform.up * xrRig.cameraInRigSpaceHeight;
Vector3 cameraDestination = m_CurrentRequest.destinationPosition + heightAdjustment;
xrRig.MoveCameraToWorldLocation(cameraDestination);
BaseTeleportationInteractable
TeleportationArea和TeleportationAnchor的基类。
有了这个类,TeleportationArea和TeleportationAnchor实现起来就变得非常简单了,只需要提供一个GenerateTeleportRequest即可。
现在Unity里面流行一种啰里啰嗦的写法:定义一个成员变量先定义一个protected字段,然后使用get set处理这个字段。
[SerializeField]
[Tooltip("The teleportation provider that this Teleport interactable will communicate Teleportation Requests to.")]
protected TeleportationProvider m_TeleportationProvider = null;
/// <summary>
/// The teleportation provider that this Teleport interactable will communicate Teleportation Requests to.
/// If no teleportation provider is configured, then on awake the base teleportation interactable will attempt to find a teleportation provider to work with.
/// </summary>
public TeleportationProvider teleportationProvider { get { return m_TeleportationProvider; } set { m_TeleportationProvider = value; } }
BaseTeleportationInteractable
TeleportationArea和TeleportationAnchor的基类。
有了这个类,TeleportationArea和TeleportationAnchor实现起来就变得非常简单了,只需要提供一个GenerateTeleportRequest即可。
现在Unity里面流行一种啰里啰嗦的写法:定义一个成员变量先定义一个protected字段,然后使用get set处理这个字段。
[SerializeField]
[Tooltip("The teleportation provider that this Teleport interactable will communicate Teleportation Requests to.")]
protected TeleportationProvider m_TeleportationProvider = null;
/// <summary>
/// The teleportation provider that this Teleport interactable will communicate Teleportation Requests to.
/// If no teleportation provider is configured, then on awake the base teleportation interactable will attempt to find a teleportation provider to work with.
/// </summary>
public TeleportationProvider teleportationProvider { get { return m_TeleportationProvider; } set { m_TeleportationProvider = value; } }
BaseTeleportationInteractable包含的字段说明:
- 
TeleportationProvider 
- 
MatchOrientation:可以指定Camera或者XRRig 
- 
TeleportTrigger:四种触发时机,select(enter+exit);activate+deactivate 
它有一个虚方法:GenerateTeleportRequest,它的所有子类都只需要提供这个方法即可。
这个方法接收两个参数:
- 
XRBaseInteractor: 
- 
Raycast 
BaseTeleportationInteractable的核心逻辑:根据射线,检测是否与colliders相撞。如果找到了collider,则向TeleportationProvider发送移动请求。
bool found = false;
for(int i = 0; i < colliders.Count; i++)
{
    if (colliders[i] == raycastHit.collider)
    {
        found = true;
        break;
    }
}
if (found)
{
    TeleportRequest tr = new TeleportRequest();
    tr.matchOrientation = m_MatchOrientation;
    tr.requestTime = Time.time;
    if (GenerateTeleportRequest(interactor, raycastHit, ref tr))
    {
        m_TeleportationProvider.QueueTeleportRequest(tr);
    }
}
TeleportationArea
设置Request的时候,使用Transform自身的方向。
protected override bool GenerateTeleportRequest(XRBaseInteractor interactor, RaycastHit raycastHit, ref TeleportRequest teleportRequest)
{          
    teleportRequest.destinationPosition = raycastHit.point;
    teleportRequest.destinationUpVector = transform.up; // use the area transform for data.
    teleportRequest.destinationForwardVector = transform.forward;
    teleportRequest.destinationRotation = transform.rotation;
    return true;
}      
TeleportationAnchor
TeleportationAnchor接受一个Transform组件,当跳转的时候,直接跳转到该transform
teleportRequest.destinationPosition = m_TeleportAnchorTransform.position;teleportRequest.destinationUpVector = m_TeleportAnchorTransform.up;teleportRequest.destinationRotation = m_TeleportAnchorTransform.rotation;            teleportRequest.destinationForwardVector = m_TeleportAnchorTransform.forward;
SnapTurnProvider:指定一个Controller(手柄),通过手柄实现转动。
snapTurn:急转弯,快速转身。意思是通过手柄上的摇杆快速实现变向。
SnapTurnProvider也是继承自LocomotionProvider,使用它可以实现用手柄的2D轴控制转向。
- 
turnUsage:控制转动的时候,一定是2D轴。一般的手柄只有一个primaryAxis,但是Unity提供了选项可以使用secondary2DAxis。turnUsage表示使用primaryAxis还是使用secondaryAxis 
- 
Controllers:一个controller数组,表示使用哪一个控制器来执行操作。 
- 
turnAmount:转动量,默认是45度。 
- 
debounceTime:防止调用太频繁,默认500ms 
- 
deadZone:摇杆必须偏移多少才执行转向,跟debounceTime一样,都是为了让输入更精确。 
计算旋转的幅度
只允许左右旋转,当摇杆转向前面或者后面的时候,不执行任何操作。当摇杆向左的时候,则向左转45度;当摇杆向右的时候,则向右转45度。
InputDevice device = controller.inputDevice;
Vector2 currentState;
if (device.TryGetFeatureValue(feature, out currentState))
{
    if (currentState.x > deadZone)
    {
        StartTurn(m_TurnAmount);
    }
    else if (currentState.x < -deadZone)
    {
        StartTurn(-m_TurnAmount);
    }
}
执行旋转
if (Math.Abs(m_CurrentTurnAmount) > 0.0f && BeginLocomotion())
{
    var xrRig = system.xrRig;    if (xrRig != null)    {        xrRig.RotateAroundCameraUsingRigUp(m_CurrentTurnAmount);    }    m_CurrentTurnAmount = 0.0f;    EndLocomotion();
}
ContinuousMoveProvider:连续移动提供者
它能够提供平滑的移动。
protected virtual Vector3 ComputeDesiredMove(Vector2 input)
{
    if (input == Vector2.zero)
        return Vector3.zero;
    var xrOrigin = system.xrOrigin;
    if (xrOrigin == null)
        return Vector3.zero;
    // Assumes that the input axes are in the range [-1, 1].
    // Clamps the magnitude of the input direction to prevent faster speed when moving diagonally,
    // while still allowing for analog input to move slower (which would be lost if simply normalizing).
    var inputMove = Vector3.ClampMagnitude(new Vector3(m_EnableStrafe ? input.x : 0f, 0f, input.y), 1f);
    var originTransform = xrOrigin.Origin.transform;
    var originUp = originTransform.up;
    // Determine frame of reference for what the input direction is relative to
    var forwardSourceTransform = m_ForwardSource == null ? xrOrigin.Camera.transform : m_ForwardSource;
    var inputForwardInWorldSpace = forwardSourceTransform.forward;
    if (Mathf.Approximately(Mathf.Abs(Vector3.Dot(inputForwardInWorldSpace, originUp)), 1f))
    {
        // When the input forward direction is parallel with the rig normal,
        // it will probably feel better for the player to move along the same direction
        // as if they tilted forward or up some rather than moving in the rig forward direction.
        // It also will probably be a better experience to at least move in a direction
        // rather than stopping if the head/controller is oriented such that it is perpendicular with the rig.
        inputForwardInWorldSpace = -forwardSourceTransform.up;
    }
    var inputForwardProjectedInWorldSpace = Vector3.ProjectOnPlane(inputForwardInWorldSpace, originUp);
    var forwardRotation = Quaternion.FromToRotation(originTransform.forward, inputForwardProjectedInWorldSpace);
    var translationInRigSpace = forwardRotation * inputMove * (m_MoveSpeed * Time.deltaTime);
    var translationInWorldSpace = originTransform.TransformDirection(translationInRigSpace);
    return translationInWorldSpace;
}
ContinuousTurnProvider:连续转向提供者
它能够提供平滑的转向,与snapTurnProvider相对应,SnapTurnProvider是立即转身提供器。
把它的Controller的size设置成1,然后把XRRig里面的LeftController设置进去,通过调整左手柄的primaryAxis就能够实现平滑的旋转。
综合的例子
- 
添加XR-Origin 
- 
添加Plane,一个地面,为地面添加TeleportationArea 
- 
为XR-Origin添加LocomotionSystem,TeleportationProvider,ContinuousTurnProvider_Device(绑定左手手柄),ContinuousMoveProvider_Device(绑定右手手柄) 
- 
为XR-Origin的左右手柄绑定Pico的Prefab。 
头戴XRRig
任何GameObject都有自己的X轴、Y轴、Z轴。一个GameObject的up指的就是这个GameObject的Y轴正方向,forward指的就是Z轴正方向。
在空间中,制定up和forward两个方向就能够确定一个GameObject的朝向。
XRRig的代码非常值得阅读,看完能够对向量变换有更深的理解。
XRRig的属性
- 
Rig base game Object:尽量不要设置这个变量,默认就是当前GameObject。Rig表示相机+手柄 
- 
Camera Floor Offset Object:相机的偏移量,不需要设置,默认是当前GameObject 
- 
Camera GameObject:相机对象 
- 
Tracking Origin Mode: 
Rig和Camera的相对位置
主要学习InverseTransformPoint。每个GameObject都有自己的XYZ坐标系,它看其它物体都能够求出其它物体的坐标。
m_CameraGameObject.transform.InverseTransformPoint(m_RigBaseGameObject.transform.position)
public float cameraYOffset { get { return m_CameraYOffset; } set { m_CameraYOffset = value; TryInitializeCamera(); } }
/// <summary>Gets the rig's local position in camera space.</summary>
public Vector3 rigInCameraSpacePos { get { return m_CameraGameObject.transform.InverseTransformPoint(m_RigBaseGameObject.transform.position); } }
/// <summary>Gets the camera's local position in rig space.</summary>
public Vector3 cameraInRigSpacePos { get { return m_RigBaseGameObject.transform.InverseTransformPoint(m_CameraGameObject.transform.position); } }
/// <summary>Gets the camera's height relative to the rig.</summary>
public float cameraInRigSpaceHeight { get { return cameraInRigSpacePos.y; } }
头戴的transform操作:有些操作Rig,有些操作Camera
bool RotateAroundCameraUsingRigUp(float angleDegrees):绕着竖直方向旋转angleDegrees
bool RotateAroundCameraPosition(Vector3 vector, float angleDegrees):绕着直线vector旋转angleDegrees
bool MatchRigUp**(Vector3 destinationUp):将相机的y轴角度设置为destinationUp**
public bool MatchRigUp(Vector3 destinationUp)
{
    if (m_RigBaseGameObject.transform.up == destinationUp)
        return true;
    if (m_RigBaseGameObject == null)
    {
        return false;
    }
    Quaternion rigUp = Quaternion.FromToRotation(m_RigBaseGameObject.transform.up, destinationUp);
    m_RigBaseGameObject.transform.rotation = rigUp * transform.rotation;
    
    return true;
}
bool MatchRigUpCameraForward**(Vector3 destinationUp, Vector3 destinationForward):将相机的up和forward分别调整为destinationUp和destinationForward。**
public bool MatchRigUpCameraForward(Vector3 destinationUp, Vector3 destinationForward)
{
    if (m_CameraGameObject != null && MatchRigUp(destinationUp))
    {
        // project current camera's forward vector on the destination plane, whose normal vector is destinationUp.
        Vector3 projectedCamForward = Vector3.ProjectOnPlane(cameraGameObject.transform.forward, destinationUp).normalized;
        // the angle that we want the rig to rotate is the signed angle between projectedCamForward and destinationForward, after the up vectors are matched. 
        float signedAngle = Vector3.SignedAngle(projectedCamForward, destinationForward, destinationUp);
        RotateAroundCameraPosition(destinationUp, signedAngle);
        return true;
    }
    return false;
}
MatchRigUpRigForward
public bool MatchRigUpRigForward (Vector3 destinationUp, Vector3 destinationForward)
{
    if (m_RigBaseGameObject != null && MatchRigUp(destinationUp))
    {
        // the angle that we want the rig to rotate is the signed angle between the rig's forward and destinationForward, after the up vectors are matched. 
        float signedAngle = Vector3.SignedAngle(m_RigBaseGameObject.transform.forward, destinationForward, destinationUp);
        RotateAroundCameraPosition(destinationUp, signedAngle);
        return true;
    }
    return false;
}
MoveCameraToWorldLocation
将相机移动到特定位置
public bool MoveCameraToWorldLocation(Vector3 desiredWorldLocation)
{
    if (m_CameraGameObject == null)
    {
        return false;
    }
    Matrix4x4 rot = Matrix4x4.Rotate(cameraGameObject.transform.rotation);
    Vector3 delta = rot.MultiplyPoint3x4(rigInCameraSpacePos);
    m_RigBaseGameObject.transform.position = delta + desiredWorldLocation;
    return true;
}
与手柄有关的脚本:XR Controller
属性
- UpdateType:分为Update、BeforeRender、UpdateAndBeforeRender三种,trackedPosDriver需要使用这个字段来判断什么时候采样数据。Update表示Update阶段,BeforeRender表示渲染前。
手柄的模型:
modelPrefab,它是一个Transform类型的变量。
modelTransform:实际上modelPrefab跟modelTransform基本上都是重合的,这个modelTransform的存在使得这两者可以不重合。
三种操作对应的手柄按键:
enum InteractionTypes { select, activate, uiPress };
- 
select:默认是grip,选择物体。 
- 
activate:默认trigger,表示激活物体 
- 
press:UI中的点击操作等,默认是trigger键 
动画:
- 
selectTransition:选中时候的渐变 
- 
DeSelectTransition:取消选中时候的渐变 
Visual:可视化
XR Interactor Line Visual:射线可视化
这个类也是位于UnityEngine.XR.Interaction.Toolkit命名空间下。
手柄射线的可视化
Reticle:准星。
XR Interactor Recicle Visual:准星可视化
准星可视化。
画布TrackedDeviceGraphicRaycaster
TrackedDeviceGraphicRaycaster使得画布能够接受手柄的射线。