1
0
forked from cgvr/DeltaVR

deltavr multiplayer 2.0

This commit is contained in:
Toomas Tamm
2023-05-08 15:56:10 +03:00
parent 978809a002
commit 07b9b9e2f4
10937 changed files with 2968397 additions and 1521012 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 60d5463e9022bbc4b968fab685923068
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 025c853820e7fb844819416ecae0f5e9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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--;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6d0c2d7cf5f0ee141a994934c9d2f1a1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e795a5f3d4373eb459b9925fc3c62013
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: acde65f5db6773e49ad668404cf55472
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 754d27665ba38d44b9c1633441bdd691
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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());
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 65a8500fa86faa04596fb5f9d40efeae
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e601cf28e9702c945abe3b90e7f51974
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 859e3c78cc08e12428cc7117416c5206
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0d494b658b37958449db82c0ee015fbb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: