first commit
This commit is contained in:
commit
aa44d3ad70
1585 changed files with 277994 additions and 0 deletions
|
@ -0,0 +1,340 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
|
||||
{
|
||||
/// <summary>
|
||||
/// Use this class to mediate the controllers and their associated interactors and input actions under different interaction states.
|
||||
/// </summary>
|
||||
[AddComponentMenu("XR/Action Based Controller Manager")]
|
||||
[DefaultExecutionOrder(k_UpdateOrder)]
|
||||
public class ActionBasedControllerManager : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Order when instances of type <see cref="ActionBasedControllerManager"/> are updated.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Executes before controller components to ensure input processors can be attached
|
||||
/// to input actions and/or bindings before the controller component reads the current
|
||||
/// values of the input actions.
|
||||
/// </remarks>
|
||||
public const int k_UpdateOrder = XRInteractionUpdateOrder.k_Controllers - 1;
|
||||
|
||||
[Space]
|
||||
[Header("Interactors")]
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The GameObject containing the interaction group used for direct and distant manipulation.")]
|
||||
XRInteractionGroup m_ManipulationInteractionGroup;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The GameObject containing the interactor used for direct manipulation.")]
|
||||
XRDirectInteractor m_DirectInteractor;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The GameObject containing the interactor used for distant/ray manipulation.")]
|
||||
XRRayInteractor m_RayInteractor;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The GameObject containing the interactor used for teleportation.")]
|
||||
XRRayInteractor m_TeleportInteractor;
|
||||
|
||||
[Space]
|
||||
[Header("Controller Actions")]
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The reference to the action to start the teleport aiming mode for this controller.")]
|
||||
InputActionReference m_TeleportModeActivate;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The reference to the action to cancel the teleport aiming mode for this controller.")]
|
||||
InputActionReference m_TeleportModeCancel;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The reference to the action of continuous turning the XR Origin with this controller.")]
|
||||
InputActionReference m_Turn;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The reference to the action of snap turning the XR Origin with this controller.")]
|
||||
InputActionReference m_SnapTurn;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The reference to the action of moving the XR Origin with this controller.")]
|
||||
InputActionReference m_Move;
|
||||
|
||||
[Space]
|
||||
[Header("Locomotion Settings")]
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("If true, continuous movement will be enabled. If false, teleport will enabled.")]
|
||||
bool m_SmoothMotionEnabled;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("If true, continuous turn will be enabled. If false, snap turn will be enabled. Note: If smooth motion is enabled and enable strafe is enabled on the continuous move provider, turn will be overriden in favor of strafe.")]
|
||||
bool m_SmoothTurnEnabled;
|
||||
|
||||
public bool smoothMotionEnabled
|
||||
{
|
||||
get => m_SmoothMotionEnabled;
|
||||
set
|
||||
{
|
||||
m_SmoothMotionEnabled = value;
|
||||
UpdateLocomotionActions();
|
||||
}
|
||||
}
|
||||
|
||||
public bool smoothTurnEnabled
|
||||
{
|
||||
get => m_SmoothTurnEnabled;
|
||||
set
|
||||
{
|
||||
m_SmoothTurnEnabled = value;
|
||||
UpdateLocomotionActions();
|
||||
}
|
||||
}
|
||||
|
||||
bool m_Teleporting;
|
||||
|
||||
/// <summary>
|
||||
/// Temporary scratch list to populate with the group members of the interaction group.
|
||||
/// </summary>
|
||||
static readonly List<IXRGroupMember> s_GroupMembers = new List<IXRGroupMember>();
|
||||
|
||||
// For our input mediation, we are enforcing a few rules between direct, ray, and teleportation interaction:
|
||||
// 1. If the Teleportation Ray is engaged, the Ray interactor is disabled
|
||||
// 2. The interaction group ensures that the Direct and Ray interactors cannot interact at the same time, with the Direct interactor taking priority
|
||||
// 3. If the Ray interactor is selecting, all locomotion controls are disabled (teleport ray, move, and turn controls) to prevent input collision
|
||||
void SetupInteractorEvents()
|
||||
{
|
||||
if (m_RayInteractor != null)
|
||||
{
|
||||
m_RayInteractor.selectEntered.AddListener(OnRaySelectEntered);
|
||||
m_RayInteractor.selectExited.AddListener(OnRaySelectExited);
|
||||
}
|
||||
|
||||
var teleportModeActivateAction = GetInputAction(m_TeleportModeActivate);
|
||||
if (teleportModeActivateAction != null)
|
||||
{
|
||||
teleportModeActivateAction.performed += OnStartTeleport;
|
||||
teleportModeActivateAction.canceled += OnCancelTeleport;
|
||||
}
|
||||
|
||||
var teleportModeCancelAction = GetInputAction(m_TeleportModeCancel);
|
||||
if (teleportModeCancelAction != null)
|
||||
{
|
||||
teleportModeCancelAction.performed += OnCancelTeleport;
|
||||
}
|
||||
}
|
||||
|
||||
void TeardownInteractorEvents()
|
||||
{
|
||||
if (m_RayInteractor != null)
|
||||
{
|
||||
m_RayInteractor.selectEntered.RemoveListener(OnRaySelectEntered);
|
||||
m_RayInteractor.selectExited.RemoveListener(OnRaySelectExited);
|
||||
}
|
||||
|
||||
var teleportModeActivateAction = GetInputAction(m_TeleportModeActivate);
|
||||
if (teleportModeActivateAction != null)
|
||||
{
|
||||
teleportModeActivateAction.performed -= OnStartTeleport;
|
||||
teleportModeActivateAction.canceled -= OnCancelTeleport;
|
||||
}
|
||||
|
||||
var teleportModeCancelAction = GetInputAction(m_TeleportModeCancel);
|
||||
if (teleportModeCancelAction != null)
|
||||
{
|
||||
teleportModeCancelAction.performed -= OnCancelTeleport;
|
||||
}
|
||||
}
|
||||
|
||||
void OnStartTeleport(InputAction.CallbackContext context)
|
||||
{
|
||||
m_Teleporting = true;
|
||||
|
||||
if (m_TeleportInteractor != null)
|
||||
m_TeleportInteractor.gameObject.SetActive(true);
|
||||
|
||||
RayInteractorUpdate();
|
||||
}
|
||||
|
||||
void OnCancelTeleport(InputAction.CallbackContext context)
|
||||
{
|
||||
m_Teleporting = false;
|
||||
|
||||
// Do not deactivate the teleport interactor in this callback.
|
||||
// We delay turning off the teleport interactor in this callback so that
|
||||
// the teleport interactor has a chance to complete the teleport if needed.
|
||||
// OnAfterInteractionEvents will handle deactivating its GameObject.
|
||||
|
||||
RayInteractorUpdate();
|
||||
}
|
||||
|
||||
void RayInteractorUpdate()
|
||||
{
|
||||
if (m_RayInteractor != null)
|
||||
m_RayInteractor.gameObject.SetActive(!m_Teleporting);
|
||||
}
|
||||
|
||||
void OnRaySelectEntered(SelectEnterEventArgs args)
|
||||
{
|
||||
// Disable locomotion and turn actions
|
||||
DisableLocomotionActions();
|
||||
}
|
||||
|
||||
void OnRaySelectExited(SelectExitEventArgs args)
|
||||
{
|
||||
// Re-enable the locomotion and turn actions
|
||||
UpdateLocomotionActions();
|
||||
}
|
||||
|
||||
protected void Awake()
|
||||
{
|
||||
// Start the coroutine that executes code after the Update phase (during yield null).
|
||||
// This routine is started during Awake to ensure the code after
|
||||
// the first yield will execute after Update but still on the first frame.
|
||||
// If started in Start, Unity would not resume execution until the second frame.
|
||||
// See https://docs.unity3d.com/Manual/ExecutionOrder.html
|
||||
StartCoroutine(OnAfterInteractionEvents());
|
||||
}
|
||||
|
||||
protected void OnEnable()
|
||||
{
|
||||
if (m_TeleportInteractor != null)
|
||||
m_TeleportInteractor.gameObject.SetActive(false);
|
||||
|
||||
SetupInteractorEvents();
|
||||
}
|
||||
|
||||
protected void OnDisable()
|
||||
{
|
||||
TeardownInteractorEvents();
|
||||
}
|
||||
|
||||
protected void Start()
|
||||
{
|
||||
// Ensure the enabled state of locomotion and turn actions are properly set up.
|
||||
// Called in Start so it is done after the InputActionManager enables all input actions earlier in OnEnable.
|
||||
UpdateLocomotionActions();
|
||||
|
||||
if (m_ManipulationInteractionGroup == null)
|
||||
{
|
||||
Debug.LogError("Missing required Manipulation Interaction Group reference. Use the Inspector window to assign the XR Interaction Group component reference.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure interactors are properly set up in the interaction group by adding
|
||||
// them if necessary and ordering Direct before Ray interactor.
|
||||
var directInteractorIndex = -1;
|
||||
var rayInteractorIndex = -1;
|
||||
m_ManipulationInteractionGroup.GetGroupMembers(s_GroupMembers);
|
||||
for (var i = 0; i < s_GroupMembers.Count; ++i)
|
||||
{
|
||||
var groupMember = s_GroupMembers[i];
|
||||
if (ReferenceEquals(groupMember, m_DirectInteractor))
|
||||
directInteractorIndex = i;
|
||||
else if (ReferenceEquals(groupMember, m_RayInteractor))
|
||||
rayInteractorIndex = i;
|
||||
}
|
||||
|
||||
if (directInteractorIndex < 0)
|
||||
{
|
||||
// Must add Direct interactor to group, and make sure it is ordered before the Ray interactor
|
||||
if (rayInteractorIndex < 0)
|
||||
{
|
||||
// Must add Ray interactor to group
|
||||
m_ManipulationInteractionGroup.AddGroupMember(m_DirectInteractor);
|
||||
m_ManipulationInteractionGroup.AddGroupMember(m_RayInteractor);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ManipulationInteractionGroup.MoveGroupMemberTo(m_DirectInteractor, rayInteractorIndex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rayInteractorIndex < 0)
|
||||
{
|
||||
// Must add Ray interactor to group
|
||||
m_ManipulationInteractionGroup.AddGroupMember(m_RayInteractor);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Must make sure Direct interactor is ordered before the Ray interactor
|
||||
if (rayInteractorIndex < directInteractorIndex)
|
||||
{
|
||||
m_ManipulationInteractionGroup.MoveGroupMemberTo(m_DirectInteractor, rayInteractorIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator OnAfterInteractionEvents()
|
||||
{
|
||||
// Avoid comparison to null each frame since that operation is somewhat expensive
|
||||
if (m_TeleportInteractor == null)
|
||||
yield break;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Yield so this coroutine is resumed after the teleport interactor
|
||||
// has a chance to process its select interaction event.
|
||||
yield return null;
|
||||
|
||||
if (!m_Teleporting && m_TeleportInteractor.gameObject.activeSelf)
|
||||
m_TeleportInteractor.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateLocomotionActions()
|
||||
{
|
||||
// Disable/enable Teleport and Turn when Move is enabled/disabled.
|
||||
SetEnabled(m_Move, m_SmoothMotionEnabled);
|
||||
SetEnabled(m_TeleportModeActivate, !m_SmoothMotionEnabled);
|
||||
SetEnabled(m_TeleportModeCancel, !m_SmoothMotionEnabled);
|
||||
|
||||
// Disable ability to turn when using continuous movement
|
||||
SetEnabled(m_Turn, !m_SmoothMotionEnabled && m_SmoothTurnEnabled);
|
||||
SetEnabled(m_SnapTurn, !m_SmoothMotionEnabled && !m_SmoothTurnEnabled);
|
||||
}
|
||||
|
||||
void DisableLocomotionActions()
|
||||
{
|
||||
DisableAction(m_Move);
|
||||
DisableAction(m_TeleportModeActivate);
|
||||
DisableAction(m_TeleportModeCancel);
|
||||
DisableAction(m_Turn);
|
||||
DisableAction(m_SnapTurn);
|
||||
}
|
||||
|
||||
static void SetEnabled(InputActionReference actionReference, bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
EnableAction(actionReference);
|
||||
else
|
||||
DisableAction(actionReference);
|
||||
}
|
||||
|
||||
static void EnableAction(InputActionReference actionReference)
|
||||
{
|
||||
var action = GetInputAction(actionReference);
|
||||
if (action != null && !action.enabled)
|
||||
action.Enable();
|
||||
}
|
||||
|
||||
static void DisableAction(InputActionReference actionReference)
|
||||
{
|
||||
var action = GetInputAction(actionReference);
|
||||
if (action != null && action.enabled)
|
||||
action.Disable();
|
||||
}
|
||||
|
||||
static InputAction GetInputAction(InputActionReference actionReference)
|
||||
{
|
||||
#pragma warning disable IDE0031 // Use null propagation -- Do not use for UnityEngine.Object types
|
||||
return actionReference != null ? actionReference.action : null;
|
||||
#pragma warning restore IDE0031
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f9ac216f0eb04754b1d938aac6380b31
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,189 @@
|
|||
using Unity.XR.CoreUtils;
|
||||
using UnityEngine.Assertions;
|
||||
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
|
||||
{
|
||||
/// <summary>
|
||||
/// A version of action-based continuous movement that automatically controls the frame of reference that
|
||||
/// determines the forward direction of movement based on user preference for each hand.
|
||||
/// For example, can configure to use head relative movement for the left hand and controller relative movement for the right hand.
|
||||
/// </summary>
|
||||
public class DynamicMoveProvider : ActionBasedContinuousMoveProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines which transform the XR Origin's movement direction is relative to.
|
||||
/// </summary>
|
||||
/// <seealso cref="leftHandMovementDirection"/>
|
||||
/// <seealso cref="rightHandMovementDirection"/>
|
||||
public enum MovementDirection
|
||||
{
|
||||
/// <summary>
|
||||
/// Use the forward direction of the head (camera) as the forward direction of the XR Origin's movement.
|
||||
/// </summary>
|
||||
HeadRelative,
|
||||
|
||||
/// <summary>
|
||||
/// Use the forward direction of the hand (controller) as the forward direction of the XR Origin's movement.
|
||||
/// </summary>
|
||||
HandRelative,
|
||||
}
|
||||
|
||||
[Space, Header("Movement Direction")]
|
||||
[SerializeField]
|
||||
[Tooltip("Directs the XR Origin's movement when using the head-relative mode. If not set, will automatically find and use the XR Origin Camera.")]
|
||||
Transform m_HeadTransform;
|
||||
|
||||
/// <summary>
|
||||
/// Directs the XR Origin's movement when using the head-relative mode. If not set, will automatically find and use the XR Origin Camera.
|
||||
/// </summary>
|
||||
public Transform headTransform
|
||||
{
|
||||
get => m_HeadTransform;
|
||||
set => m_HeadTransform = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Directs the XR Origin's movement when using the hand-relative mode with the left hand.")]
|
||||
Transform m_LeftControllerTransform;
|
||||
|
||||
/// <summary>
|
||||
/// Directs the XR Origin's movement when using the hand-relative mode with the left hand.
|
||||
/// </summary>
|
||||
public Transform leftControllerTransform
|
||||
{
|
||||
get => m_LeftControllerTransform;
|
||||
set => m_LeftControllerTransform = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Directs the XR Origin's movement when using the hand-relative mode with the right hand.")]
|
||||
Transform m_RightControllerTransform;
|
||||
|
||||
public Transform rightControllerTransform
|
||||
{
|
||||
get => m_RightControllerTransform;
|
||||
set => m_RightControllerTransform = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Whether to use the specified head transform or left controller transform to direct the XR Origin's movement for the left hand.")]
|
||||
MovementDirection m_LeftHandMovementDirection;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use the specified head transform or controller transform to direct the XR Origin's movement for the left hand.
|
||||
/// </summary>
|
||||
/// <seealso cref="MovementDirection"/>
|
||||
public MovementDirection leftHandMovementDirection
|
||||
{
|
||||
get => m_LeftHandMovementDirection;
|
||||
set => m_LeftHandMovementDirection = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Whether to use the specified head transform or right controller transform to direct the XR Origin's movement for the right hand.")]
|
||||
MovementDirection m_RightHandMovementDirection;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use the specified head transform or controller transform to direct the XR Origin's movement for the right hand.
|
||||
/// </summary>
|
||||
/// <seealso cref="MovementDirection"/>
|
||||
public MovementDirection rightHandMovementDirection
|
||||
{
|
||||
get => m_RightHandMovementDirection;
|
||||
set => m_RightHandMovementDirection = value;
|
||||
}
|
||||
|
||||
Transform m_CombinedTransform;
|
||||
Pose m_LeftMovementPose = Pose.identity;
|
||||
Pose m_RightMovementPose = Pose.identity;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
m_CombinedTransform = new GameObject("[Dynamic Move Provider] Combined Forward Source").transform;
|
||||
m_CombinedTransform.SetParent(transform, false);
|
||||
m_CombinedTransform.localPosition = Vector3.zero;
|
||||
m_CombinedTransform.localRotation = Quaternion.identity;
|
||||
|
||||
forwardSource = m_CombinedTransform;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Vector3 ComputeDesiredMove(Vector2 input)
|
||||
{
|
||||
// Don't need to do anything if the total input is zero.
|
||||
// This is the same check as the base method.
|
||||
if (input == Vector2.zero)
|
||||
return Vector3.zero;
|
||||
|
||||
// Initialize the Head Transform if necessary, getting the Camera from XR Origin
|
||||
if (m_HeadTransform == null)
|
||||
{
|
||||
var xrOrigin = system.xrOrigin;
|
||||
if (xrOrigin != null)
|
||||
{
|
||||
var xrCamera = xrOrigin.Camera;
|
||||
if (xrCamera != null)
|
||||
m_HeadTransform = xrCamera.transform;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the forward source for the left hand input
|
||||
switch (m_LeftHandMovementDirection)
|
||||
{
|
||||
case MovementDirection.HeadRelative:
|
||||
if (m_HeadTransform != null)
|
||||
m_LeftMovementPose = m_HeadTransform.GetWorldPose();
|
||||
|
||||
break;
|
||||
|
||||
case MovementDirection.HandRelative:
|
||||
if (m_LeftControllerTransform != null)
|
||||
m_LeftMovementPose = m_LeftControllerTransform.GetWorldPose();
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
Assert.IsTrue(false, $"Unhandled {nameof(MovementDirection)}={m_LeftHandMovementDirection}");
|
||||
break;
|
||||
}
|
||||
|
||||
// Get the forward source for the right hand input
|
||||
switch (m_RightHandMovementDirection)
|
||||
{
|
||||
case MovementDirection.HeadRelative:
|
||||
if (m_HeadTransform != null)
|
||||
m_RightMovementPose = m_HeadTransform.GetWorldPose();
|
||||
|
||||
break;
|
||||
|
||||
case MovementDirection.HandRelative:
|
||||
if (m_RightControllerTransform != null)
|
||||
m_RightMovementPose = m_RightControllerTransform.GetWorldPose();
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
Assert.IsTrue(false, $"Unhandled {nameof(MovementDirection)}={m_RightHandMovementDirection}");
|
||||
break;
|
||||
}
|
||||
|
||||
// Combine the two poses into the forward source based on the magnitude of input
|
||||
var leftHandValue = leftHandMoveAction.action?.ReadValue<Vector2>() ?? Vector2.zero;
|
||||
var rightHandValue = rightHandMoveAction.action?.ReadValue<Vector2>() ?? Vector2.zero;
|
||||
|
||||
var totalSqrMagnitude = leftHandValue.sqrMagnitude + rightHandValue.sqrMagnitude;
|
||||
var leftHandBlend = 0.5f;
|
||||
if (totalSqrMagnitude > Mathf.Epsilon)
|
||||
leftHandBlend = leftHandValue.sqrMagnitude / totalSqrMagnitude;
|
||||
|
||||
var combinedPosition = Vector3.Lerp(m_RightMovementPose.position, m_LeftMovementPose.position, leftHandBlend);
|
||||
var combinedRotation = Quaternion.Slerp(m_RightMovementPose.rotation, m_LeftMovementPose.rotation, leftHandBlend);
|
||||
m_CombinedTransform.SetPositionAndRotation(combinedPosition, combinedRotation);
|
||||
|
||||
return base.ComputeDesiredMove(input);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9b1e8c997df241c1a67045eeac79b41b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,95 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages input fallback for <see cref="XRGazeInteractor"/> when eye tracking is not available.
|
||||
/// </summary>
|
||||
public class GazeInputManager : MonoBehaviour
|
||||
{
|
||||
// This is the name of the layout that is registered by EyeGazeInteraction in the OpenXR Plugin package
|
||||
const string k_EyeGazeLayoutName = "EyeGaze";
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Enable fallback to head tracking if eye tracking is unavailable.")]
|
||||
bool m_FallbackIfEyeTrackingUnavailable = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enable fallback to head tracking if eye tracking is unavailable.
|
||||
/// </summary>
|
||||
public bool fallbackIfEyeTrackingUnavailable
|
||||
{
|
||||
get => m_FallbackIfEyeTrackingUnavailable;
|
||||
set => m_FallbackIfEyeTrackingUnavailable = value;
|
||||
}
|
||||
|
||||
|
||||
bool m_EyeTrackingDeviceFound;
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void Awake()
|
||||
{
|
||||
// Check if we have eye tracking support
|
||||
var inputDeviceList = new List<InputDevice>();
|
||||
InputDevices.GetDevicesWithCharacteristics(InputDeviceCharacteristics.EyeTracking, inputDeviceList);
|
||||
if (inputDeviceList.Count > 0)
|
||||
{
|
||||
Debug.Log("Eye tracking device found!", this);
|
||||
m_EyeTrackingDeviceFound = true;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var device in InputSystem.InputSystem.devices)
|
||||
{
|
||||
if (device.layout == k_EyeGazeLayoutName)
|
||||
{
|
||||
Debug.Log("Eye gaze device found!", this);
|
||||
m_EyeTrackingDeviceFound = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.LogWarning($"Could not find a device that supports eye tracking on Awake. {this} has subscribed to device connected events and will activate the GameObject when an eye tracking device is connected.", this);
|
||||
|
||||
InputDevices.deviceConnected += OnDeviceConnected;
|
||||
InputSystem.InputSystem.onDeviceChange += OnDeviceChange;
|
||||
|
||||
gameObject.SetActive(m_FallbackIfEyeTrackingUnavailable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void OnDestroy()
|
||||
{
|
||||
InputDevices.deviceConnected -= OnDeviceConnected;
|
||||
InputSystem.InputSystem.onDeviceChange -= OnDeviceChange;
|
||||
}
|
||||
|
||||
void OnDeviceConnected(InputDevice inputDevice)
|
||||
{
|
||||
if (m_EyeTrackingDeviceFound || !inputDevice.characteristics.HasFlag(InputDeviceCharacteristics.EyeTracking))
|
||||
return;
|
||||
|
||||
Debug.Log("Eye tracking device found!", this);
|
||||
m_EyeTrackingDeviceFound = true;
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
void OnDeviceChange(InputSystem.InputDevice device, InputDeviceChange change)
|
||||
{
|
||||
if (m_EyeTrackingDeviceFound || change != InputDeviceChange.Added)
|
||||
return;
|
||||
|
||||
if (device.layout == k_EyeGazeLayoutName)
|
||||
{
|
||||
Debug.Log("Eye gaze device found!", this);
|
||||
m_EyeTrackingDeviceFound = true;
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6ef0e4723b64c884699a375196c13ac0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 73fceb68998bec84496a6ef93dd7cf16
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,45 @@
|
|||
using UnityEngine.UI;
|
||||
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
|
||||
{
|
||||
/// <summary>
|
||||
/// Add this component to a GameObject and call the <see cref="IncrementText"/> method
|
||||
/// in response to a Unity Event to update a text display to count up with each event.
|
||||
/// </summary>
|
||||
public class IncrementUIText : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("The Text component this behavior uses to display the incremented value.")]
|
||||
Text m_Text;
|
||||
|
||||
/// <summary>
|
||||
/// The Text component this behavior uses to display the incremented value.
|
||||
/// </summary>
|
||||
public Text text
|
||||
{
|
||||
get => m_Text;
|
||||
set => m_Text = value;
|
||||
}
|
||||
|
||||
int m_Count;
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void Awake()
|
||||
{
|
||||
if (m_Text == null)
|
||||
Debug.LogWarning("Missing required Text component reference. Use the Inspector window to assign which Text component to increment.", this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increment the string message of the Text component.
|
||||
/// </summary>
|
||||
public void IncrementText()
|
||||
{
|
||||
m_Count += 1;
|
||||
if (m_Text != null)
|
||||
m_Text.text = m_Count.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ba6ff5e7c92519444bc2a7ca46558963
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,182 @@
|
|||
using Unity.Mathematics;
|
||||
using Unity.XR.CoreUtils.Bindings;
|
||||
using UnityEngine.XR.Interaction.Toolkit.AffordanceSystem.State;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Filtering;
|
||||
using UnityEngine.XR.Interaction.Toolkit.Utilities.Tweenables.Primitives;
|
||||
|
||||
namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
|
||||
{
|
||||
/// <summary>
|
||||
/// Follow animation affordance for <see cref="IPokeStateDataProvider"/>, such as <see cref="XRPokeFilter"/>.
|
||||
/// Used to animate a pressed transform, such as a button to follow the poke position.
|
||||
/// </summary>
|
||||
[AddComponentMenu("XR/XR Poke Follow Affordance", 22)]
|
||||
public class XRPokeFollowAffordance : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("Transform that will move in the poke direction when this or a parent GameObject is poked." +
|
||||
"\nNote: Should be a direct child GameObject.")]
|
||||
Transform m_PokeFollowTransform;
|
||||
|
||||
/// <summary>
|
||||
/// Transform that will animate along the axis of interaction when this interactable is poked.
|
||||
/// Note: Must be a direct child GameObject as it moves in local space relative to the poke target's transform.
|
||||
/// </summary>
|
||||
public Transform pokeFollowTransform
|
||||
{
|
||||
get => m_PokeFollowTransform;
|
||||
set => m_PokeFollowTransform = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Range(0f, 20f)]
|
||||
[Tooltip("Multiplies transform position interpolation as a factor of Time.deltaTime. If 0, no smoothing will be applied.")]
|
||||
float m_SmoothingSpeed = 16f;
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies transform position interpolation as a factor of <see cref="Time.deltaTime"/>. If <c>0</c>, no smoothing will be applied.
|
||||
/// </summary>
|
||||
public float smoothingSpeed
|
||||
{
|
||||
get => m_SmoothingSpeed;
|
||||
set => m_SmoothingSpeed = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("When this component is no longer the target of the poke, the Poke Follow Transform returns to the original position.")]
|
||||
bool m_ReturnToInitialPosition = true;
|
||||
|
||||
/// <summary>
|
||||
/// When this component is no longer the target of the poke, the <see cref="pokeFollowTransform"/> returns to the original position.
|
||||
/// </summary>
|
||||
public bool returnToInitialPosition
|
||||
{
|
||||
get => m_ReturnToInitialPosition;
|
||||
set => m_ReturnToInitialPosition = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Whether to apply the follow animation if the target of the poke is a child of this transform. " +
|
||||
"This is useful for UI objects that may have child graphics.")]
|
||||
bool m_ApplyIfChildIsTarget = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to apply the follow animation if the target of the poke is a child of this transform.
|
||||
/// This is useful for UI objects that may have child graphics.
|
||||
/// </summary>
|
||||
public bool applyIfChildIsTarget
|
||||
{
|
||||
get => m_ApplyIfChildIsTarget;
|
||||
set => m_ApplyIfChildIsTarget = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Whether to keep the Poke Follow Transform from moving past a maximum distance from the poke target.")]
|
||||
bool m_ClampToMaxDistance;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to keep the <see cref="pokeFollowTransform"/> from moving past <see cref="maxDistance"/> from the poke target.
|
||||
/// </summary>
|
||||
public bool clampToMaxDistance
|
||||
{
|
||||
get => m_ClampToMaxDistance;
|
||||
set => m_ClampToMaxDistance = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The maximum distance from this transform that the Poke Follow Transform can move.")]
|
||||
float m_MaxDistance;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum distance from this transform that the <see cref="pokeFollowTransform"/> can move when
|
||||
/// <see cref="clampToMaxDistance"/> is <see langword="true"/>.
|
||||
/// </summary>
|
||||
public float maxDistance
|
||||
{
|
||||
get => m_MaxDistance;
|
||||
set => m_MaxDistance = value;
|
||||
}
|
||||
|
||||
IPokeStateDataProvider m_PokeDataProvider;
|
||||
|
||||
readonly Vector3TweenableVariable m_TransformTweenableVariable = new Vector3TweenableVariable();
|
||||
readonly BindingsGroup m_BindingsGroup = new BindingsGroup();
|
||||
Vector3 m_InitialPosition;
|
||||
bool m_IsFirstFrame;
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void Awake()
|
||||
{
|
||||
m_PokeDataProvider = GetComponentInParent<IPokeStateDataProvider>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void Start()
|
||||
{
|
||||
if (m_PokeFollowTransform != null)
|
||||
{
|
||||
m_InitialPosition = m_PokeFollowTransform.localPosition;
|
||||
m_BindingsGroup.AddBinding(m_TransformTweenableVariable.Subscribe(OnTransformTweenableVariableUpdated));
|
||||
m_BindingsGroup.AddBinding(m_PokeDataProvider.pokeStateData.SubscribeAndUpdate(OnPokeStateDataUpdated));
|
||||
}
|
||||
else
|
||||
{
|
||||
enabled = false;
|
||||
Debug.LogWarning($"Missing Poke Follow Transform assignment on {this}. Disabling component.", this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void OnDestroy()
|
||||
{
|
||||
m_BindingsGroup.Clear();
|
||||
m_TransformTweenableVariable?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="MonoBehaviour"/>.
|
||||
/// </summary>
|
||||
protected void LateUpdate()
|
||||
{
|
||||
if (m_IsFirstFrame)
|
||||
{
|
||||
m_TransformTweenableVariable.HandleTween(1f);
|
||||
m_IsFirstFrame = false;
|
||||
return;
|
||||
}
|
||||
m_TransformTweenableVariable.HandleTween(m_SmoothingSpeed > 0f ? Time.deltaTime * m_SmoothingSpeed : 1f);
|
||||
}
|
||||
|
||||
void OnTransformTweenableVariableUpdated(float3 position)
|
||||
{
|
||||
m_PokeFollowTransform.localPosition = position;
|
||||
}
|
||||
|
||||
void OnPokeStateDataUpdated(PokeStateData data)
|
||||
{
|
||||
var pokeTarget = data.target;
|
||||
var applyFollow = m_ApplyIfChildIsTarget
|
||||
? pokeTarget != null && pokeTarget.IsChildOf(transform)
|
||||
: pokeTarget == transform;
|
||||
|
||||
if (applyFollow)
|
||||
{
|
||||
var targetPosition = pokeTarget.InverseTransformPoint(data.axisAlignedPokeInteractionPoint);
|
||||
if (m_ClampToMaxDistance && targetPosition.sqrMagnitude > m_MaxDistance * m_MaxDistance)
|
||||
targetPosition = Vector3.ClampMagnitude(targetPosition, m_MaxDistance);
|
||||
|
||||
m_TransformTweenableVariable.target = targetPosition;
|
||||
}
|
||||
else if (m_ReturnToInitialPosition)
|
||||
{
|
||||
m_TransformTweenableVariable.target = m_InitialPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 07b3638c2f5db5b479ff24c2859713d4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Add table
Add a link
Reference in a new issue