Files
dungeons/Assets/UltimateXR/Runtime/Scripts/Devices/UxrControllerInput.cs
2024-08-06 21:58:35 +02:00

1539 lines
68 KiB
C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrControllerInput.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Animation.GameObjects;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components.Composite;
using UltimateXR.Core.Settings;
using UltimateXR.Devices.Integrations;
using UltimateXR.Devices.Visualization;
using UltimateXR.Extensions.System;
using UltimateXR.Extensions.System.Collections;
using UltimateXR.Extensions.System.Math;
using UltimateXR.Extensions.Unity;
using UltimateXR.Haptics;
using UltimateXR.Manipulation;
using UnityEngine;
namespace UltimateXR.Devices
{
/// <summary>
/// Controller base class for all VR input devices, supporting single controllers and dual controller setups.
/// </summary>
public abstract partial class UxrControllerInput : UxrAvatarComponent<UxrControllerInput>, IUxrControllerInput, IUxrControllerInputUpdater
{
#region Inspector Properties/Serialized Fields
[Header("Controllers in the avatar hierarchy:")] [SerializeField] protected UxrController3DModel _leftController;
[SerializeField] protected UxrController3DModel _rightController;
[SerializeField] protected UxrController3DModel _controller;
[Header("Avatar objects to enable when active:")] [Tooltip("Enables game objects based when the left input device is present.")] [SerializeField] protected List<GameObject> _enableObjectListLeft;
[Tooltip( "Enables game objects based when the right input device is present.")] [SerializeField] protected List<GameObject> _enableObjectListRight;
[Header("Avatar objects to enable when active:")] [Tooltip("Enables game objects based on the presence of the input device.")] [SerializeField] protected List<GameObject> _enableObjectList;
#endregion
#region Public Types & Data
/// <summary>
/// Event called whenever any controller input device is connected or disconnected
/// </summary>
public static event EventHandler<UxrDeviceConnectEventArgs> GlobalControllerConnected;
/// <summary>
/// Event called after any controller button state changed.
/// </summary>
public static event EventHandler<UxrInputButtonEventArgs> GlobalButtonStateChanged;
/// <summary>
/// Event called after any <see cref="UxrInput1D" /> element changed.
/// </summary>
public static event EventHandler<UxrInput1DEventArgs> GlobalInput1DChanged;
/// <summary>
/// Event called after any <see cref="UxrInput2D" /> element changed.
/// </summary>
public static event EventHandler<UxrInput2DEventArgs> GlobalInput2DChanged;
/// <summary>
/// Event called right before any haptic feedback was requested.
/// </summary>
public static event EventHandler<UxrControllerHapticEventArgs> GlobalHapticRequesting;
/// <summary>
/// Gets the current input component, which is the enabled input component belonging to the local avatar.
/// If the avatar has no input component enabled, it will return a dummy input so that there is no need to check
/// for nulls. This dummy component doesn't generate input events.
/// The only way to get a null would be if there is no local avatar in the scene.
/// </summary>
public static UxrControllerInput Current
{
get
{
// We use this one first because it will return a dummy component if there is no input component enabled
if (LocalAvatar)
{
return LocalAvatar.ControllerInput;
}
// Else, return first enabled non-dummy component. Can be null.
return EnabledComponentsInLocalAvatar.FirstOrDefault(i => i.GetType() != typeof(UxrDummyControllerInput));
}
}
#endregion
#region Implicit IUxrControllerInput
/// <inheritdoc />
public abstract UxrControllerSetupType SetupType { get; }
/// <inheritdoc />
public abstract bool IsHandednessSupported { get; }
/// <inheritdoc />
public virtual string LeftControllerName => string.Empty;
/// <inheritdoc />
public virtual string RightControllerName => string.Empty;
/// <inheritdoc />
public virtual bool MainJoystickIsTouchpad => false;
/// <inheritdoc />
public virtual float JoystickDeadZone => 0.15f;
/// <inheritdoc />
public virtual UxrHandSide Handedness { get; set; } = UxrHandSide.Right;
/// <inheritdoc />
public UxrHandSide Primary => Handedness == UxrHandSide.Left ? UxrHandSide.Left : UxrHandSide.Right;
/// <inheritdoc />
public UxrHandSide Secondary => Handedness == UxrHandSide.Left ? UxrHandSide.Right : UxrHandSide.Left;
/// <inheritdoc />
public UxrController3DModel LeftController3DModel => SetupType == UxrControllerSetupType.Dual ? _leftController : _controller;
/// <inheritdoc />
public UxrController3DModel RightController3DModel => SetupType == UxrControllerSetupType.Dual ? _rightController : _controller;
/// <inheritdoc />
public event EventHandler Updating;
/// <inheritdoc />
public event EventHandler Updated;
/// <inheritdoc />
public event EventHandler<UxrInputButtonEventArgs> ButtonStateChanged;
/// <inheritdoc />
public event EventHandler<UxrInput1DEventArgs> Input1DChanged;
/// <inheritdoc />
public event EventHandler<UxrInput2DEventArgs> Input2DChanged;
/// <inheritdoc />
public event EventHandler<UxrControllerHapticEventArgs> HapticRequesting;
/// <inheritdoc />
public abstract bool IsControllerEnabled(UxrHandSide handSide);
/// <inheritdoc />
public abstract bool HasControllerElements(UxrHandSide handSide, UxrControllerElements controllerElement);
/// <inheritdoc />
public abstract UxrControllerInputCapabilities GetControllerCapabilities(UxrHandSide handSide);
/// <inheritdoc />
public abstract float GetInput1D(UxrHandSide handSide, UxrInput1D input1D, bool getIgnoredInput = false);
/// <inheritdoc />
public abstract Vector2 GetInput2D(UxrHandSide handSide, UxrInput2D input2D, bool getIgnoredInput = false);
/// <inheritdoc />
public virtual void SendHapticFeedback(UxrHandSide handSide, UxrHapticClip hapticClip)
{
OnHapticRequesting(new UxrControllerHapticEventArgs(handSide, hapticClip));
}
/// <inheritdoc />
public virtual void SendHapticFeedback(UxrHandSide handSide,
float frequency,
float amplitude,
float durationSeconds,
UxrHapticMode hapticMode = UxrHapticMode.Mix)
{
OnHapticRequesting(new UxrControllerHapticEventArgs(handSide, frequency, amplitude, durationSeconds, hapticMode));
}
/// <inheritdoc />
public virtual void StopHapticFeedback(UxrHandSide handSide)
{
OnHapticRequesting(UxrControllerHapticEventArgs.GetHapticStopEvent(handSide));
}
/// <inheritdoc />
public uint GetButtonTouchFlags(UxrHandSide handSide, bool getIgnoredInput = false)
{
if (ShouldIgnoreInput(handSide, getIgnoredInput))
{
return 0;
}
return handSide == UxrHandSide.Right ? _buttonTouchFlagsRight : _buttonTouchFlagsLeft;
}
/// <inheritdoc />
public uint GetButtonTouchFlagsLastFrame(UxrHandSide handSide, bool getIgnoredInput = false)
{
if (ShouldIgnoreInput(handSide, getIgnoredInput))
{
return 0;
}
return handSide == UxrHandSide.Right ? _buttonTouchFlagsLastFrameRight : _buttonTouchFlagsLastFrameLeft;
}
/// <inheritdoc />
public uint GetButtonPressFlags(UxrHandSide handSide, bool getIgnoredInput = false)
{
if (ShouldIgnoreInput(handSide, getIgnoredInput))
{
return 0;
}
return handSide == UxrHandSide.Right ? _buttonPressFlagsRight : _buttonPressFlagsLeft;
}
/// <inheritdoc />
public uint GetButtonPressFlagsLastFrame(UxrHandSide handSide, bool getIgnoredInput = false)
{
if (ShouldIgnoreInput(handSide, getIgnoredInput))
{
return 0;
}
return handSide == UxrHandSide.Right ? _buttonPressFlagsLastFrameRight : _buttonPressFlagsLastFrameLeft;
}
/// <inheritdoc />
public bool GetButtonsEvent(UxrHandSide handSide, UxrInputButtons buttons, UxrButtonEventType buttonEventType, bool getIgnoredInput = false)
{
return buttonEventType switch
{
UxrButtonEventType.Touching => GetButtonsTouch(handSide, buttons, getIgnoredInput),
UxrButtonEventType.TouchDown => GetButtonsTouchDown(handSide, buttons, getIgnoredInput),
UxrButtonEventType.TouchUp => GetButtonsTouchUp(handSide, buttons, getIgnoredInput),
UxrButtonEventType.Pressing => GetButtonsPress(handSide, buttons, getIgnoredInput),
UxrButtonEventType.PressDown => GetButtonsPressDown(handSide, buttons, getIgnoredInput),
UxrButtonEventType.PressUp => GetButtonsPressUp(handSide, buttons, getIgnoredInput),
_ => false
};
}
/// <inheritdoc />
public bool GetButtonsEventAny(UxrHandSide handSide, UxrInputButtons buttons, UxrButtonEventType buttonEventType, bool getIgnoredInput = false)
{
return buttonEventType switch
{
UxrButtonEventType.Touching => GetButtonsTouchAny(handSide, buttons, getIgnoredInput),
UxrButtonEventType.TouchDown => GetButtonsTouchDownAny(handSide, buttons, getIgnoredInput),
UxrButtonEventType.TouchUp => GetButtonsTouchUpAny(handSide, buttons, getIgnoredInput),
UxrButtonEventType.Pressing => GetButtonsPressAny(handSide, buttons, getIgnoredInput),
UxrButtonEventType.PressDown => GetButtonsPressDownAny(handSide, buttons, getIgnoredInput),
UxrButtonEventType.PressUp => GetButtonsPressUpAny(handSide, buttons, getIgnoredInput),
_ => false
};
}
/// <inheritdoc />
public bool GetButtonsTouch(UxrHandSide handSide, UxrInputButtons buttons, bool getIgnoredInput = false)
{
if (buttons == UxrInputButtons.Any)
{
return GetButtonTouchFlags(handSide, getIgnoredInput) != 0;
}
return GetButtonTouchFlags(handSide, getIgnoredInput).HasFlags((uint)buttons);
}
/// <inheritdoc />
public bool GetButtonsTouchAny(UxrHandSide handSide, UxrInputButtons buttons, bool getIgnoredInput = false)
{
return (GetButtonTouchFlags(handSide, getIgnoredInput) & (uint)buttons) != 0;
}
/// <inheritdoc />
public bool GetButtonsTouchDown(UxrHandSide handSide, UxrInputButtons buttons, bool getIgnoredInput = false)
{
if (buttons == UxrInputButtons.Any)
{
return (~GetButtonTouchFlagsLastFrame(handSide, getIgnoredInput) & GetButtonTouchFlags(handSide, getIgnoredInput)) != 0;
}
return (GetButtonTouchFlagsLastFrame(handSide, getIgnoredInput) & (uint)buttons) == 0 && (GetButtonTouchFlags(handSide, getIgnoredInput) & (uint)buttons) == (uint)buttons;
}
/// <inheritdoc />
public bool GetButtonsTouchDownAny(UxrHandSide handSide, UxrInputButtons buttons, bool getIgnoredInput = false)
{
uint touchFlagsLastFrame = GetButtonTouchFlagsLastFrame(handSide, getIgnoredInput);
uint touchFlags = GetButtonTouchFlags(handSide, getIgnoredInput);
foreach (UxrInputButtons button in buttons.GetFlags())
{
uint buttonFlag = (uint)button;
if ((touchFlagsLastFrame & buttonFlag) == 0 && (touchFlags & buttonFlag) == buttonFlag)
{
return true;
}
}
return false;
}
/// <inheritdoc />
public bool GetButtonsTouchUp(UxrHandSide handSide, UxrInputButtons buttons, bool getIgnoredInput = false)
{
if (buttons == UxrInputButtons.Any)
{
return (GetButtonTouchFlagsLastFrame(handSide, getIgnoredInput) & ~GetButtonTouchFlags(handSide, getIgnoredInput)) != 0;
}
return (GetButtonTouchFlagsLastFrame(handSide, getIgnoredInput) & (uint)buttons) == (uint)buttons && (GetButtonTouchFlags(handSide, getIgnoredInput) & (uint)buttons) == 0;
}
/// <inheritdoc />
public bool GetButtonsTouchUpAny(UxrHandSide handSide, UxrInputButtons buttons, bool getIgnoredInput = false)
{
uint touchFlagsLastFrame = GetButtonTouchFlagsLastFrame(handSide, getIgnoredInput);
uint touchFlags = GetButtonTouchFlags(handSide, getIgnoredInput);
foreach (UxrInputButtons button in buttons.GetFlags())
{
uint buttonFlag = (uint)button;
if ((touchFlagsLastFrame & buttonFlag) == buttonFlag && (touchFlags & buttonFlag) == 0)
{
return true;
}
}
return false;
}
/// <inheritdoc />
public bool GetButtonsPress(UxrHandSide handSide, UxrInputButtons buttons, bool getIgnoredInput = false)
{
if (buttons == UxrInputButtons.Any)
{
return GetButtonPressFlags(handSide, getIgnoredInput) != 0;
}
return GetButtonPressFlags(handSide, getIgnoredInput).HasFlags((uint)buttons);
}
/// <inheritdoc />
public bool GetButtonsPressAny(UxrHandSide handSide, UxrInputButtons buttons, bool getIgnoredInput = false)
{
return (GetButtonPressFlags(handSide, getIgnoredInput) & (uint)buttons) != 0;
}
/// <inheritdoc />
public bool GetButtonsPressDown(UxrHandSide handSide, UxrInputButtons buttons, bool getIgnoredInput = false)
{
if (buttons == UxrInputButtons.Any)
{
return (~GetButtonPressFlagsLastFrame(handSide, getIgnoredInput) & GetButtonPressFlags(handSide, getIgnoredInput)) != 0;
}
return (GetButtonPressFlagsLastFrame(handSide, getIgnoredInput) & (uint)buttons) == 0 && (GetButtonPressFlags(handSide, getIgnoredInput) & (uint)buttons) == (uint)buttons;
}
/// <inheritdoc />
public bool GetButtonsPressDownAny(UxrHandSide handSide, UxrInputButtons buttons, bool getIgnoredInput = false)
{
uint pressFlagsLastFrame = GetButtonPressFlagsLastFrame(handSide, getIgnoredInput);
uint pressFlags = GetButtonPressFlags(handSide, getIgnoredInput);
foreach (UxrInputButtons button in buttons.GetFlags())
{
uint buttonFlag = (uint)button;
if ((pressFlagsLastFrame & buttonFlag) == 0 && (pressFlags & buttonFlag) == buttonFlag)
{
return true;
}
}
return false;
}
/// <inheritdoc />
public bool GetButtonsPressUp(UxrHandSide handSide, UxrInputButtons buttons, bool getIgnoredInput = false)
{
if (buttons == UxrInputButtons.Any)
{
return (GetButtonPressFlagsLastFrame(handSide, getIgnoredInput) & ~GetButtonPressFlags(handSide, getIgnoredInput)) != 0;
}
return (GetButtonPressFlagsLastFrame(handSide, getIgnoredInput) & (uint)buttons) == (uint)buttons && (GetButtonPressFlags(handSide, getIgnoredInput) & (uint)buttons) == 0;
}
/// <inheritdoc />
public bool GetButtonsPressUpAny(UxrHandSide handSide, UxrInputButtons buttons, bool getIgnoredInput = false)
{
uint pressFlagsLastFrame = GetButtonPressFlagsLastFrame(handSide, getIgnoredInput);
uint pressFlags = GetButtonPressFlags(handSide, getIgnoredInput);
foreach (UxrInputButtons button in buttons.GetFlags())
{
uint buttonFlag = (uint)button;
if ((pressFlagsLastFrame & buttonFlag) == buttonFlag && (pressFlags & buttonFlag) == 0)
{
return true;
}
}
return false;
}
/// <inheritdoc />
public void SendHapticFeedback(UxrHandSide handSide,
UxrHapticClipType clipType,
float amplitude,
float durationSeconds = -1.0f,
UxrHapticMode hapticMode = UxrHapticMode.Mix)
{
StartCoroutine(SendHapticFeedbackCoroutine(handSide, clipType, amplitude, durationSeconds, hapticMode));
}
/// <inheritdoc />
public void SendGrabbableHapticFeedback(UxrGrabbableObject grabbableObject,
UxrHapticClipType clipType,
float amplitude = DefaultHapticAmplitude,
float durationSeconds = -1.0f,
UxrHapticMode hapticMode = UxrHapticMode.Mix)
{
if (UxrGrabManager.Instance.IsHandGrabbing(UxrAvatar.LocalAvatar, grabbableObject, UxrHandSide.Left, true))
{
SendHapticFeedback(UxrHandSide.Left, clipType, amplitude, durationSeconds, hapticMode);
}
if (UxrGrabManager.Instance.IsHandGrabbing(UxrAvatar.LocalAvatar, grabbableObject, UxrHandSide.Right, true))
{
SendHapticFeedback(UxrHandSide.Right, clipType, amplitude, durationSeconds, hapticMode);
}
}
/// <inheritdoc />
public void SendGrabbableHapticFeedback(UxrGrabbableObject grabbableObject, UxrHapticClip hapticClip)
{
if (UxrGrabManager.Instance.IsHandGrabbing(UxrAvatar.LocalAvatar, grabbableObject, UxrHandSide.Left, true))
{
SendHapticFeedback(UxrHandSide.Left, hapticClip);
}
if (UxrGrabManager.Instance.IsHandGrabbing(UxrAvatar.LocalAvatar, grabbableObject, UxrHandSide.Right, true))
{
SendHapticFeedback(UxrHandSide.Right, hapticClip);
}
}
/// <inheritdoc />
public UxrController3DModel GetController3DModel(UxrHandSide handSide)
{
return handSide == UxrHandSide.Left ? _leftController : _rightController;
}
/// <inheritdoc />
public IEnumerable<GameObject> GetControllerElementsGameObjects(UxrHandSide handSide, UxrControllerElements controllerElements)
{
UxrController3DModel controller3DModel = handSide == UxrHandSide.Left ? _leftController : _rightController;
return controller3DModel != null ? controller3DModel.GetElements(controllerElements) : Enumerable.Empty<GameObject>();
}
/// <inheritdoc />
public void StartControllerElementsBlinking(UxrHandSide handSide,
UxrControllerElements controllerElements,
Color emissionColor,
float blinksPerSec = 3.0f,
float durationSeconds = -1.0f)
{
BeginSync();
UxrController3DModel controller3DModel = GetController3DModel(handSide);
if (controller3DModel == null)
{
return;
}
controller3DModel.GetElements(controllerElements).ForEach(go => UxrObjectBlink.StartBlinking(go, emissionColor, blinksPerSec, durationSeconds));
EndSyncMethod(new object[] { handSide, controllerElements, emissionColor, blinksPerSec, durationSeconds });
}
/// <inheritdoc />
public void StopControllerElementsBlinking(UxrHandSide handSide, UxrControllerElements controllerElements)
{
BeginSync();
UxrController3DModel controller3DModel = GetController3DModel(handSide);
if (controller3DModel)
{
controller3DModel.GetElements(controllerElements).ForEach(UxrObjectBlink.StopBlinking);
}
EndSyncMethod(new object[] { handSide, controllerElements });
}
/// <inheritdoc />
public void StopAllBlinking(UxrHandSide handSide)
{
StopControllerElementsBlinking(handSide, UxrControllerElements.Everything);
}
/// <inheritdoc />
public bool IsAnyControllerElementBlinking(UxrHandSide handSide, UxrControllerElements controllerElements)
{
UxrController3DModel controller3DModel = GetController3DModel(handSide);
if (controller3DModel != null)
{
return controller3DModel.GetElements(controllerElements).Any(UxrObjectBlink.CheckBlinking);
}
return false;
}
/// <inheritdoc />
public bool AreAllControllerElementsBlinking(UxrHandSide handSide, UxrControllerElements controllerElements)
{
UxrController3DModel controller3DModel = GetController3DModel(handSide);
if (controller3DModel != null)
{
return controller3DModel.GetElements(controllerElements).All(UxrObjectBlink.CheckBlinking);
}
return false;
}
#endregion
#region Implicit IUxrDevice
/// <inheritdoc />
public virtual string SDKDependency => null;
/// <inheritdoc />
public event EventHandler<UxrDeviceConnectEventArgs> DeviceConnected;
#endregion
#region Explicit IUxrControllerInputUpdater
/// <summary>
/// This is the explicit implementation of <see cref="IUxrControllerInputUpdater.UpdateInput" />.
/// It is only accessible from the UXR framework because it's an explicit implementation,
/// so it can only be called when casting an object to this interface. Since this interface
/// is internal it can only be called from inside the UXR assembly.
/// API users will be able to implement their own input devices by inheriting from this
/// class and overriding <see cref="UpdateInput" />.
/// </summary>
void IUxrControllerInputUpdater.UpdateInput()
{
// Trigger Updating event
OnUpdating();
_buttonTouchFlagsLastFrameLeft = _buttonTouchFlagsLeft;
_buttonPressFlagsLastFrameLeft = _buttonPressFlagsLeft;
_buttonTouchFlagsLastFrameRight = _buttonTouchFlagsRight;
_buttonPressFlagsLastFrameRight = _buttonPressFlagsRight;
// Call the overridable UpdateInput()
UpdateInput();
// In devices where there is no handedness, UxrControllerInput.UpdateInput() should update the left button flags
// and this method will take care of copying them to the right so that both hands return the same input.
if (IsHandednessSupported == false)
{
_buttonTouchFlagsRight = _buttonTouchFlagsLeft;
_buttonPressFlagsRight = _buttonPressFlagsLeft;
}
// Update controllers graphics and hands if necessary
if (_leftController != null && _leftController.isActiveAndEnabled)
{
_leftController.UpdateFromInput(this);
}
if (_rightController != null && _rightController.isActiveAndEnabled)
{
_rightController.UpdateFromInput(this);
}
// Trigger input events (buttons, controllers1D/2D...)
RaiseInputEvents();
// Trigger Updated event
OnUpdated();
}
#endregion
#region Public Methods
/// <summary>
/// Gets whether the given controller input should be ignored.
/// </summary>
/// <param name="handSide">
/// Which controller to check. In <see cref="UxrControllerSetupType.Single" /> devices where
/// <see cref="IsHandednessSupported" /> is false, such as in gamepads, both hands will address the single device.
/// </param>
/// <returns>True if the given input should be ignored</returns>
public static bool GetIgnoreControllerInput(UxrHandSide handSide)
{
return s_ignoreControllerInput[handSide];
}
/// <summary>
/// Sets whether the given controller input should be ignored.
/// </summary>
/// <param name="handSide">
/// Which controller to change. In <see cref="UxrControllerSetupType.Single" /> devices where
/// <see cref="IsHandednessSupported" /> is false, such as in gamepads, both hands will address the single device.
/// </param>
/// <param name="ignore">Boolean telling whether to ignore the given controller input</param>
public static void SetIgnoreControllerInput(UxrHandSide handSide, bool ignore)
{
s_ignoreControllerInput[handSide] = ignore;
}
/// <summary>
/// Gets the controller button (<see cref="UxrInputButtons" />) enum value given a controller element (
/// <see cref="UxrControllerElements" />) enum value.
/// </summary>
/// <remarks>This method doesn't support using flag composition for <see cref="element" />, use only a single value</remarks>
/// <param name="element">Controller element to get the button enum value for</param>
/// <returns>
/// Button enum value representing the controller element, or <see cref="UxrInputButtons.None" /> if it doesn't exist
/// </returns>
public static UxrInputButtons ControllerElementToButton(UxrControllerElements element)
{
switch (element)
{
case UxrControllerElements.Joystick: return UxrInputButtons.Joystick;
case UxrControllerElements.Joystick2: return UxrInputButtons.Joystick2;
case UxrControllerElements.Trigger: return UxrInputButtons.Trigger;
case UxrControllerElements.Trigger2: return UxrInputButtons.Trigger2;
case UxrControllerElements.Grip: return UxrInputButtons.Grip;
case UxrControllerElements.ThumbCapSense: return UxrInputButtons.ThumbCapSense;
case UxrControllerElements.IndexCapSense: return UxrInputButtons.IndexCapSense;
case UxrControllerElements.MiddleCapSense: return UxrInputButtons.MiddleCapSense;
case UxrControllerElements.RingCapSense: return UxrInputButtons.RingCapSense;
case UxrControllerElements.LittleCapSense: return UxrInputButtons.LittleCapSense;
case UxrControllerElements.Button1: return UxrInputButtons.Button1;
case UxrControllerElements.Button2: return UxrInputButtons.Button2;
case UxrControllerElements.Button3: return UxrInputButtons.Button3;
case UxrControllerElements.Button4: return UxrInputButtons.Button4;
case UxrControllerElements.Bumper: return UxrInputButtons.Bumper;
case UxrControllerElements.Bumper2: return UxrInputButtons.Bumper2;
case UxrControllerElements.Back: return UxrInputButtons.Back;
case UxrControllerElements.Menu: return UxrInputButtons.Menu;
}
return UxrInputButtons.None;
}
/// <summary>
/// Gets the controller element (<see cref="UxrControllerElements" />) enum value given a controller button (
/// <see cref="UxrInputButtons" />) enum value.
/// </summary>
/// <remarks>This method doesn't support using flag composition for <see cref="button" />, use only a single value</remarks>
/// <param name="button">Controller button to get the element enum value for</param>
/// <returns>
/// Controller element enum value representing the controller button, or <see cref="UxrControllerElements.None" /> if
/// it doesn't exist
/// </returns>
public static UxrControllerElements ButtonToControllerElement(UxrInputButtons button)
{
switch (button)
{
case UxrInputButtons.Joystick: return UxrControllerElements.Joystick;
case UxrInputButtons.JoystickLeft: return UxrControllerElements.Joystick;
case UxrInputButtons.JoystickRight: return UxrControllerElements.Joystick;
case UxrInputButtons.JoystickUp: return UxrControllerElements.Joystick;
case UxrInputButtons.JoystickDown: return UxrControllerElements.Joystick;
case UxrInputButtons.Joystick2: return UxrControllerElements.Joystick2;
case UxrInputButtons.Joystick2Left: return UxrControllerElements.Joystick2;
case UxrInputButtons.Joystick2Right: return UxrControllerElements.Joystick2;
case UxrInputButtons.Joystick2Up: return UxrControllerElements.Joystick2;
case UxrInputButtons.Joystick2Down: return UxrControllerElements.Joystick2;
case UxrInputButtons.DPadLeft: return UxrControllerElements.DPad;
case UxrInputButtons.DPadRight: return UxrControllerElements.DPad;
case UxrInputButtons.DPadUp: return UxrControllerElements.DPad;
case UxrInputButtons.DPadDown: return UxrControllerElements.DPad;
case UxrInputButtons.Trigger: return UxrControllerElements.Trigger;
case UxrInputButtons.Trigger2: return UxrControllerElements.Trigger2;
case UxrInputButtons.Grip: return UxrControllerElements.Grip;
case UxrInputButtons.ThumbCapSense: return UxrControllerElements.ThumbCapSense;
case UxrInputButtons.IndexCapSense: return UxrControllerElements.IndexCapSense;
case UxrInputButtons.MiddleCapSense: return UxrControllerElements.MiddleCapSense;
case UxrInputButtons.RingCapSense: return UxrControllerElements.RingCapSense;
case UxrInputButtons.LittleCapSense: return UxrControllerElements.LittleCapSense;
case UxrInputButtons.Button1: return UxrControllerElements.Button1;
case UxrInputButtons.Button2: return UxrControllerElements.Button2;
case UxrInputButtons.Button3: return UxrControllerElements.Button3;
case UxrInputButtons.Button4: return UxrControllerElements.Button4;
case UxrInputButtons.Bumper: return UxrControllerElements.Bumper;
case UxrInputButtons.Bumper2: return UxrControllerElements.Bumper2;
case UxrInputButtons.Back: return UxrControllerElements.Back;
case UxrInputButtons.Menu: return UxrControllerElements.Menu;
}
return UxrControllerElements.None;
}
/// <summary>
/// Gets the <see cref="UxrInput1D" /> enum value given a controller element (<see cref="UxrControllerElements" />
/// ) enum value.
/// </summary>
/// <remarks>This method doesn't support using flag composition for <see cref="element" />, use only a single value</remarks>
/// <param name="element">Controller element to get the <see cref="UxrInput1D" /> enum value for</param>
/// <returns>
/// <see cref="UxrInput1D" /> enum value representing the controller element, or <see cref="UxrInput1D.None" /> if it
/// doesn't exist
/// </returns>
public static UxrInput1D ControllerElementToInput1D(UxrControllerElements element)
{
switch (element)
{
case UxrControllerElements.Grip: return UxrInput1D.Grip;
case UxrControllerElements.Trigger: return UxrInput1D.Trigger;
case UxrControllerElements.Trigger2: return UxrInput1D.Trigger2;
}
return UxrInput1D.None;
}
/// <summary>
/// Gets the controller elements <see cref="UxrControllerElements" /> enum value given a <see cref="UxrInput1D" /> enum
/// value.
/// </summary>
/// <param name="input1D">1D input element to get the <see cref="UxrControllerElements" /> enum value for</param>
/// <returns>
/// <see cref="UxrControllerElements" /> enum value representing the input1D, or
/// <see cref="UxrControllerElements.None" /> if it doesn't exist
/// </returns>
public static UxrControllerElements Input1DToControllerElement(UxrInput1D input1D)
{
switch (input1D)
{
case UxrInput1D.Grip: return UxrControllerElements.Grip;
case UxrInput1D.Trigger: return UxrControllerElements.Trigger;
case UxrInput1D.Trigger2: return UxrControllerElements.Trigger2;
}
return UxrControllerElements.None;
}
/// <summary>
/// Gets the <see cref="UxrInput2D" /> enum value given a controller element (<see cref="UxrControllerElements" />
/// ) enum value.
/// </summary>
/// <remarks>This method doesn't support using flag composition for <see cref="element" />, use only a single value</remarks>
/// <param name="element">Controller element to get the <see cref="UxrInput2D" /> enum value for</param>
/// <returns>
/// <see cref="UxrInput2D" /> enum value representing the controller element, or <see cref="UxrInput2D.None" /> if it
/// doesn't exist
/// </returns>
public static UxrInput2D ControllerElementToInput2D(UxrControllerElements element)
{
switch (element)
{
case UxrControllerElements.Joystick: return UxrInput2D.Joystick;
case UxrControllerElements.Joystick2: return UxrInput2D.Joystick2;
}
return UxrInput2D.None;
}
/// <summary>
/// Gets the controller elements <see cref="UxrControllerElements" /> enum value given a <see cref="UxrInput2D" /> enum
/// value.
/// </summary>
/// <param name="input2D">2D input element to get the <see cref="UxrControllerElements" /> enum value for</param>
/// <returns>
/// <see cref="UxrControllerElements" /> enum value representing the input2D, or
/// <see cref="UxrControllerElements.None" /> if it doesn't exist
/// </returns>
public static UxrControllerElements Input2DToControllerElement(UxrInput2D input2D)
{
switch (input2D)
{
case UxrInput2D.Joystick: return UxrControllerElements.Joystick;
case UxrInput2D.Joystick2: return UxrControllerElements.Joystick2;
}
return UxrControllerElements.None;
}
#endregion
#region Internal Methods
/// <summary>
/// Enables or disables the list of objects that should be enabled whenever the left controller is available in a dual
/// controller setup, and the avatar is being rendered.
/// </summary>
internal void EnableObjectListLeft(bool enable)
{
_enableObjectListLeft?.ForEach(go => go.SetActive(enable));
}
/// <summary>
/// Enables or disables the list of objects that should be enabled whenever the right controller is available in a dual
/// controller setup, and the avatar is being rendered.
/// </summary>
internal void EnableObjectListRight(bool enable)
{
_enableObjectListRight?.ForEach(go => go.SetActive(enable));
}
/// <summary>
/// Enables or disables the list of objects that should be enabled whenever the single controller is available in a
/// single controller setup, and the avatar is being rendered.
/// </summary>
internal void EnableObjectListSingle(bool enable)
{
_enableObjectList?.ForEach(go => go.SetActive(enable));
}
#endregion
#region Unity
/// <summary>
/// Initializes internal data
/// </summary>
protected override void Awake()
{
base.Awake();
if (UxrGlobalSettings.Instance.LogLevelDevices >= UxrLogLevel.Errors)
{
if (SetupType == UxrControllerSetupType.Single)
{
if (_controller != null && _controller.IsInPrefab() && UxrGlobalSettings.Instance.LogLevelDevices >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.DevicesModule}: The controller in the {GetType().Name} component needs to be instantiated in the avatar. It cannot be an asset.");
}
}
else if (SetupType == UxrControllerSetupType.Dual)
{
if (_leftController != null && _leftController.IsInPrefab() && UxrGlobalSettings.Instance.LogLevelDevices >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.DevicesModule}: The left controller in the {GetType().Name} component needs to be instantiated in the avatar. It cannot be an asset.");
}
if (_rightController != null && _rightController.IsInPrefab() && UxrGlobalSettings.Instance.LogLevelDevices >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.DevicesModule}: The right controller in the {GetType().Name} component needs to be instantiated in the avatar. It cannot be an asset.");
}
}
}
// Reset event dictionaries. We use these for sending only a single zero value event when a input1D/2D is not being pressed.
foreach (UxrInput1D input1D in Enum.GetValues(typeof(UxrInput1D)))
{
if (HasControllerElements(UxrHandSide.Left, Input1DToControllerElement(input1D)) && _controllers1DResetLeft.ContainsKey(input1D) == false)
{
_controllers1DResetLeft.Add(input1D, true);
}
if (HasControllerElements(UxrHandSide.Right, Input1DToControllerElement(input1D)) && _controllers1DResetRight.ContainsKey(input1D) == false)
{
_controllers1DResetRight.Add(input1D, true);
}
}
foreach (UxrInput2D input2D in Enum.GetValues(typeof(UxrInput2D)))
{
if (HasControllerElements(UxrHandSide.Left, Input2DToControllerElement(input2D)) && _controllers2DResetLeft.ContainsKey(input2D) == false)
{
_controllers2DResetLeft.Add(input2D, true);
}
if (HasControllerElements(UxrHandSide.Right, Input2DToControllerElement(input2D)) && _controllers2DResetRight.ContainsKey(input2D) == false)
{
_controllers2DResetRight.Add(input2D, true);
}
}
}
/// <summary>
/// Sets events to null in order to help remove unused references
/// </summary>
protected override void OnDestroy()
{
base.OnDestroy();
DeviceConnected = null;
Updating = null;
Updated = null;
ButtonStateChanged = null;
Input1DChanged = null;
Input2DChanged = null;
HapticRequesting = null;
}
/// <summary>
/// Unity Start event
/// </summary>
protected override void Start()
{
base.Start();
if (RaiseConnectOnStart)
{
OnDeviceConnected(new UxrDeviceConnectEventArgs(true));
}
}
#endregion
#region Coroutines
/// <summary>
/// Coroutine that sends a pre-defined haptic feedback clip emulating it using a composition of smaller steps
/// with varying frequency and amplitude.
/// </summary>
/// <param name="handSide">Target hand</param>
/// <param name="clipType">Pre-defined clip to play on the controller to make it vibrate</param>
/// <param name="amplitude">Amplitude [0.0, 1.0]</param>
/// <param name="durationSeconds">Duration in seconds</param>
/// <param name="hapticMode">Mix or replace</param>
/// <returns>Coroutine IEnumerator</returns>
private IEnumerator SendHapticFeedbackCoroutine(UxrHandSide handSide,
UxrHapticClipType clipType,
float amplitude,
float durationSeconds,
UxrHapticMode hapticMode = UxrHapticMode.Mix)
{
int steps = 10;
switch (clipType)
{
case UxrHapticClipType.RumbleFreqVeryLow:
SendHapticFeedback(handSide, 10.0f, amplitude, durationSeconds <= 0.0f ? 0.5f : durationSeconds, hapticMode);
break;
case UxrHapticClipType.RumbleFreqLow:
SendHapticFeedback(handSide, 25.0f, amplitude, durationSeconds <= 0.0f ? 0.5f : durationSeconds, hapticMode);
break;
case UxrHapticClipType.RumbleFreqNormal:
SendHapticFeedback(handSide, 64.0f, amplitude, durationSeconds <= 0.0f ? 0.5f : durationSeconds, hapticMode);
break;
case UxrHapticClipType.RumbleFreqHigh:
SendHapticFeedback(handSide, 160.0f, amplitude, durationSeconds <= 0.0f ? 0.5f : durationSeconds, hapticMode);
break;
case UxrHapticClipType.RumbleFreqVeryHigh:
SendHapticFeedback(handSide, 320.0f, amplitude, durationSeconds <= 0.0f ? 0.5f : durationSeconds, hapticMode);
break;
case UxrHapticClipType.Click:
SendHapticFeedback(handSide, 0.0f, amplitude, durationSeconds <= 0.0f ? 0.05f : durationSeconds, hapticMode);
break;
case UxrHapticClipType.Shot:
durationSeconds = durationSeconds <= 0.0f ? 0.12f : durationSeconds;
SendHapticFeedback(handSide, 0.0f, amplitude, durationSeconds * 0.5f, hapticMode);
for (int i = 0; i < steps; ++i)
{
float t = (float)i / (steps - 1);
SendHapticFeedback(handSide, Mathf.Lerp(180.0f, 64.0f, t), amplitude, durationSeconds * 0.5f / steps, hapticMode);
yield return new WaitForSeconds(durationSeconds * 0.5f / steps);
}
break;
case UxrHapticClipType.ShotBig:
durationSeconds = durationSeconds <= 0.0f ? 0.25f : durationSeconds;
for (int i = 0; i < steps; ++i)
{
float t = (float)i / (steps - 1);
SendHapticFeedback(handSide,
Mathf.Lerp(320.0f, 32.0f, t),
amplitude * Mathf.Clamp01(amplitude * (2.0f - t * 2.0f)),
durationSeconds / steps,
hapticMode);
yield return new WaitForSeconds(durationSeconds / steps);
}
break;
case UxrHapticClipType.ShotBigger:
durationSeconds = durationSeconds <= 0.0f ? 0.4f : durationSeconds;
for (int i = 0; i < steps; ++i)
{
float t = (float)i / (steps - 1);
SendHapticFeedback(handSide,
Mathf.Lerp(200.0f, 32.0f, t),
amplitude * Mathf.Clamp01(amplitude * (2.0f - t * 2.0f)),
durationSeconds / steps,
hapticMode);
yield return new WaitForSeconds(durationSeconds / steps);
}
break;
case UxrHapticClipType.Slide:
durationSeconds = durationSeconds <= 0.0f ? 0.5f : durationSeconds;
for (int i = 0; i < steps; ++i)
{
float t = (float)i / (steps - 1);
SendHapticFeedback(handSide, 160.0f, amplitude * (1.0f - t), durationSeconds / steps, hapticMode);
yield return new WaitForSeconds(durationSeconds / steps);
}
break;
case UxrHapticClipType.Explosion:
durationSeconds = durationSeconds <= 0.0f ? 0.5f : durationSeconds;
for (int i = 0; i < steps; ++i)
{
float t = (float)i / (steps - 1);
SendHapticFeedback(handSide, Mathf.Lerp(180.0f, 32.0f, t), amplitude, durationSeconds / steps, hapticMode);
yield return new WaitForSeconds(durationSeconds / steps);
}
break;
}
yield return null;
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Event trigger for the <see cref="DeviceConnected" /> event
/// </summary>
/// <param name="e">Event args</param>
protected virtual void OnDeviceConnected(UxrDeviceConnectEventArgs e)
{
DeviceConnected?.Invoke(this, e);
GlobalControllerConnected?.Invoke(this, e);
}
/// <summary>
/// Event trigger for the <see cref="Updating" /> event
/// </summary>
protected virtual void OnUpdating()
{
Updating?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Event trigger for the <see cref="Updated" /> event
/// </summary>
protected virtual void OnUpdated()
{
Updated?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Event trigger for the <see cref="ButtonStateChanged" /> event
/// </summary>
/// <param name="e">Event args</param>
protected virtual void OnButtonStateChanged(UxrInputButtonEventArgs e)
{
ButtonStateChanged?.Invoke(this, e);
GlobalButtonStateChanged?.Invoke(this, e);
}
/// <summary>
/// Event trigger for the <see cref="Input1DChanged" /> event
/// </summary>
/// <param name="e">Event args</param>
protected virtual void OnInput1DChanged(UxrInput1DEventArgs e)
{
Input1DChanged?.Invoke(this, e);
GlobalInput1DChanged?.Invoke(this, e);
}
/// <summary>
/// Event trigger for the <see cref="Input2DChanged" /> event
/// </summary>
/// <param name="e">Event args</param>
protected virtual void OnInput2DChanged(UxrInput2DEventArgs e)
{
Input2DChanged?.Invoke(this, e);
GlobalInput2DChanged?.Invoke(this, e);
}
/// <summary>
/// Event trigger for the <see cref="HapticRequesting" /> event
/// </summary>
/// <param name="e">Event args</param>
protected virtual void OnHapticRequesting(UxrControllerHapticEventArgs e)
{
HapticRequesting?.Invoke(this, e);
GlobalHapticRequesting?.Invoke(this, e);
}
/// <summary>
/// Raises the necessary input events if there are changes in the current frame input state.
/// </summary>
private void RaiseInputEvents()
{
// We will generate events based on the input gathered. We use event trigger methods
// for this internally, but we check first if there is any subscription to the event
// in order to avoid iterating through all the elements unnecessarily
if (ButtonStateChanged != null || GlobalButtonStateChanged != null)
{
// Button events
foreach (UxrInputButtons button in Enum.GetValues(typeof(UxrInputButtons)))
{
UxrControllerElements controllerElement = ButtonToControllerElement(button);
foreach (UxrHandSide handSide in Enum.GetValues(typeof(UxrHandSide)))
{
if (controllerElement != UxrControllerElements.None && HasControllerElements(handSide, controllerElement))
{
foreach (UxrButtonEventType eventType in Enum.GetValues(typeof(UxrButtonEventType)))
{
if (GetButtonsEvent(handSide, button, eventType))
{
OnButtonStateChanged(new UxrInputButtonEventArgs(handSide, button, eventType));
}
}
}
}
}
}
if (Input1DChanged != null || GlobalInput1DChanged != null)
{
// UxrInput1D events
foreach (UxrInput1D input1D in Enum.GetValues(typeof(UxrInput1D)))
{
UxrControllerElements controllerElement = Input1DToControllerElement(input1D);
if (controllerElement != UxrControllerElements.None)
{
if (HasControllerElements(UxrHandSide.Left, controllerElement))
{
float input1DValue = GetInput1D(UxrHandSide.Left, input1D);
if (input1DValue == 0.0f)
{
if (_controllers1DResetLeft[input1D] == false)
{
_controllers1DResetLeft[input1D] = true;
}
}
else
{
_controllers1DResetLeft[input1D] = false;
}
OnInput1DChanged(new UxrInput1DEventArgs(UxrHandSide.Left, input1D, input1DValue));
}
if (HasControllerElements(UxrHandSide.Right, controllerElement))
{
float input1DValue = GetInput1D(UxrHandSide.Right, input1D);
if (input1DValue == 0.0f)
{
if (_controllers1DResetRight[input1D] == false)
{
_controllers1DResetRight[input1D] = true;
}
}
else
{
_controllers1DResetRight[input1D] = false;
}
OnInput1DChanged(new UxrInput1DEventArgs(UxrHandSide.Right, input1D, input1DValue));
}
}
}
}
if (Input2DChanged != null || GlobalInput2DChanged != null)
{
// UxrInput2D events
foreach (UxrInput2D input2D in Enum.GetValues(typeof(UxrInput2D)))
{
UxrControllerElements controllerElement = Input2DToControllerElement(input2D);
if (controllerElement != UxrControllerElements.None)
{
if (HasControllerElements(UxrHandSide.Left, controllerElement))
{
Vector2 input2DValue = GetInput2D(UxrHandSide.Left, input2D);
if (input2DValue == Vector2.zero)
{
if (_controllers2DResetLeft[input2D] == false)
{
_controllers2DResetLeft[input2D] = true;
}
}
else
{
_controllers2DResetLeft[input2D] = false;
}
OnInput2DChanged(new UxrInput2DEventArgs(UxrHandSide.Left, input2D, input2DValue));
}
if (HasControllerElements(UxrHandSide.Right, controllerElement))
{
Vector2 input2DValue = GetInput2D(UxrHandSide.Right, input2D);
if (input2DValue == Vector2.zero)
{
if (_controllers2DResetRight[input2D] == false)
{
_controllers2DResetRight[input2D] = true;
}
}
else
{
_controllers2DResetRight[input2D] = false;
}
OnInput2DChanged(new UxrInput2DEventArgs(UxrHandSide.Right, input2D, input2DValue));
}
}
}
}
}
#endregion
#region Protected Methods
/// <summary>
/// Filters a two-axis input using a dead-zone. Values inside the dead-zone will remain (0.0, 0.0).
/// </summary>
/// <param name="input2DPos">2-axis value</param>
/// <param name="deadZone">Dead-zone threshold [0.0, 1.0]</param>
/// <returns>Filtered input</returns>
protected static Vector2 FilterTwoAxesDeadZone(Vector2 input2DPos, float deadZone)
{
Vector2 filtered2D = input2DPos;
if (Mathf.Abs(filtered2D.x) < deadZone)
{
filtered2D.x = 0.0f;
}
if (Mathf.Abs(filtered2D.y) < deadZone)
{
filtered2D.y = 0.0f;
}
return filtered2D;
}
/// <summary>
/// Transforms a two-axis input to an angle. 0 degrees is right and degrees increase counterclockwise.
/// </summary>
/// <param name="input2D">2-axis input</param>
/// <returns>Angle in degrees</returns>
protected static float Input2DToAngle(Vector2 input2D)
{
float controllerAngle = Mathf.Atan2(input2D.y, input2D.x) * Mathf.Rad2Deg;
if (controllerAngle < 0.0f)
{
controllerAngle += 360.0f;
}
return controllerAngle;
}
/// <summary>
/// Checks if the given 2-axis input corresponds to a left press in a digital pad.
/// </summary>
/// <param name="input2D">2-axis input</param>
/// <returns>True if the input corresponds to a left press</returns>
protected static bool IsInput2dDPadLeft(Vector2 input2D)
{
float touchPadAngle = Input2DToAngle(input2D);
return IsInput2dDPadLeft(touchPadAngle);
}
/// <summary>
/// Checks if the given 2-axis input represented as an angle in degrees corresponds to a left press in a digital pad.
/// 0 degrees is right and degrees increase counterclockwise.
/// </summary>
/// <param name="touchPadAngle">2-axis input in degrees</param>
/// <returns>True if the input corresponds to a left press</returns>
protected static bool IsInput2dDPadLeft(float touchPadAngle)
{
return touchPadAngle > 135.0f && touchPadAngle <= 225.0f;
}
/// <summary>
/// Checks if the given 2-axis input corresponds to a right press in a digital pad.
/// </summary>
/// <param name="input2D">2-axis input</param>
/// <returns>True if the input corresponds to a right press</returns>
protected static bool IsInput2dDPadRight(Vector2 input2D)
{
float touchPadAngle = Input2DToAngle(input2D);
return IsInput2dDPadRight(touchPadAngle);
}
/// <summary>
/// Checks if the given 2-axis input represented as an angle in degrees corresponds to a right press in a digital pad.
/// 0 degrees is right and degrees increase counterclockwise.
/// </summary>
/// <param name="touchPadAngle">2-axis input in degrees</param>
/// <returns>True if the input corresponds to a right press</returns>
protected static bool IsInput2dDPadRight(float touchPadAngle)
{
return (touchPadAngle > 315.0f && touchPadAngle <= 360.0f) || (touchPadAngle >= 0.0f && touchPadAngle <= 45.0f);
}
/// <summary>
/// Checks if the given 2-axis input corresponds to an up press in a digital pad.
/// </summary>
/// <param name="input2D">2-axis input</param>
/// <returns>True if the input corresponds to an up press</returns>
protected static bool IsInput2dDPadUp(Vector2 input2D)
{
float touchPadAngle = Input2DToAngle(input2D);
return IsInput2dDPadUp(touchPadAngle);
}
/// <summary>
/// Checks if the given 2-axis input represented as an angle in degrees corresponds to an up press in a digital pad.
/// 0 degrees is right and degrees increase counterclockwise.
/// </summary>
/// <param name="touchPadAngle">2-axis input in degrees</param>
/// <returns>True if the input corresponds to an up press</returns>
protected static bool IsInput2dDPadUp(float touchPadAngle)
{
return touchPadAngle > 45.0f && touchPadAngle <= 135.0f;
}
/// <summary>
/// Checks if the given 2-axis input corresponds to a down press in a digital pad.
/// </summary>
/// <param name="input2D">2-axis input</param>
/// <returns>True if the input corresponds to a down press</returns>
protected static bool IsInput2dDPadDown(Vector2 input2D)
{
float touchPadAngle = Input2DToAngle(input2D);
return IsInput2dDPadUp(touchPadAngle);
}
/// <summary>
/// Checks if the given 2-axis input represented as an angle in degrees corresponds to a down press in a digital pad.
/// 0 degrees is right and degrees increase counterclockwise.
/// </summary>
/// <param name="touchPadAngle">2-axis input in degrees</param>
/// <returns>True if the input corresponds to a down press</returns>
protected static bool IsInput2dDPadDown(float touchPadAngle)
{
return touchPadAngle > 225.0f && touchPadAngle <= 315.0f;
}
/// <summary>
/// Virtual method that should be overriden in child classes in order to
/// update the current input state information (buttons and all the other elements in the controllers).
/// </summary>
protected virtual void UpdateInput()
{
}
/// <summary>
/// Checks whether the given hand input should be ignored.
/// </summary>
/// <param name="handSide">Which hand</param>
/// <param name="getIgnoredInput">If a hand input should be ignored, whether to get it anyway</param>
/// <returns>Whether the given hand input should be ignored</returns>
protected bool ShouldIgnoreInput(UxrHandSide handSide, bool getIgnoredInput)
{
return GetIgnoreControllerInput(handSide) && !getIgnoredInput;
}
/// <summary>
/// Gets flags representing the current button state
/// </summary>
/// <param name="buttonFlags">Which button flags to retrieve</param>
/// <returns>Button flags</returns>
protected uint GetButtonFlags(ButtonFlags buttonFlags)
{
switch (buttonFlags)
{
case ButtonFlags.TouchFlagsLeft: return _buttonTouchFlagsLeft;
case ButtonFlags.PressFlagsLeft: return _buttonPressFlagsLeft;
case ButtonFlags.TouchFlagsRight: return _buttonTouchFlagsRight;
case ButtonFlags.PressFlagsRight: return _buttonPressFlagsRight;
}
return 0;
}
/// <summary>
/// Sets the given button flags
/// </summary>
/// <param name="buttonFlags">Which button flags to set</param>
/// <param name="buttons">Which button(s) to set</param>
/// <param name="set">True or false representing the flag state</param>
protected void SetButtonFlags(ButtonFlags buttonFlags, UxrInputButtons buttons, bool set)
{
if (set)
{
switch (buttonFlags)
{
case ButtonFlags.TouchFlagsLeft:
SetButtonFlags(ref _buttonTouchFlagsLeft, buttons);
break;
case ButtonFlags.PressFlagsLeft:
SetButtonFlags(ref _buttonPressFlagsLeft, buttons);
break;
case ButtonFlags.TouchFlagsRight:
SetButtonFlags(ref _buttonTouchFlagsRight, buttons);
break;
case ButtonFlags.PressFlagsRight:
SetButtonFlags(ref _buttonPressFlagsRight, buttons);
break;
}
}
else
{
switch (buttonFlags)
{
case ButtonFlags.TouchFlagsLeft:
ClearButtonFlags(ref _buttonTouchFlagsLeft, buttons);
break;
case ButtonFlags.PressFlagsLeft:
ClearButtonFlags(ref _buttonPressFlagsLeft, buttons);
break;
case ButtonFlags.TouchFlagsRight:
ClearButtonFlags(ref _buttonTouchFlagsRight, buttons);
break;
case ButtonFlags.PressFlagsRight:
ClearButtonFlags(ref _buttonPressFlagsRight, buttons);
break;
}
}
}
#endregion
#region Private Methods
/// <summary>
/// Sets the given button flags
/// </summary>
/// <param name="flags">Which button flags to set</param>
/// <param name="buttons">Which button(s) to set</param>
private void SetButtonFlags(ref uint flags, UxrInputButtons buttons)
{
flags = flags.WithFlags((uint)buttons);
}
/// <summary>
/// Clears (sets to 0) the given button flags
/// </summary>
/// <param name="flags">Which button flags to clear</param>
/// <param name="buttons">Which button(s) to clear</param>
private void ClearButtonFlags(ref uint flags, UxrInputButtons buttons)
{
flags = flags.WithoutFlags((uint)buttons);
}
#endregion
#region Protected Types & Data
/// <summary>
/// Used by child classes to notify that the Connect event should be forcefully raised during Start().
/// This is required to propagate Connect events properly when a new scene is loaded and the devices are already
/// connected and thus not sending any events. We still need to get Connect notifications, so child classes
/// need to detect during Awake() if the device(s) are already connected and if so, notify that the
/// Connect event needs to be raised.
/// </summary>
protected bool RaiseConnectOnStart { get; set; }
/// <summary>
/// Minimum axis value required to consider an analog input as a DPad digital press in any direction.
/// </summary>
protected const float AnalogAsDPadThreshold = 0.2f;
/// <summary>
/// Default haptic amplitude if not specified
/// </summary>
protected const float DefaultHapticAmplitude = 0.6f;
#endregion
#region Private Types & Data
private static readonly Dictionary<UxrHandSide, bool> s_ignoreControllerInput = new Dictionary<UxrHandSide, bool>
{
{ UxrHandSide.Left, false },
{ UxrHandSide.Right, false }
};
private readonly Dictionary<UxrInput1D, bool> _controllers1DResetLeft = new Dictionary<UxrInput1D, bool>();
private readonly Dictionary<UxrInput1D, bool> _controllers1DResetRight = new Dictionary<UxrInput1D, bool>();
private readonly Dictionary<UxrInput2D, bool> _controllers2DResetLeft = new Dictionary<UxrInput2D, bool>();
private readonly Dictionary<UxrInput2D, bool> _controllers2DResetRight = new Dictionary<UxrInput2D, bool>();
private uint _buttonTouchFlagsLastFrameLeft;
private uint _buttonPressFlagsLastFrameLeft;
private uint _buttonTouchFlagsLastFrameRight;
private uint _buttonPressFlagsLastFrameRight;
private uint _buttonTouchFlagsLeft;
private uint _buttonPressFlagsLeft;
private uint _buttonTouchFlagsRight;
private uint _buttonPressFlagsRight;
#endregion
}
}