参考资料
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使得画布能够接受手柄的射线。