forked from cgvr/DeltaVR
deltavr multiplayer 2.0
This commit is contained in:
8
Assets/XRI_Examples/UI_3D/Scripts/ClawMachine.meta
Normal file
8
Assets/XRI_Examples/UI_3D/Scripts/ClawMachine.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60d5463e9022bbc4b968fab685923068
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
165
Assets/XRI_Examples/UI_3D/Scripts/ClawMachine/ClawMachine.cs
Normal file
165
Assets/XRI_Examples/UI_3D/Scripts/ClawMachine/ClawMachine.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using System.Collections;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is responsible to update the claw position and its state.
|
||||
/// The 3 states (NoPrize, TryGrabPrize and ReleasePrize) are coroutines and they update
|
||||
/// the claw speed, particles and the UfoAbductionForce
|
||||
/// <seealso cref="XRJoystick"/> and <seealso cref="XRPushButton"/>
|
||||
/// </summary>
|
||||
public class ClawMachine : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("The claw transform that will be updated and translated")]
|
||||
Transform m_ClawTransform;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The claw socket used to get the prizes")]
|
||||
XRSocketInteractor m_ClawSocket;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The component used to apply a force on the prizes")]
|
||||
UfoAbductionForce m_UfoAbductionForce;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The claw speed when not carrying a prize")]
|
||||
float m_ClawWithoutPrizeSpeed;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The claw speed when carrying a prize")]
|
||||
float m_ClawWithPrizeSpeed;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The claw speed when the UfoAbductionForce is enabled")]
|
||||
float m_ClawAbductionSpeed;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The claw's minimum local position. Used to clamp the claw position")]
|
||||
Vector2 m_MinClawPosition;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The claw's maximum local position. Used to clamp the claw position")]
|
||||
Vector2 m_MaxClawPosition;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The Sparklies particle. This particle is activated while the UfoAbductionForce is enabled")]
|
||||
ParticleSystem m_SparkliesParticle;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The UfoBeam particle. This particle is activated while the PushButton is held down")]
|
||||
ParticleSystem m_UfoBeamParticle;
|
||||
|
||||
bool m_ButtonPressed;
|
||||
Vector2 m_JoystickValue;
|
||||
|
||||
void Start()
|
||||
{
|
||||
StartCoroutine(NoPrizeState());
|
||||
}
|
||||
|
||||
void UpdateClawPosition(float speed)
|
||||
{
|
||||
// Get current claw position
|
||||
var clawPosition = m_ClawTransform.localPosition;
|
||||
|
||||
// Calculate claw velocity and new position
|
||||
clawPosition += new Vector3(m_JoystickValue.x * speed * Time.deltaTime, 0f,
|
||||
m_JoystickValue.y * speed * Time.deltaTime);
|
||||
|
||||
// Clamp claw position
|
||||
clawPosition.x = Mathf.Clamp(clawPosition.x, m_MinClawPosition.x, m_MaxClawPosition.x);
|
||||
clawPosition.z = Mathf.Clamp(clawPosition.z, m_MinClawPosition.y, m_MaxClawPosition.y);
|
||||
|
||||
// Update claw position
|
||||
m_ClawTransform.localPosition = clawPosition;
|
||||
}
|
||||
|
||||
IEnumerator NoPrizeState()
|
||||
{
|
||||
// Move the claw
|
||||
while (!m_ButtonPressed)
|
||||
{
|
||||
UpdateClawPosition(m_ClawWithoutPrizeSpeed);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
StartCoroutine(TryGrabPrizeState());
|
||||
}
|
||||
|
||||
IEnumerator TryGrabPrizeState()
|
||||
{
|
||||
// Start particles, activate the Socket and the UfoAbductionForce
|
||||
m_SparkliesParticle.Play();
|
||||
m_UfoBeamParticle.Play();
|
||||
m_ClawSocket.socketActive = true;
|
||||
m_UfoAbductionForce.enabled = true;
|
||||
|
||||
// Try get a prize, the claw can still move
|
||||
while (m_ButtonPressed && !m_ClawSocket.hasSelection)
|
||||
{
|
||||
UpdateClawPosition(m_ClawAbductionSpeed);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// Disable abduction force and the Sparklies particle
|
||||
m_UfoAbductionForce.enabled = false;
|
||||
m_SparkliesParticle.Stop();
|
||||
|
||||
StartCoroutine(ReleasePrizeState());
|
||||
}
|
||||
|
||||
IEnumerator ReleasePrizeState()
|
||||
{
|
||||
// Move the claw
|
||||
while (m_ButtonPressed)
|
||||
{
|
||||
UpdateClawPosition(m_ClawWithPrizeSpeed);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// Release the prize and stop the last particle
|
||||
m_ClawSocket.socketActive = false;
|
||||
m_UfoBeamParticle.Stop();
|
||||
|
||||
StartCoroutine(NoPrizeState());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the internal state of the push button used by this class.
|
||||
/// Called by the <c>XRPushButton.OnPress</c> event.
|
||||
/// </summary>
|
||||
public void OnButtonPress()
|
||||
{
|
||||
m_ButtonPressed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the internal state of the push button used by this class.
|
||||
/// Called by the <c>XRPushButton.OnRelease</c> event.
|
||||
/// </summary>
|
||||
public void OnButtonRelease()
|
||||
{
|
||||
m_ButtonPressed = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the X value of the joystick. Called by the <c>XRJoystick.OnValueChangeX</c> event.
|
||||
/// </summary>
|
||||
/// <param name="x">The joystick's X value</param>
|
||||
public void OnJoystickValueChangeX(float x)
|
||||
{
|
||||
m_JoystickValue.x = x;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Y value of the joystick. Called by the <c>XRJoystick.OnValueChangeY</c> event.
|
||||
/// </summary>
|
||||
/// <param name="y">The joystick's Y value</param>
|
||||
public void OnJoystickValueChangeY(float y)
|
||||
{
|
||||
m_JoystickValue.y = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 025c853820e7fb844819416ecae0f5e9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
34
Assets/XRI_Examples/UI_3D/Scripts/ClawMachine/FlippyDoor.cs
Normal file
34
Assets/XRI_Examples/UI_3D/Scripts/ClawMachine/FlippyDoor.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// This class rotates the flippy door of the ClawMachine when there is any rigidbody inside its trigger.
|
||||
/// This class uses the <c>m_Count</c> integer to count the rigidbodies in the trigger and then check
|
||||
/// it to update the rotation of the <c>m_Trasform</c>.
|
||||
/// </summary>
|
||||
public class FlippyDoor : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("The transform of the FlippyDoor that will be rotated")]
|
||||
Transform m_Transform;
|
||||
|
||||
int m_Count;
|
||||
|
||||
void Update()
|
||||
{
|
||||
var eulerAngles = m_Transform.eulerAngles;
|
||||
var desiredAngle = m_Count > 0 ? 90f : 0f;
|
||||
eulerAngles.x = Mathf.LerpAngle(eulerAngles.x, desiredAngle, Time.deltaTime * 4f);
|
||||
m_Transform.eulerAngles = eulerAngles;
|
||||
}
|
||||
|
||||
void OnTriggerEnter(Collider other)
|
||||
{
|
||||
m_Count++;
|
||||
}
|
||||
|
||||
void OnTriggerExit(Collider other)
|
||||
{
|
||||
m_Count--;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d0c2d7cf5f0ee141a994934c9d2f1a1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,75 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// This class applies an abduction force in all rigidbodies inside the trigger collider.
|
||||
/// A list is used to store all rigidbodies inside the trigger; the force is applied in
|
||||
/// the FixedUpdate. The force magnitude is calculated using the pressure value of the <c>XRPushButton</c>
|
||||
/// <seealso cref="XRPushButton"/>
|
||||
/// </summary>
|
||||
public class UfoAbductionForce : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("The minimum magnitude of the abduction force")]
|
||||
float m_MinForceMagnitude;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The maximum magnitude of the abduction force")]
|
||||
float m_MaxForceMagnitude;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("All rigidbodies inside this trigger will receive the abduction force.")]
|
||||
Collider m_Trigger;
|
||||
|
||||
float m_ButtonValue;
|
||||
readonly List<Rigidbody> m_Rigidbodies = new List<Rigidbody>();
|
||||
|
||||
void Awake()
|
||||
{
|
||||
m_Trigger.enabled = false;
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_Trigger.enabled = true;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
m_Trigger.enabled = false;
|
||||
m_Rigidbodies.Clear();
|
||||
}
|
||||
|
||||
void FixedUpdate()
|
||||
{
|
||||
var deltaForce = m_MaxForceMagnitude - m_MinForceMagnitude;
|
||||
var force = transform.up * (m_MinForceMagnitude + deltaForce * m_ButtonValue);
|
||||
foreach (var rigidbody in m_Rigidbodies)
|
||||
rigidbody.AddForce(force, ForceMode.Acceleration);
|
||||
}
|
||||
|
||||
void OnTriggerEnter(Collider other)
|
||||
{
|
||||
var otherRigidbody = other.GetComponent<Rigidbody>();
|
||||
if (otherRigidbody != null)
|
||||
m_Rigidbodies.Add(otherRigidbody);
|
||||
}
|
||||
|
||||
void OnTriggerExit(Collider other)
|
||||
{
|
||||
var otherRigidbody = other.GetComponent<Rigidbody>();
|
||||
if (otherRigidbody != null)
|
||||
m_Rigidbodies.Remove(otherRigidbody);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current button value. Called by the <c>XRPushButton.OnValueChange</c> event.
|
||||
/// </summary>
|
||||
/// <param name="value">The current button value</param>
|
||||
public void OnButtonValueChange(float value)
|
||||
{
|
||||
m_ButtonValue = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e795a5f3d4373eb459b9925fc3c62013
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
172
Assets/XRI_Examples/UI_3D/Scripts/XRGripButton.cs
Normal file
172
Assets/XRI_Examples/UI_3D/Scripts/XRGripButton.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// An interactable that can be pressed by a direct interactor
|
||||
/// </summary>
|
||||
public class XRGripButton : XRBaseInteractable
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("The object that is visually pressed down")]
|
||||
Transform m_Button = null;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The distance the button can be pressed")]
|
||||
float m_PressDistance = 0.1f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Treat this button like an on/off toggle")]
|
||||
bool m_ToggleButton = false;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Events to trigger when the button is pressed")]
|
||||
UnityEvent m_OnPress;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Events to trigger when the button is released")]
|
||||
UnityEvent m_OnRelease;
|
||||
|
||||
bool m_Hovered = false;
|
||||
bool m_Selected = false;
|
||||
bool m_Toggled = false;
|
||||
|
||||
/// <summary>
|
||||
/// The object that is visually pressed down
|
||||
/// </summary>
|
||||
public Transform button
|
||||
{
|
||||
get => m_Button;
|
||||
set => m_Button = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The distance the button can be pressed
|
||||
/// </summary>
|
||||
public float pressDistance
|
||||
{
|
||||
get => m_PressDistance;
|
||||
set => m_PressDistance = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Events to trigger when the button is pressed
|
||||
/// </summary>
|
||||
public UnityEvent onPress => m_OnPress;
|
||||
|
||||
/// <summary>
|
||||
/// Events to trigger when the button is released
|
||||
/// </summary>
|
||||
public UnityEvent onRelease => m_OnRelease;
|
||||
|
||||
void Start()
|
||||
{
|
||||
SetButtonHeight(0.0f);
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
if (m_ToggleButton)
|
||||
selectEntered.AddListener(StartTogglePress);
|
||||
else
|
||||
{
|
||||
selectEntered.AddListener(StartPress);
|
||||
selectExited.AddListener(EndPress);
|
||||
hoverEntered.AddListener(StartHover);
|
||||
hoverExited.AddListener(EndHover);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
if (m_ToggleButton)
|
||||
selectEntered.RemoveListener(StartTogglePress);
|
||||
else
|
||||
{
|
||||
selectEntered.RemoveListener(StartPress);
|
||||
selectExited.RemoveListener(EndPress);
|
||||
hoverEntered.RemoveListener(StartHover);
|
||||
hoverExited.RemoveListener(EndHover);
|
||||
base.OnDisable();
|
||||
}
|
||||
}
|
||||
|
||||
void StartTogglePress(SelectEnterEventArgs args)
|
||||
{
|
||||
m_Toggled = !m_Toggled;
|
||||
|
||||
if (m_Toggled)
|
||||
{
|
||||
SetButtonHeight(-m_PressDistance);
|
||||
m_OnPress.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
SetButtonHeight(0.0f);
|
||||
m_OnRelease.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
void StartPress(SelectEnterEventArgs args)
|
||||
{
|
||||
SetButtonHeight(-m_PressDistance);
|
||||
m_OnPress.Invoke();
|
||||
m_Selected = true;
|
||||
}
|
||||
|
||||
void EndPress(SelectExitEventArgs args)
|
||||
{
|
||||
if (m_Hovered)
|
||||
m_OnRelease.Invoke();
|
||||
|
||||
SetButtonHeight(0.0f);
|
||||
m_Selected = false;
|
||||
}
|
||||
|
||||
void StartHover(HoverEnterEventArgs args)
|
||||
{
|
||||
m_Hovered = true;
|
||||
if (m_Selected)
|
||||
SetButtonHeight(-m_PressDistance);
|
||||
}
|
||||
|
||||
void EndHover(HoverExitEventArgs args)
|
||||
{
|
||||
m_Hovered = false;
|
||||
SetButtonHeight(0.0f);
|
||||
}
|
||||
|
||||
void SetButtonHeight(float height)
|
||||
{
|
||||
if (m_Button == null)
|
||||
return;
|
||||
|
||||
Vector3 newPosition = m_Button.localPosition;
|
||||
newPosition.y = height;
|
||||
m_Button.localPosition = newPosition;
|
||||
}
|
||||
|
||||
void OnDrawGizmosSelected()
|
||||
{
|
||||
var pressStartPoint = transform.position;
|
||||
var pressDownDirection = -transform.up;
|
||||
|
||||
if (m_Button != null)
|
||||
{
|
||||
pressStartPoint = m_Button.position;
|
||||
pressDownDirection = -m_Button.up;
|
||||
}
|
||||
|
||||
Gizmos.color = Color.green;
|
||||
Gizmos.DrawLine(pressStartPoint, pressStartPoint + (pressDownDirection * m_PressDistance));
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
SetButtonHeight(0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/XRI_Examples/UI_3D/Scripts/XRGripButton.cs.meta
Normal file
11
Assets/XRI_Examples/UI_3D/Scripts/XRGripButton.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: acde65f5db6773e49ad668404cf55472
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
323
Assets/XRI_Examples/UI_3D/Scripts/XRJoystick.cs
Normal file
323
Assets/XRI_Examples/UI_3D/Scripts/XRJoystick.cs
Normal file
@@ -0,0 +1,323 @@
|
||||
using System;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// An interactable joystick that can move side to side, and forward and back by a direct interactor
|
||||
/// </summary>
|
||||
public class XRJoystick : XRBaseInteractable
|
||||
{
|
||||
const float k_MaxDeadZonePercent = 0.9f;
|
||||
|
||||
public enum JoystickType
|
||||
{
|
||||
BothCircle,
|
||||
BothSquare,
|
||||
FrontBack,
|
||||
LeftRight,
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ValueChangeEvent : UnityEvent<float> { }
|
||||
|
||||
[Tooltip("Controls how the joystick moves")]
|
||||
[SerializeField]
|
||||
JoystickType m_JoystickMotion = JoystickType.BothCircle;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The object that is visually grabbed and manipulated")]
|
||||
Transform m_Handle = null;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The value of the joystick")]
|
||||
Vector2 m_Value = Vector2.zero;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("If true, the joystick will return to center on release")]
|
||||
bool m_RecenterOnRelease = true;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Maximum angle the joystick can move")]
|
||||
[Range(1.0f, 90.0f)]
|
||||
float m_MaxAngle = 60.0f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Minimum amount the joystick must move off the center to register changes")]
|
||||
[Range(1.0f, 90.0f)]
|
||||
float m_DeadZoneAngle = 10.0f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Events to trigger when the joystick's x value changes")]
|
||||
ValueChangeEvent m_OnValueChangeX = new ValueChangeEvent();
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Events to trigger when the joystick's y value changes")]
|
||||
ValueChangeEvent m_OnValueChangeY = new ValueChangeEvent();
|
||||
|
||||
IXRSelectInteractor m_Interactor;
|
||||
|
||||
/// <summary>
|
||||
/// Controls how the joystick moves
|
||||
/// </summary>
|
||||
public JoystickType joystickMotion
|
||||
{
|
||||
get => m_JoystickMotion;
|
||||
set => m_JoystickMotion = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The object that is visually grabbed and manipulated
|
||||
/// </summary>
|
||||
public Transform handle
|
||||
{
|
||||
get => m_Handle;
|
||||
set => m_Handle = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value of the joystick
|
||||
/// </summary>
|
||||
public Vector2 value
|
||||
{
|
||||
get => m_Value;
|
||||
set
|
||||
{
|
||||
if (!m_RecenterOnRelease)
|
||||
{
|
||||
SetValue(value);
|
||||
SetHandleAngle(value * m_MaxAngle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If true, the joystick will return to center on release
|
||||
/// </summary>
|
||||
public bool recenterOnRelease
|
||||
{
|
||||
get => m_RecenterOnRelease;
|
||||
set => m_RecenterOnRelease = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maximum angle the joystick can move
|
||||
/// </summary>
|
||||
public float maxAngle
|
||||
{
|
||||
get => m_MaxAngle;
|
||||
set => m_MaxAngle = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Minimum amount the joystick must move off the center to register changes
|
||||
/// </summary>
|
||||
public float deadZoneAngle
|
||||
{
|
||||
get => m_DeadZoneAngle;
|
||||
set => m_DeadZoneAngle = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Events to trigger when the joystick's x value changes
|
||||
/// </summary>
|
||||
public ValueChangeEvent onValueChangeX => m_OnValueChangeX;
|
||||
|
||||
/// <summary>
|
||||
/// Events to trigger when the joystick's y value changes
|
||||
/// </summary>
|
||||
public ValueChangeEvent onValueChangeY => m_OnValueChangeY;
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (m_RecenterOnRelease)
|
||||
SetHandleAngle(Vector2.zero);
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
selectEntered.AddListener(StartGrab);
|
||||
selectExited.AddListener(EndGrab);
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
selectEntered.RemoveListener(StartGrab);
|
||||
selectExited.RemoveListener(EndGrab);
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
private void StartGrab(SelectEnterEventArgs args)
|
||||
{
|
||||
m_Interactor = args.interactorObject;
|
||||
}
|
||||
|
||||
private void EndGrab(SelectExitEventArgs arts)
|
||||
{
|
||||
UpdateValue();
|
||||
|
||||
if (m_RecenterOnRelease)
|
||||
{
|
||||
SetHandleAngle(Vector2.zero);
|
||||
SetValue(Vector2.zero);
|
||||
}
|
||||
|
||||
m_Interactor = null;
|
||||
}
|
||||
|
||||
public override void ProcessInteractable(XRInteractionUpdateOrder.UpdatePhase updatePhase)
|
||||
{
|
||||
base.ProcessInteractable(updatePhase);
|
||||
|
||||
if (updatePhase == XRInteractionUpdateOrder.UpdatePhase.Dynamic)
|
||||
{
|
||||
if (isSelected)
|
||||
{
|
||||
UpdateValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 GetLookDirection()
|
||||
{
|
||||
Vector3 direction = m_Interactor.GetAttachTransform(this).position - m_Handle.position;
|
||||
direction = transform.InverseTransformDirection(direction);
|
||||
switch (m_JoystickMotion)
|
||||
{
|
||||
case JoystickType.FrontBack:
|
||||
direction.x = 0;
|
||||
break;
|
||||
case JoystickType.LeftRight:
|
||||
direction.z = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
direction.y = Mathf.Clamp(direction.y, 0.01f, 1.0f);
|
||||
return direction.normalized;
|
||||
}
|
||||
|
||||
void UpdateValue()
|
||||
{
|
||||
var lookDirection = GetLookDirection();
|
||||
|
||||
// Get up/down angle and left/right angle
|
||||
var upDownAngle = Mathf.Atan2(lookDirection.z, lookDirection.y) * Mathf.Rad2Deg;
|
||||
var leftRightAngle = Mathf.Atan2(lookDirection.x, lookDirection.y) * Mathf.Rad2Deg;
|
||||
|
||||
// Extract signs
|
||||
var signX = Mathf.Sign(leftRightAngle);
|
||||
var signY = Mathf.Sign(upDownAngle);
|
||||
|
||||
upDownAngle = Mathf.Abs(upDownAngle);
|
||||
leftRightAngle = Mathf.Abs(leftRightAngle);
|
||||
|
||||
var stickValue = new Vector2(leftRightAngle, upDownAngle) * (1.0f / m_MaxAngle);
|
||||
|
||||
// Clamp the stick value between 0 and 1 when doing everything but circular stick motion
|
||||
if (m_JoystickMotion != JoystickType.BothCircle)
|
||||
{
|
||||
stickValue.x = Mathf.Clamp01(stickValue.x);
|
||||
stickValue.y = Mathf.Clamp01(stickValue.y);
|
||||
}
|
||||
else
|
||||
{
|
||||
// With circular motion, if the stick value is greater than 1, we normalize
|
||||
// This way, an extremely strong value in one direction will influence the overall stick direction
|
||||
if (stickValue.magnitude > 1.0f)
|
||||
{
|
||||
stickValue.Normalize();
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild the angle values for visuals
|
||||
leftRightAngle = stickValue.x * signX * m_MaxAngle;
|
||||
upDownAngle = stickValue.y * signY * m_MaxAngle;
|
||||
|
||||
// Apply deadzone and sign back to the logical stick value
|
||||
var deadZone = m_DeadZoneAngle / m_MaxAngle;
|
||||
var aliveZone = (1.0f - deadZone);
|
||||
stickValue.x = Mathf.Clamp01((stickValue.x - deadZone)) / aliveZone;
|
||||
stickValue.y = Mathf.Clamp01((stickValue.y - deadZone)) / aliveZone;
|
||||
|
||||
// Re-apply signs
|
||||
stickValue.x *= signX;
|
||||
stickValue.y *= signY;
|
||||
|
||||
SetHandleAngle(new Vector2(leftRightAngle, upDownAngle));
|
||||
SetValue(stickValue);
|
||||
}
|
||||
|
||||
void SetValue(Vector2 value)
|
||||
{
|
||||
m_Value = value;
|
||||
m_OnValueChangeX.Invoke(m_Value.x);
|
||||
m_OnValueChangeY.Invoke(m_Value.y);
|
||||
}
|
||||
|
||||
void SetHandleAngle(Vector2 angles)
|
||||
{
|
||||
if (m_Handle == null)
|
||||
return;
|
||||
|
||||
var xComp = Mathf.Tan(angles.x * Mathf.Deg2Rad);
|
||||
var zComp = Mathf.Tan(angles.y * Mathf.Deg2Rad);
|
||||
var largerComp = Mathf.Max(Mathf.Abs(xComp), Mathf.Abs(zComp));
|
||||
var yComp = Mathf.Sqrt(1.0f - largerComp * largerComp);
|
||||
|
||||
m_Handle.up = (transform.up * yComp) + (transform.right * xComp) + (transform.forward * zComp);
|
||||
}
|
||||
|
||||
void OnDrawGizmosSelected()
|
||||
{
|
||||
var angleStartPoint = transform.position;
|
||||
|
||||
if (m_Handle != null)
|
||||
angleStartPoint = m_Handle.position;
|
||||
|
||||
const float k_AngleLength = 0.25f;
|
||||
|
||||
if (m_JoystickMotion != JoystickType.LeftRight)
|
||||
{
|
||||
Gizmos.color = Color.green;
|
||||
var axisPoint1 = angleStartPoint + transform.TransformDirection(Quaternion.Euler(m_MaxAngle, 0.0f, 0.0f) * Vector3.up) * k_AngleLength;
|
||||
var axisPoint2 = angleStartPoint + transform.TransformDirection(Quaternion.Euler(-m_MaxAngle, 0.0f, 0.0f) * Vector3.up) * k_AngleLength;
|
||||
Gizmos.DrawLine(angleStartPoint, axisPoint1);
|
||||
Gizmos.DrawLine(angleStartPoint, axisPoint2);
|
||||
|
||||
if (m_DeadZoneAngle > 0.0f)
|
||||
{
|
||||
Gizmos.color = Color.red;
|
||||
axisPoint1 = angleStartPoint + transform.TransformDirection(Quaternion.Euler(m_DeadZoneAngle, 0.0f, 0.0f) * Vector3.up) * k_AngleLength;
|
||||
axisPoint2 = angleStartPoint + transform.TransformDirection(Quaternion.Euler(-m_DeadZoneAngle, 0.0f, 0.0f) * Vector3.up) * k_AngleLength;
|
||||
Gizmos.DrawLine(angleStartPoint, axisPoint1);
|
||||
Gizmos.DrawLine(angleStartPoint, axisPoint2);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_JoystickMotion != JoystickType.FrontBack)
|
||||
{
|
||||
Gizmos.color = Color.green;
|
||||
var axisPoint1 = angleStartPoint + transform.TransformDirection(Quaternion.Euler(0.0f, 0.0f, m_MaxAngle) * Vector3.up) * k_AngleLength;
|
||||
var axisPoint2 = angleStartPoint + transform.TransformDirection(Quaternion.Euler(0.0f, 0.0f, -m_MaxAngle) * Vector3.up) * k_AngleLength;
|
||||
Gizmos.DrawLine(angleStartPoint, axisPoint1);
|
||||
Gizmos.DrawLine(angleStartPoint, axisPoint2);
|
||||
|
||||
if (m_DeadZoneAngle > 0.0f)
|
||||
{
|
||||
Gizmos.color = Color.red;
|
||||
axisPoint1 = angleStartPoint + transform.TransformDirection(Quaternion.Euler(0.0f, 0.0f, m_DeadZoneAngle) * Vector3.up) * k_AngleLength;
|
||||
axisPoint2 = angleStartPoint + transform.TransformDirection(Quaternion.Euler(0.0f, 0.0f, -m_DeadZoneAngle) * Vector3.up) * k_AngleLength;
|
||||
Gizmos.DrawLine(angleStartPoint, axisPoint1);
|
||||
Gizmos.DrawLine(angleStartPoint, axisPoint2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
m_DeadZoneAngle = Mathf.Min(m_DeadZoneAngle, m_MaxAngle * k_MaxDeadZonePercent);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/XRI_Examples/UI_3D/Scripts/XRJoystick.cs.meta
Normal file
11
Assets/XRI_Examples/UI_3D/Scripts/XRJoystick.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 754d27665ba38d44b9c1633441bdd691
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
425
Assets/XRI_Examples/UI_3D/Scripts/XRKnob.cs
Normal file
425
Assets/XRI_Examples/UI_3D/Scripts/XRKnob.cs
Normal file
@@ -0,0 +1,425 @@
|
||||
using System;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// An interactable knob that follows the rotation of the interactor
|
||||
/// </summary>
|
||||
public class XRKnob : XRBaseInteractable
|
||||
{
|
||||
const float k_ModeSwitchDeadZone = 0.1f; // Prevents rapid switching between the different rotation tracking modes
|
||||
|
||||
/// <summary>
|
||||
/// Helper class used to track rotations that can go beyond 180 degrees while minimizing accumulation error
|
||||
/// </summary>
|
||||
struct TrackedRotation
|
||||
{
|
||||
/// <summary>
|
||||
/// The anchor rotation we calculate an offset from
|
||||
/// </summary>
|
||||
float m_BaseAngle;
|
||||
|
||||
/// <summary>
|
||||
/// The target rotate we calculate the offset to
|
||||
/// </summary>
|
||||
float m_CurrentOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Any previous offsets we've added in
|
||||
/// </summary>
|
||||
float m_AccumulatedAngle;
|
||||
|
||||
/// <summary>
|
||||
/// The total rotation that occurred from when this rotation started being tracked
|
||||
/// </summary>
|
||||
public float totalOffset => m_AccumulatedAngle + m_CurrentOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Resets the tracked rotation so that total offset returns 0
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
m_BaseAngle = 0.0f;
|
||||
m_CurrentOffset = 0.0f;
|
||||
m_AccumulatedAngle = 0.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a new anchor rotation while maintaining any previously accumulated offset
|
||||
/// </summary>
|
||||
/// <param name="direction">The XZ vector used to calculate a rotation angle</param>
|
||||
public void SetBaseFromVector(Vector3 direction)
|
||||
{
|
||||
// Update any accumulated angle
|
||||
m_AccumulatedAngle += m_CurrentOffset;
|
||||
|
||||
// Now set a new base angle
|
||||
m_BaseAngle = Mathf.Atan2(direction.z, direction.x) * Mathf.Rad2Deg;
|
||||
m_CurrentOffset = 0.0f;
|
||||
}
|
||||
|
||||
public void SetTargetFromVector(Vector3 direction)
|
||||
{
|
||||
// Set the target angle
|
||||
var targetAngle = Mathf.Atan2(direction.z, direction.x) * Mathf.Rad2Deg;
|
||||
|
||||
// Return the offset
|
||||
m_CurrentOffset = ShortestAngleDistance(m_BaseAngle, targetAngle, 360.0f);
|
||||
|
||||
// If the offset is greater than 90 degrees, we update the base so we can rotate beyond 180 degrees
|
||||
if (Mathf.Abs(m_CurrentOffset) > 90.0f)
|
||||
{
|
||||
m_BaseAngle = targetAngle;
|
||||
m_AccumulatedAngle += m_CurrentOffset;
|
||||
m_CurrentOffset = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ValueChangeEvent : UnityEvent<float> { }
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The object that is visually grabbed and manipulated")]
|
||||
Transform m_Handle = null;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The value of the knob")]
|
||||
[Range(0.0f, 1.0f)]
|
||||
float m_Value = 0.5f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Whether this knob's rotation should be clamped by the angle limits")]
|
||||
bool m_ClampedMotion = true;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Rotation of the knob at value '1'")]
|
||||
float m_MaxAngle = 90.0f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Rotation of the knob at value '0'")]
|
||||
float m_MinAngle = -90.0f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Angle increments to support, if greater than '0'")]
|
||||
float m_AngleIncrement = 0.0f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The position of the interactor controls rotation when outside this radius")]
|
||||
float m_PositionTrackedRadius = 0.1f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("How much controller rotation ")]
|
||||
float m_TwistSensitivity = 1.5f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Events to trigger when the knob is rotated")]
|
||||
ValueChangeEvent m_OnValueChange = new ValueChangeEvent();
|
||||
|
||||
IXRSelectInteractor m_Interactor;
|
||||
|
||||
bool m_PositionDriven = false;
|
||||
bool m_UpVectorDriven = false;
|
||||
|
||||
TrackedRotation m_PositionAngles = new TrackedRotation();
|
||||
TrackedRotation m_UpVectorAngles = new TrackedRotation();
|
||||
TrackedRotation m_ForwardVectorAngles = new TrackedRotation();
|
||||
|
||||
float m_BaseKnobRotation = 0.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The object that is visually grabbed and manipulated
|
||||
/// </summary>
|
||||
public Transform handle
|
||||
{
|
||||
get => m_Handle;
|
||||
set => m_Handle = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value of the knob
|
||||
/// </summary>
|
||||
public float value
|
||||
{
|
||||
get => m_Value;
|
||||
set
|
||||
{
|
||||
SetValue(value);
|
||||
SetKnobRotation(ValueToRotation());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this knob's rotation should be clamped by the angle limits
|
||||
/// </summary>
|
||||
public bool clampedMotion
|
||||
{
|
||||
get => m_ClampedMotion;
|
||||
set => m_ClampedMotion = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotation of the knob at value '1'
|
||||
/// </summary>
|
||||
public float maxAngle
|
||||
{
|
||||
get => m_MaxAngle;
|
||||
set => m_MaxAngle = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotation of the knob at value '0'
|
||||
/// </summary>
|
||||
public float minAngle
|
||||
{
|
||||
get => m_MinAngle;
|
||||
set => m_MinAngle = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The position of the interactor controls rotation when outside this radius
|
||||
/// </summary>
|
||||
public float positionTrackedRadius
|
||||
{
|
||||
get => m_PositionTrackedRadius;
|
||||
set => m_PositionTrackedRadius = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Events to trigger when the knob is rotated
|
||||
/// </summary>
|
||||
public ValueChangeEvent onValueChange => m_OnValueChange;
|
||||
|
||||
void Start()
|
||||
{
|
||||
SetValue(m_Value);
|
||||
SetKnobRotation(ValueToRotation());
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
selectEntered.AddListener(StartGrab);
|
||||
selectExited.AddListener(EndGrab);
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
selectEntered.RemoveListener(StartGrab);
|
||||
selectExited.RemoveListener(EndGrab);
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
void StartGrab(SelectEnterEventArgs args)
|
||||
{
|
||||
m_Interactor = args.interactorObject;
|
||||
|
||||
m_PositionAngles.Reset();
|
||||
m_UpVectorAngles.Reset();
|
||||
m_ForwardVectorAngles.Reset();
|
||||
|
||||
UpdateBaseKnobRotation();
|
||||
UpdateRotation(true);
|
||||
}
|
||||
|
||||
void EndGrab(SelectExitEventArgs args)
|
||||
{
|
||||
m_Interactor = null;
|
||||
}
|
||||
|
||||
public override void ProcessInteractable(XRInteractionUpdateOrder.UpdatePhase updatePhase)
|
||||
{
|
||||
base.ProcessInteractable(updatePhase);
|
||||
|
||||
if (updatePhase == XRInteractionUpdateOrder.UpdatePhase.Dynamic)
|
||||
{
|
||||
if (isSelected)
|
||||
{
|
||||
UpdateRotation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateRotation(bool freshCheck = false)
|
||||
{
|
||||
// Are we in position offset or direction rotation mode?
|
||||
var interactorTransform = m_Interactor.GetAttachTransform(this);
|
||||
|
||||
// We cache the three potential sources of rotation - the position offset, the forward vector of the controller, and up vector of the controller
|
||||
// We store any data used for determining which rotation to use, then flatten the vectors to the local xz plane
|
||||
var localOffset = transform.InverseTransformVector(interactorTransform.position - m_Handle.position);
|
||||
localOffset.y = 0.0f;
|
||||
var radiusOffset = transform.TransformVector(localOffset).magnitude;
|
||||
localOffset.Normalize();
|
||||
|
||||
var localForward = transform.InverseTransformDirection(interactorTransform.forward);
|
||||
var localY = Math.Abs(localForward.y);
|
||||
localForward.y = 0.0f;
|
||||
localForward.Normalize();
|
||||
|
||||
var localUp = transform.InverseTransformDirection(interactorTransform.up);
|
||||
localUp.y = 0.0f;
|
||||
localUp.Normalize();
|
||||
|
||||
|
||||
if (m_PositionDriven && !freshCheck)
|
||||
radiusOffset *= (1.0f + k_ModeSwitchDeadZone);
|
||||
|
||||
// Determine when a certain source of rotation won't contribute - in that case we bake in the offset it has applied
|
||||
// and set a new anchor when they can contribute again
|
||||
if (radiusOffset >= m_PositionTrackedRadius)
|
||||
{
|
||||
if (!m_PositionDriven || freshCheck)
|
||||
{
|
||||
m_PositionAngles.SetBaseFromVector(localOffset);
|
||||
m_PositionDriven = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
m_PositionDriven = false;
|
||||
|
||||
// If it's not a fresh check, then we weight the local Y up or down to keep it from flickering back and forth at boundaries
|
||||
if (!freshCheck)
|
||||
{
|
||||
if (!m_UpVectorDriven)
|
||||
localY *= (1.0f - (k_ModeSwitchDeadZone * 0.5f));
|
||||
else
|
||||
localY *= (1.0f + (k_ModeSwitchDeadZone * 0.5f));
|
||||
}
|
||||
|
||||
if (localY > 0.707f)
|
||||
{
|
||||
if (!m_UpVectorDriven || freshCheck)
|
||||
{
|
||||
m_UpVectorAngles.SetBaseFromVector(localUp);
|
||||
m_UpVectorDriven = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_UpVectorDriven || freshCheck)
|
||||
{
|
||||
m_ForwardVectorAngles.SetBaseFromVector(localForward);
|
||||
m_UpVectorDriven = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get angle from position
|
||||
if (m_PositionDriven)
|
||||
m_PositionAngles.SetTargetFromVector(localOffset);
|
||||
|
||||
if (m_UpVectorDriven)
|
||||
m_UpVectorAngles.SetTargetFromVector(localUp);
|
||||
else
|
||||
m_ForwardVectorAngles.SetTargetFromVector(localForward);
|
||||
|
||||
// Apply offset to base knob rotation to get new knob rotation
|
||||
var knobRotation = m_BaseKnobRotation - ((m_UpVectorAngles.totalOffset + m_ForwardVectorAngles.totalOffset) * m_TwistSensitivity) - m_PositionAngles.totalOffset;
|
||||
|
||||
// Clamp to range
|
||||
if (m_ClampedMotion)
|
||||
knobRotation = Mathf.Clamp(knobRotation, m_MinAngle, m_MaxAngle);
|
||||
|
||||
SetKnobRotation(knobRotation);
|
||||
|
||||
// Reverse to get value
|
||||
var knobValue = (knobRotation - m_MinAngle) / (m_MaxAngle - m_MinAngle);
|
||||
SetValue(knobValue);
|
||||
}
|
||||
|
||||
void SetKnobRotation(float angle)
|
||||
{
|
||||
if (m_AngleIncrement > 0)
|
||||
{
|
||||
var normalizeAngle = angle - m_MinAngle;
|
||||
angle = (Mathf.Round(normalizeAngle / m_AngleIncrement) * m_AngleIncrement) + m_MinAngle;
|
||||
}
|
||||
|
||||
if (m_Handle != null)
|
||||
m_Handle.localEulerAngles = new Vector3(0.0f, angle, 0.0f);
|
||||
}
|
||||
|
||||
void SetValue(float value)
|
||||
{
|
||||
if (m_ClampedMotion)
|
||||
value = Mathf.Clamp01(value);
|
||||
|
||||
if (m_AngleIncrement > 0)
|
||||
{
|
||||
var angleRange = m_MaxAngle - m_MinAngle;
|
||||
var angle = Mathf.Lerp(0.0f, angleRange, value);
|
||||
angle = Mathf.Round(angle / m_AngleIncrement) * m_AngleIncrement;
|
||||
value = Mathf.InverseLerp(0.0f, angleRange, angle);
|
||||
}
|
||||
|
||||
m_Value = value;
|
||||
m_OnValueChange.Invoke(m_Value);
|
||||
}
|
||||
|
||||
float ValueToRotation()
|
||||
{
|
||||
return m_ClampedMotion ? Mathf.Lerp(m_MinAngle, m_MaxAngle, m_Value) : Mathf.LerpUnclamped(m_MinAngle, m_MaxAngle, m_Value);
|
||||
}
|
||||
|
||||
void UpdateBaseKnobRotation()
|
||||
{
|
||||
m_BaseKnobRotation = Mathf.LerpUnclamped(m_MinAngle, m_MaxAngle, m_Value);
|
||||
}
|
||||
|
||||
static float ShortestAngleDistance(float start, float end, float max)
|
||||
{
|
||||
var angleDelta = end - start;
|
||||
var angleSign = Mathf.Sign(angleDelta);
|
||||
|
||||
angleDelta = Math.Abs(angleDelta) % max;
|
||||
if (angleDelta > (max * 0.5f))
|
||||
angleDelta = -(max - angleDelta);
|
||||
|
||||
return angleDelta * angleSign;
|
||||
}
|
||||
|
||||
void OnDrawGizmosSelected()
|
||||
{
|
||||
const int k_CircleSegments = 16;
|
||||
const float k_SegmentRatio = 1.0f / k_CircleSegments;
|
||||
|
||||
// Nothing to do if position radius is too small
|
||||
if (m_PositionTrackedRadius <= Mathf.Epsilon)
|
||||
return;
|
||||
|
||||
// Draw a circle from the handle point at size of position tracked radius
|
||||
var circleCenter = transform.position;
|
||||
|
||||
if (m_Handle != null)
|
||||
circleCenter = m_Handle.position;
|
||||
|
||||
var circleX = transform.right;
|
||||
var circleY = transform.forward;
|
||||
|
||||
Gizmos.color = Color.green;
|
||||
var segmentCounter = 0;
|
||||
while (segmentCounter < k_CircleSegments)
|
||||
{
|
||||
var startAngle = (float)segmentCounter * k_SegmentRatio * 2.0f * Mathf.PI;
|
||||
segmentCounter++;
|
||||
var endAngle = (float)segmentCounter * k_SegmentRatio * 2.0f * Mathf.PI;
|
||||
|
||||
Gizmos.DrawLine(circleCenter + (Mathf.Cos(startAngle) * circleX + Mathf.Sin(startAngle) * circleY) * m_PositionTrackedRadius,
|
||||
circleCenter + (Mathf.Cos(endAngle) * circleX + Mathf.Sin(endAngle) * circleY) * m_PositionTrackedRadius);
|
||||
}
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
if (m_ClampedMotion)
|
||||
m_Value = Mathf.Clamp01(m_Value);
|
||||
|
||||
if (m_MinAngle > m_MaxAngle)
|
||||
m_MinAngle = m_MaxAngle;
|
||||
|
||||
SetKnobRotation(ValueToRotation());
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/XRI_Examples/UI_3D/Scripts/XRKnob.cs.meta
Normal file
11
Assets/XRI_Examples/UI_3D/Scripts/XRKnob.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65a8500fa86faa04596fb5f9d40efeae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
228
Assets/XRI_Examples/UI_3D/Scripts/XRLever.cs
Normal file
228
Assets/XRI_Examples/UI_3D/Scripts/XRLever.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// An interactable lever that snaps into an on or off position by a direct interactor
|
||||
/// </summary>
|
||||
public class XRLever : XRBaseInteractable
|
||||
{
|
||||
const float k_LeverDeadZone = 0.1f; // Prevents rapid switching between on and off states when right in the middle
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The object that is visually grabbed and manipulated")]
|
||||
Transform m_Handle = null;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The value of the lever")]
|
||||
bool m_Value = false;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("If enabled, the lever will snap to the value position when released")]
|
||||
bool m_LockToValue;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Angle of the lever in the 'on' position")]
|
||||
[Range(-90.0f, 90.0f)]
|
||||
float m_MaxAngle = 90.0f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Angle of the lever in the 'off' position")]
|
||||
[Range(-90.0f, 90.0f)]
|
||||
float m_MinAngle = -90.0f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Events to trigger when the lever activates")]
|
||||
UnityEvent m_OnLeverActivate = new UnityEvent();
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Events to trigger when the lever deactivates")]
|
||||
UnityEvent m_OnLeverDeactivate = new UnityEvent();
|
||||
|
||||
IXRSelectInteractor m_Interactor;
|
||||
|
||||
/// <summary>
|
||||
/// The object that is visually grabbed and manipulated
|
||||
/// </summary>
|
||||
public Transform handle
|
||||
{
|
||||
get => m_Handle;
|
||||
set => m_Handle = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value of the lever
|
||||
/// </summary>
|
||||
public bool value
|
||||
{
|
||||
get => m_Value;
|
||||
set => SetValue(value, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If enabled, the lever will snap to the value position when released
|
||||
/// </summary>
|
||||
public bool lockToValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Angle of the lever in the 'on' position
|
||||
/// </summary>
|
||||
public float maxAngle
|
||||
{
|
||||
get => m_MaxAngle;
|
||||
set => m_MaxAngle = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Angle of the lever in the 'off' position
|
||||
/// </summary>
|
||||
public float minAngle
|
||||
{
|
||||
get => m_MinAngle;
|
||||
set => m_MinAngle = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Events to trigger when the lever activates
|
||||
/// </summary>
|
||||
public UnityEvent onLeverActivate => m_OnLeverActivate;
|
||||
|
||||
/// <summary>
|
||||
/// Events to trigger when the lever deactivates
|
||||
/// </summary>
|
||||
public UnityEvent onLeverDeactivate => m_OnLeverDeactivate;
|
||||
|
||||
void Start()
|
||||
{
|
||||
SetValue(m_Value, true);
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
selectEntered.AddListener(StartGrab);
|
||||
selectExited.AddListener(EndGrab);
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
selectEntered.RemoveListener(StartGrab);
|
||||
selectExited.RemoveListener(EndGrab);
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
void StartGrab(SelectEnterEventArgs args)
|
||||
{
|
||||
m_Interactor = args.interactorObject;
|
||||
}
|
||||
|
||||
void EndGrab(SelectExitEventArgs args)
|
||||
{
|
||||
SetValue(m_Value, true);
|
||||
m_Interactor = null;
|
||||
}
|
||||
|
||||
public override void ProcessInteractable(XRInteractionUpdateOrder.UpdatePhase updatePhase)
|
||||
{
|
||||
base.ProcessInteractable(updatePhase);
|
||||
|
||||
if (updatePhase == XRInteractionUpdateOrder.UpdatePhase.Dynamic)
|
||||
{
|
||||
if (isSelected)
|
||||
{
|
||||
UpdateValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 GetLookDirection()
|
||||
{
|
||||
Vector3 direction = m_Interactor.GetAttachTransform(this).position - m_Handle.position;
|
||||
direction = transform.InverseTransformDirection(direction);
|
||||
direction.x = 0;
|
||||
|
||||
return direction.normalized;
|
||||
}
|
||||
|
||||
void UpdateValue()
|
||||
{
|
||||
var lookDirection = GetLookDirection();
|
||||
var lookAngle = Mathf.Atan2(lookDirection.z, lookDirection.y) * Mathf.Rad2Deg;
|
||||
|
||||
if (m_MinAngle < m_MaxAngle)
|
||||
lookAngle = Mathf.Clamp(lookAngle, m_MinAngle, m_MaxAngle);
|
||||
else
|
||||
lookAngle = Mathf.Clamp(lookAngle, m_MaxAngle, m_MinAngle);
|
||||
|
||||
var maxAngleDistance = Mathf.Abs(m_MaxAngle - lookAngle);
|
||||
var minAngleDistance = Mathf.Abs(m_MinAngle - lookAngle);
|
||||
|
||||
if (m_Value)
|
||||
maxAngleDistance *= (1.0f - k_LeverDeadZone);
|
||||
else
|
||||
minAngleDistance *= (1.0f - k_LeverDeadZone);
|
||||
|
||||
var newValue = (maxAngleDistance < minAngleDistance);
|
||||
|
||||
SetHandleAngle(lookAngle);
|
||||
|
||||
SetValue(newValue);
|
||||
}
|
||||
|
||||
void SetValue(bool isOn, bool forceRotation = false)
|
||||
{
|
||||
if (m_Value == isOn)
|
||||
{
|
||||
if (forceRotation)
|
||||
SetHandleAngle(m_Value ? m_MaxAngle : m_MinAngle);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
m_Value = isOn;
|
||||
|
||||
if (m_Value)
|
||||
{
|
||||
m_OnLeverActivate.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_OnLeverDeactivate.Invoke();
|
||||
}
|
||||
|
||||
if (!isSelected && (m_LockToValue || forceRotation))
|
||||
SetHandleAngle(m_Value ? m_MaxAngle : m_MinAngle);
|
||||
}
|
||||
|
||||
void SetHandleAngle(float angle)
|
||||
{
|
||||
if (m_Handle != null)
|
||||
m_Handle.localRotation = Quaternion.Euler(angle, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
void OnDrawGizmosSelected()
|
||||
{
|
||||
var angleStartPoint = transform.position;
|
||||
|
||||
if (m_Handle != null)
|
||||
angleStartPoint = m_Handle.position;
|
||||
|
||||
const float k_AngleLength = 0.25f;
|
||||
|
||||
var angleMaxPoint = angleStartPoint + transform.TransformDirection(Quaternion.Euler(m_MaxAngle, 0.0f, 0.0f) * Vector3.up) * k_AngleLength;
|
||||
var angleMinPoint = angleStartPoint + transform.TransformDirection(Quaternion.Euler(m_MinAngle, 0.0f, 0.0f) * Vector3.up) * k_AngleLength;
|
||||
|
||||
Gizmos.color = Color.green;
|
||||
Gizmos.DrawLine(angleStartPoint, angleMaxPoint);
|
||||
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawLine(angleStartPoint, angleMinPoint);
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
SetHandleAngle(m_Value ? m_MaxAngle : m_MinAngle);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/XRI_Examples/UI_3D/Scripts/XRLever.cs.meta
Normal file
11
Assets/XRI_Examples/UI_3D/Scripts/XRLever.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e601cf28e9702c945abe3b90e7f51974
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
292
Assets/XRI_Examples/UI_3D/Scripts/XRPushButton.cs
Normal file
292
Assets/XRI_Examples/UI_3D/Scripts/XRPushButton.cs
Normal file
@@ -0,0 +1,292 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// An interactable that can be pushed by a direct interactor's movement
|
||||
/// </summary>
|
||||
public class XRPushButton : XRBaseInteractable
|
||||
{
|
||||
class PressInfo
|
||||
{
|
||||
internal IXRHoverInteractor m_Interactor;
|
||||
internal bool m_InPressRegion = false;
|
||||
internal bool m_WrongSide = false;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ValueChangeEvent : UnityEvent<float> { }
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The object that is visually pressed down")]
|
||||
Transform m_Button = null;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The distance the button can be pressed")]
|
||||
float m_PressDistance = 0.1f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Extra distance for clicking the button down")]
|
||||
float m_PressBuffer = 0.01f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Offset from the button base to start testing for push")]
|
||||
float m_ButtonOffset = 0.0f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("How big of a surface area is available for pressing the button")]
|
||||
float m_ButtonSize = 0.1f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Treat this button like an on/off toggle")]
|
||||
bool m_ToggleButton = false;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Events to trigger when the button is pressed")]
|
||||
UnityEvent m_OnPress;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Events to trigger when the button is released")]
|
||||
UnityEvent m_OnRelease;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Events to trigger when the button pressed value is updated. Only called when the button is pressed")]
|
||||
ValueChangeEvent m_OnValueChange;
|
||||
|
||||
bool m_Pressed = false;
|
||||
bool m_Toggled = false;
|
||||
float m_Value = 0f;
|
||||
Vector3 m_BaseButtonPosition = Vector3.zero;
|
||||
|
||||
Dictionary<IXRHoverInteractor, PressInfo> m_HoveringInteractors = new Dictionary<IXRHoverInteractor, PressInfo>();
|
||||
|
||||
/// <summary>
|
||||
/// The object that is visually pressed down
|
||||
/// </summary>
|
||||
public Transform button
|
||||
{
|
||||
get => m_Button;
|
||||
set => m_Button = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The distance the button can be pressed
|
||||
/// </summary>
|
||||
public float pressDistance
|
||||
{
|
||||
get => m_PressDistance;
|
||||
set => m_PressDistance = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The distance (in percentage from 0 to 1) the button is currently being held down
|
||||
/// </summary>
|
||||
public float value => m_Value;
|
||||
|
||||
/// <summary>
|
||||
/// Events to trigger when the button is pressed
|
||||
/// </summary>
|
||||
public UnityEvent onPress => m_OnPress;
|
||||
|
||||
/// <summary>
|
||||
/// Events to trigger when the button is released
|
||||
/// </summary>
|
||||
public UnityEvent onRelease => m_OnRelease;
|
||||
|
||||
/// <summary>
|
||||
/// Events to trigger when the button distance value is changed. Only called when the button is pressed
|
||||
/// </summary>
|
||||
public ValueChangeEvent onValueChange => m_OnValueChange;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not a toggle button is in the locked down position
|
||||
/// </summary>
|
||||
public bool toggleValue
|
||||
{
|
||||
get => m_ToggleButton && m_Toggled;
|
||||
set
|
||||
{
|
||||
if (!m_ToggleButton)
|
||||
return;
|
||||
|
||||
m_Toggled = value;
|
||||
if (m_Toggled)
|
||||
SetButtonHeight(-m_PressDistance);
|
||||
else
|
||||
SetButtonHeight(0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsHoverableBy(IXRHoverInteractor interactor)
|
||||
{
|
||||
if (interactor is XRRayInteractor)
|
||||
return false;
|
||||
|
||||
return base.IsHoverableBy(interactor);
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (m_Button != null)
|
||||
m_BaseButtonPosition = m_Button.position;
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
if (m_Toggled)
|
||||
SetButtonHeight(-m_PressDistance);
|
||||
else
|
||||
SetButtonHeight(0.0f);
|
||||
|
||||
hoverEntered.AddListener(StartHover);
|
||||
hoverExited.AddListener(EndHover);
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
hoverEntered.RemoveListener(StartHover);
|
||||
hoverExited.RemoveListener(EndHover);
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
void StartHover(HoverEnterEventArgs args)
|
||||
{
|
||||
m_HoveringInteractors.Add(args.interactorObject, new PressInfo { m_Interactor = args.interactorObject });
|
||||
}
|
||||
|
||||
void EndHover(HoverExitEventArgs args)
|
||||
{
|
||||
m_HoveringInteractors.Remove(args.interactorObject);
|
||||
|
||||
if (m_HoveringInteractors.Count == 0)
|
||||
{
|
||||
if (m_ToggleButton && m_Toggled)
|
||||
SetButtonHeight(-m_PressDistance);
|
||||
else
|
||||
SetButtonHeight(0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ProcessInteractable(XRInteractionUpdateOrder.UpdatePhase updatePhase)
|
||||
{
|
||||
base.ProcessInteractable(updatePhase);
|
||||
|
||||
if (updatePhase == XRInteractionUpdateOrder.UpdatePhase.Dynamic)
|
||||
{
|
||||
if (m_HoveringInteractors.Count > 0)
|
||||
{
|
||||
UpdatePress();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdatePress()
|
||||
{
|
||||
var minimumHeight = 0.0f;
|
||||
|
||||
if (m_ToggleButton && m_Toggled)
|
||||
minimumHeight = -m_PressDistance;
|
||||
|
||||
// Go through each interactor
|
||||
foreach (var pressInfo in m_HoveringInteractors.Values)
|
||||
{
|
||||
var interactorTransform = pressInfo.m_Interactor.GetAttachTransform(this);
|
||||
var localOffset = transform.InverseTransformVector(interactorTransform.position - m_BaseButtonPosition);
|
||||
|
||||
var withinButtonRegion = (Mathf.Abs(localOffset.x) < m_ButtonSize && Mathf.Abs(localOffset.z) < m_ButtonSize);
|
||||
if (withinButtonRegion)
|
||||
{
|
||||
if (!pressInfo.m_InPressRegion)
|
||||
{
|
||||
pressInfo.m_WrongSide = (localOffset.y < m_ButtonOffset);
|
||||
}
|
||||
|
||||
if (!pressInfo.m_WrongSide)
|
||||
minimumHeight = Mathf.Min(minimumHeight, localOffset.y - m_ButtonOffset);
|
||||
}
|
||||
|
||||
pressInfo.m_InPressRegion = withinButtonRegion;
|
||||
}
|
||||
|
||||
minimumHeight = Mathf.Max(minimumHeight, -(m_PressDistance + m_PressBuffer));
|
||||
|
||||
// If button height goes below certain amount, enter press mode
|
||||
var pressed = m_ToggleButton ? (minimumHeight <= -(m_PressDistance + m_PressBuffer)) : (minimumHeight < -m_PressDistance);
|
||||
|
||||
var currentDistance = Mathf.Max(0f, -minimumHeight - m_PressBuffer);
|
||||
m_Value = currentDistance / m_PressDistance;
|
||||
|
||||
if (m_ToggleButton)
|
||||
{
|
||||
if (pressed)
|
||||
{
|
||||
if (!m_Pressed)
|
||||
{
|
||||
m_Toggled = !m_Toggled;
|
||||
|
||||
if (m_Toggled)
|
||||
m_OnPress.Invoke();
|
||||
else
|
||||
m_OnRelease.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pressed)
|
||||
{
|
||||
if (!m_Pressed)
|
||||
m_OnPress.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_Pressed)
|
||||
m_OnRelease.Invoke();
|
||||
}
|
||||
}
|
||||
m_Pressed = pressed;
|
||||
|
||||
// Call value change event
|
||||
if (m_Pressed)
|
||||
m_OnValueChange.Invoke(m_Value);
|
||||
|
||||
SetButtonHeight(minimumHeight);
|
||||
}
|
||||
|
||||
void SetButtonHeight(float height)
|
||||
{
|
||||
if (m_Button == null)
|
||||
return;
|
||||
|
||||
Vector3 newPosition = m_Button.localPosition;
|
||||
newPosition.y = height;
|
||||
m_Button.localPosition = newPosition;
|
||||
}
|
||||
|
||||
void OnDrawGizmosSelected()
|
||||
{
|
||||
var pressStartPoint = Vector3.zero;
|
||||
|
||||
if (m_Button != null)
|
||||
{
|
||||
pressStartPoint = m_Button.localPosition;
|
||||
}
|
||||
|
||||
pressStartPoint.y += m_ButtonOffset - (m_PressDistance * 0.5f);
|
||||
|
||||
Gizmos.color = Color.green;
|
||||
Gizmos.matrix = transform.localToWorldMatrix;
|
||||
Gizmos.DrawWireCube(pressStartPoint, new Vector3(m_ButtonSize, m_PressDistance, m_ButtonSize));
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
SetButtonHeight(0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/XRI_Examples/UI_3D/Scripts/XRPushButton.cs.meta
Normal file
11
Assets/XRI_Examples/UI_3D/Scripts/XRPushButton.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 859e3c78cc08e12428cc7117416c5206
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
139
Assets/XRI_Examples/UI_3D/Scripts/XRSlider.cs
Normal file
139
Assets/XRI_Examples/UI_3D/Scripts/XRSlider.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.XR.Interaction.Toolkit;
|
||||
|
||||
namespace UnityEngine.XR.Content.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// An interactable that follows the position of the interactor on a single axis
|
||||
/// </summary>
|
||||
public class XRSlider : XRBaseInteractable
|
||||
{
|
||||
[Serializable]
|
||||
public class ValueChangeEvent : UnityEvent<float> { }
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The object that is visually grabbed and manipulated")]
|
||||
Transform m_Handle = null;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The value of the slider")]
|
||||
[Range(0.0f, 1.0f)]
|
||||
float m_Value = 0.5f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The offset of the slider at value '1'")]
|
||||
float m_MaxPosition = 0.5f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The offset of the slider at value '0'")]
|
||||
float m_MinPosition = -0.5f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Events to trigger when the slider is moved")]
|
||||
ValueChangeEvent m_OnValueChange = new ValueChangeEvent();
|
||||
|
||||
IXRSelectInteractor m_Interactor;
|
||||
|
||||
/// <summary>
|
||||
/// The value of the slider
|
||||
/// </summary>
|
||||
public float value
|
||||
{
|
||||
get => m_Value;
|
||||
set
|
||||
{
|
||||
SetValue(value);
|
||||
SetSliderPosition(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Events to trigger when the slider is moved
|
||||
/// </summary>
|
||||
public ValueChangeEvent onValueChange => m_OnValueChange;
|
||||
|
||||
void Start()
|
||||
{
|
||||
SetValue(m_Value);
|
||||
SetSliderPosition(m_Value);
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
selectEntered.AddListener(StartGrab);
|
||||
selectExited.AddListener(EndGrab);
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
selectEntered.RemoveListener(StartGrab);
|
||||
selectExited.RemoveListener(EndGrab);
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
void StartGrab(SelectEnterEventArgs args)
|
||||
{
|
||||
m_Interactor = args.interactorObject;
|
||||
UpdateSliderPosition();
|
||||
}
|
||||
|
||||
void EndGrab(SelectExitEventArgs args)
|
||||
{
|
||||
m_Interactor = null;
|
||||
}
|
||||
|
||||
public override void ProcessInteractable(XRInteractionUpdateOrder.UpdatePhase updatePhase)
|
||||
{
|
||||
base.ProcessInteractable(updatePhase);
|
||||
|
||||
if (updatePhase == XRInteractionUpdateOrder.UpdatePhase.Dynamic)
|
||||
{
|
||||
if (isSelected)
|
||||
{
|
||||
UpdateSliderPosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateSliderPosition()
|
||||
{
|
||||
// Put anchor position into slider space
|
||||
var localPosition = transform.InverseTransformPoint(m_Interactor.GetAttachTransform(this).position);
|
||||
var sliderValue = Mathf.Clamp01((localPosition.z - m_MinPosition) / (m_MaxPosition - m_MinPosition));
|
||||
SetValue(sliderValue);
|
||||
SetSliderPosition(sliderValue);
|
||||
}
|
||||
|
||||
void SetSliderPosition(float value)
|
||||
{
|
||||
if (m_Handle == null)
|
||||
return;
|
||||
|
||||
var handlePos = m_Handle.localPosition;
|
||||
handlePos.z = Mathf.Lerp(m_MinPosition, m_MaxPosition, value);
|
||||
m_Handle.localPosition = handlePos;
|
||||
}
|
||||
|
||||
void SetValue(float value)
|
||||
{
|
||||
m_Value = value;
|
||||
m_OnValueChange.Invoke(m_Value);
|
||||
}
|
||||
|
||||
void OnDrawGizmosSelected()
|
||||
{
|
||||
var sliderMinPoint = transform.TransformPoint(new Vector3(0.0f, 0.0f, m_MinPosition));
|
||||
var sliderMaxPoint = transform.TransformPoint(new Vector3(0.0f, 0.0f, m_MaxPosition));
|
||||
|
||||
Gizmos.color = Color.green;
|
||||
Gizmos.DrawLine(sliderMinPoint, sliderMaxPoint);
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
SetSliderPosition(m_Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/XRI_Examples/UI_3D/Scripts/XRSlider.cs.meta
Normal file
11
Assets/XRI_Examples/UI_3D/Scripts/XRSlider.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d494b658b37958449db82c0ee015fbb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user