Add ultimate xr

This commit is contained in:
2024-08-06 21:58:35 +02:00
parent 864033bf10
commit 7165bacd9d
3952 changed files with 2162037 additions and 35 deletions

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: da3d1ea9989c4cab9f15910108b907a2
timeCreated: 1648244279

View File

@@ -0,0 +1,780 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrControlInput.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using UltimateXR.Extensions.System.Threading;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace UltimateXR.UI.UnityInputModule.Controls
{
public delegate void DragStartedEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void DraggedEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void DragEndedEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void DroppedEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void PressedEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void ReleasedEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void ClickedEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void PressHeldEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void CursorEnteredEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void CursorExitedEventHandler(UxrControlInput controlInput, PointerEventData eventData);
public delegate void UpdateSelectedEventHandler(UxrControlInput controlInput, BaseEventData eventData);
public delegate void InputSubmittedEventHandler(UxrControlInput controlInput, BaseEventData eventData);
/// <summary>
/// A component derived from <see cref="EventTrigger" /> that simplifies the handling of events triggered by UI
/// controls. Among the key benefits are:
/// <list type="bullet">
/// <item>
/// Be able to write UI code by subscribing to events generated by UI controls. Global static events are
/// also provided to handle events coming from any control.
/// </item>
/// <item>
/// New controls with more complex behaviour can inherit from this class and add their own logic. Event
/// triggers are provided so that handling events can be done by overriding the appropriate methods, making
/// sure the base class is always called at the beginning. An example is <see cref="UxrToggleControlInput" />.
/// </item>
/// <item>
/// Each <see cref="UxrControlInput" /> can specify the audio/haptic feedback for the click/down/up events.
/// </item>
/// </list>
/// </summary>
public class UxrControlInput : EventTrigger
{
#region Inspector Properties/Serialized Fields
[SerializeField] private float _pressAndHoldDuration = 1.0f;
[SerializeField] private UxrControlFeedback _feedbackOnPress;
[SerializeField] private UxrControlFeedback _feedbackOnRelease;
[SerializeField] private UxrControlFeedback _feedbackOnClick;
#endregion
#region Public Types & Data
/// <summary>
/// Event called whenever any <see cref="UxrControlInput" /> is pressed.
/// </summary>
public static event PressedEventHandler GlobalPressed;
/// <summary>
/// Event called whenever any <see cref="UxrControlInput" /> press is released.
/// </summary>
public static event ReleasedEventHandler GlobalReleased;
/// <summary>
/// Event called whenever any <see cref="UxrControlInput" /> is clicked. A click depending on the operating mode can be
/// a press or a release after a press.
/// </summary>
public static event ClickedEventHandler GlobalClicked;
/// <summary>
/// Event called whenever any <see cref="UxrControlInput" /> started being dragged.
/// </summary>
public static event DragStartedEventHandler GlobalDragStarted;
/// <summary>
/// Event called during the frames any <see cref="UxrControlInput" /> is being dragged.
/// </summary>
public static event DraggedEventHandler GlobalDragged;
/// <summary>
/// Event called whenever any <see cref="UxrControlInput" /> stopped being dragged.
/// </summary>
public static event DragEndedEventHandler GlobalDragEnded;
/// <summary>
/// Event called whenever the control started being dragged.
/// </summary>
public event DragStartedEventHandler DragStarted;
/// <summary>
/// Event called each frame the control is being dragged.
/// </summary>
public event DraggedEventHandler Dragged;
/// <summary>
/// Event called whenever the control stopped being dragged.
/// </summary>
public event DragEndedEventHandler DragEnded;
/// <summary>
/// Event called whenever the control was dropped.
/// </summary>
public event DroppedEventHandler Dropped;
/// <summary>
/// Event called whenever the control was pressed.
/// </summary>
public event PressedEventHandler Pressed;
/// <summary>
/// Event called whenever the control was released after being pressed.
/// </summary>
public event ReleasedEventHandler Released;
/// <summary>
/// Event called whenever the control was clicked. A click depending on the operating mode can be a press or a release
/// after a press.
/// </summary>
public event ClickedEventHandler Clicked;
/// <summary>
/// Event called whenever the control was kept being pressed for <see cref="PressAndHoldDuration" /> seconds without
/// being dragged.
/// </summary>
public event PressHeldEventHandler PressHeld;
/// <summary>
/// Event called whenever the pointer entered the control.
/// </summary>
public event CursorEnteredEventHandler CursorEntered;
/// <summary>
/// Event called whenever the pointer exited the control.
/// </summary>
public event CursorExitedEventHandler CursorExited;
/// <summary>
/// Event called whenever the selected control's input field is updated/changed.
/// </summary>
public event UpdateSelectedEventHandler UpdateSelected;
/// <summary>
/// Event called whenever the control's input field was submitted (OK was pressed).
/// </summary>
public event InputSubmittedEventHandler InputSubmitted;
/// <summary>
/// Gets the control's GameObject
/// </summary>
public GameObject GameObject => gameObject;
/// <summary>
/// Gets the Unity RectTransform component.
/// </summary>
public RectTransform RectTransform { get; private set; }
/// <summary>
/// Gets the Unity Image component on the same object if it exists.
/// </summary>
public Image Image { get; private set; }
/// <summary>
/// Gets the ScrollRect reference used in drag/scroll events.
/// </summary>
public ScrollRect ScrollRect { get; private set; }
/// <summary>
/// Gets whether the control is currently being dragged.
/// </summary>
public bool IsDragging { get; private set; }
/// <summary>
/// Gets whether the component is being destroyed. This means OnDestroy() was called the same frame
/// and will effectively be destroyed at the end of it.
/// </summary>
public bool IsBeingDestroyed { get; private set; }
/// <summary>
/// Gets or sets whether the object can be interacted with and will send any events.
/// </summary>
public bool Enabled
{
get => enabled;
set
{
enabled = value;
if (_selectable != null)
{
_selectable.interactable = value && _interactable;
}
if (_graphic != null)
{
_graphic.raycastTarget = value && _raycastTarget;
}
}
}
/// <summary>
/// Gets or sets whether the widget is interactable or not. The widget should have a <see cref="Selectable" />
/// component.
/// </summary>
public bool Interactable
{
get => _interactable;
set
{
if (_selectable != null)
{
_selectable.interactable = value;
}
_interactable = value;
}
}
/// <summary>
/// Gets or sets the custom data property.
/// </summary>
public object Tag { get; set; }
/// <summary>
/// Gets whether the control was clicked since the last time it was set to false.
/// </summary>
public bool WasClicked { get; set; }
/// <summary>
/// Gets or sets how many seconds need to pass to trigger a <see cref="PressHeld" /> event
/// </summary>
public float PressAndHoldDuration
{
get => _pressAndHoldDuration;
set => _pressAndHoldDuration = value;
}
/// <summary>
/// Gets or sets the feedback when the UI element was pressed.
/// </summary>
public UxrControlFeedback FeedbackOnDown
{
get => _feedbackOnPress;
set => _feedbackOnPress = value;
}
/// <summary>
/// Gets or sets the feedback when the UI element was released.
/// </summary>
public UxrControlFeedback FeedbackOnUp
{
get => _feedbackOnRelease;
set => _feedbackOnRelease = value;
}
/// <summary>
/// Gets or sets the feedback when the UI element was clicked.
/// </summary>
public UxrControlFeedback FeedbackOnClick
{
get => _feedbackOnClick;
set => _feedbackOnClick = value;
}
#endregion
#region Public Methods
/// <summary>
/// Creates an awaitable task that blocks until a control from a given set is clicked, and returns the control that was
/// clicked.
/// </summary>
/// <param name="ct">Cancellation token, to cancel the task</param>
/// <param name="controls">Controls to listen to</param>
/// <returns>Awaitable <see cref="Task" /> returning the control that was clicked, or null if the task was cancelled</returns>
public static async Task<UxrControlInput> WaitForClickAsync(CancellationToken ct, params UxrControlInput[] controls)
{
async Task<UxrControlInput> ReadControl(UxrControlInput control, CancellationToken ct = default)
{
await control.WaitForClickAsync(ct);
return ct.IsCancellationRequested ? null : control;
}
using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
IEnumerable<Task<UxrControlInput>> tasks = controls.Select(b => ReadControl(b, ct));
Task<UxrControlInput> finishedTask = await Task.WhenAny(tasks);
if (!finishedTask.IsCanceled)
{
cts.Cancel();
}
return await finishedTask;
}
/// <summary>
/// Creates an awaitable task that blocks until the control is clicked.
/// </summary>
/// <param name="ct">Optional cancellation token, to cancel the task</param>
/// <returns>Awaitable <see cref="Task" /> returning the control that was clicked or null if the task was cancelled</returns>
public async Task WaitForClickAsync(CancellationToken ct = default)
{
bool isClicked = false;
void ControlClicked(UxrControlInput localControl, PointerEventData eventData)
{
isClicked = localControl.Interactable;
}
Clicked += ControlClicked;
await TaskExt.WaitUntil(() => isClicked, ct);
Clicked -= ControlClicked;
}
#endregion
#region Unity
/// <summary>
/// Sets up the internal references.
/// </summary>
protected virtual void Awake()
{
RectTransform = GetComponent<RectTransform>();
Image = GetComponent<Image>();
ScrollRect = GetComponentInParent<ScrollRect>();
_selectable = GetComponent<Selectable>();
_graphic = GetComponent<Graphic>();
if (_graphic)
{
_raycastTarget = _graphic.raycastTarget;
}
if (_selectable != null)
{
Interactable &= _selectable.interactable;
}
}
/// <summary>
/// Unity OnDestroy() method.
/// </summary>
protected virtual void OnDestroy()
{
IsBeingDestroyed = true;
}
/// <summary>
/// Unity OnEnable() method.
/// </summary>
protected virtual void OnEnable()
{
}
/// <summary>
/// Unity OnDisable() method.
/// </summary>
protected virtual void OnDisable()
{
}
/// <summary>
/// Resets the component.
/// </summary>
protected virtual void Reset()
{
_feedbackOnPress = UxrControlFeedback.FeedbackDown;
_feedbackOnRelease = UxrControlFeedback.FeedbackUp;
_feedbackOnClick = UxrControlFeedback.FeedbackClick;
}
/// <summary>
/// Unity Start() method.
/// </summary>
protected virtual void Start()
{
}
/// <summary>
/// Checks for the press held event.
/// </summary>
protected virtual void Update()
{
CheckPressHeldEvent();
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Method called by Unity when the control started being dragged.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnBeginDrag(PointerEventData eventData)
{
base.OnBeginDrag(eventData);
OnDragStarted(eventData);
}
/// <summary>
/// Method called by Unity each frame the control is being dragged.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnDrag(PointerEventData eventData)
{
base.OnDrag(eventData);
OnDragged(eventData);
}
/// <summary>
/// Method called by Unity when a drag event ended on the control.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnEndDrag(PointerEventData eventData)
{
base.OnEndDrag(eventData);
OnDragEnded(eventData);
}
/// <summary>
/// Method called by Unity when the control was dropped.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnDrop(PointerEventData eventData)
{
base.OnDrop(eventData);
OnDropped(eventData);
}
/// <summary>
/// Method called by Unity when the control was pressed.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnPointerDown(PointerEventData eventData)
{
base.OnPointerDown(eventData);
OnPressed(eventData);
}
/// <summary>
/// Method called by Unity when the control was released after being pressed.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnPointerUp(PointerEventData eventData)
{
base.OnPointerUp(eventData);
OnReleased(eventData);
}
/// <summary>
/// Method called by Unity when the control was clicked. A click depending on the operating mode can be a press or a
/// release after a press.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnPointerClick(PointerEventData eventData)
{
base.OnPointerClick(eventData);
OnClicked(eventData);
}
/// <summary>
/// Method called by Unity when the cursor entered the control rect.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnPointerEnter(PointerEventData eventData)
{
base.OnPointerEnter(eventData);
OnCursorEntered(eventData);
}
/// <summary>
/// Method called by Unity when the cursor exited the control rect.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnPointerExit(PointerEventData eventData)
{
base.OnPointerExit(eventData);
OnCursorExited(eventData);
}
/// <summary>
/// Method called by Unity when the content of an InputField was updated on the control.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnUpdateSelected(BaseEventData eventData)
{
base.OnUpdateSelected(eventData);
if (enabled && UpdateSelected != null)
{
UpdateSelected?.Invoke(this, eventData);
}
}
/// <summary>
/// Method called by Unity when the content of an InputField was validated (OK was pressed) on the control.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnSubmit(BaseEventData eventData)
{
base.OnSubmit(eventData);
OnInputSubmitted(eventData);
}
/// <summary>
/// Method called by Unity when the content was scrolled on the control.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnScroll(PointerEventData eventData)
{
base.OnScroll(eventData);
if (ScrollRect != null)
{
ScrollRect.OnScroll(eventData);
}
}
/// <summary>
/// Method called by Unity when the control was selected.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnSelect(BaseEventData eventData)
{
base.OnSelect(eventData);
}
/// <summary>
/// Method called by Unity when a Cancel event was sent to control.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnCancel(BaseEventData eventData)
{
base.OnCancel(eventData);
}
/// <summary>
/// Method called by Unity when the control was deselected.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnDeselect(BaseEventData eventData)
{
base.OnDeselect(eventData);
}
/// <summary>
/// Method called by Unity when a potential drag could be started on the the control but the drag did not start yet.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnInitializePotentialDrag(PointerEventData eventData)
{
base.OnInitializePotentialDrag(eventData);
}
/// <summary>
/// Method called when navigating through the control.
/// </summary>
/// <param name="eventData">Event data</param>
public override void OnMove(AxisEventData eventData)
{
base.OnMove(eventData);
}
/// <summary>
/// Overridable event trigger for <see cref="DragStarted" /> and <see cref="GlobalDragStarted" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnDragStarted(PointerEventData eventData)
{
if (ScrollRect != null)
{
ScrollRect.OnBeginDrag(eventData);
}
if (enabled)
{
IsDragging = true;
DragStarted?.Invoke(this, eventData);
GlobalDragStarted?.Invoke(this, eventData);
}
ResetTapAndHoldEventInfo();
}
/// <summary>
/// Overridable event trigger for <see cref="Dragged" /> and <see cref="GlobalDragged" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnDragged(PointerEventData eventData)
{
if (ScrollRect != null)
{
ScrollRect.OnDrag(eventData);
}
if (enabled)
{
Dragged?.Invoke(this, eventData);
GlobalDragged?.Invoke(this, eventData);
}
}
/// <summary>
/// Overridable event trigger for <see cref="DragEnded" /> and <see cref="GlobalDragEnded" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnDragEnded(PointerEventData eventData)
{
if (ScrollRect != null)
{
ScrollRect.OnEndDrag(eventData);
}
if (enabled)
{
IsDragging = false;
DragEnded?.Invoke(this, eventData);
GlobalDragEnded?.Invoke(this, eventData);
}
}
/// <summary>
/// Overridable event trigger for <see cref="Dropped" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnDropped(PointerEventData eventData)
{
if (enabled && Dropped != null)
{
Dropped?.Invoke(this, eventData);
}
}
/// <summary>
/// Overridable event trigger for <see cref="Pressed" /> and <see cref="GlobalPressed" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnPressed(PointerEventData eventData)
{
if (enabled)
{
_isPressAndHold = true;
_pressAndHoldEventData = eventData;
_pressAndHoldTimer = 0.0f;
GlobalPressed?.Invoke(this, eventData);
Pressed?.Invoke(this, eventData);
}
}
/// <summary>
/// Overridable event trigger for <see cref="Released" /> and <see cref="GlobalReleased" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnReleased(PointerEventData eventData)
{
if (enabled)
{
GlobalReleased?.Invoke(this, eventData);
Released?.Invoke(this, eventData);
}
ResetTapAndHoldEventInfo();
}
/// <summary>
/// Overridable event trigger for <see cref="Clicked" /> and <see cref="GlobalClicked" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnClicked(PointerEventData eventData)
{
if (!IsDragging && enabled && Interactable)
{
WasClicked = true;
GlobalClicked?.Invoke(this, eventData);
Clicked?.Invoke(this, eventData);
}
}
/// <summary>
/// Overridable event trigger for <see cref="CursorEntered" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnCursorEntered(PointerEventData eventData)
{
if (enabled)
{
CursorEntered?.Invoke(this, eventData);
}
}
/// <summary>
/// Overridable event trigger for <see cref="CursorExited" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnCursorExited(PointerEventData eventData)
{
if (enabled)
{
CursorExited?.Invoke(this, eventData);
}
}
/// <summary>
/// Overridable event trigger for <see cref="InputSubmitted" />.
/// </summary>
/// <param name="eventData">Event parameters</param>
protected virtual void OnInputSubmitted(BaseEventData eventData)
{
if (enabled && InputSubmitted != null)
{
InputSubmitted?.Invoke(this, eventData);
}
}
#endregion
#region Private Methods
/// <summary>
/// Checks if the TapAndHold timer reached its goal.
/// </summary>
private void CheckPressHeldEvent()
{
if (PressHeld != null && _isPressAndHold)
{
_pressAndHoldTimer += Time.deltaTime;
if (_pressAndHoldTimer > _pressAndHoldDuration)
{
PressHeld(this, _pressAndHoldEventData);
ResetTapAndHoldEventInfo();
}
}
}
/// <summary>
/// Resets the TapAndHold timers and state
/// </summary>
private void ResetTapAndHoldEventInfo()
{
_isPressAndHold = false;
_pressAndHoldEventData = null;
_pressAndHoldTimer = 0.0f;
}
#endregion
#region Private Types & Data
private Selectable _selectable;
private Graphic _graphic;
private bool _raycastTarget;
private float _pressAndHoldTimer;
private bool _isPressAndHold;
private PointerEventData _pressAndHoldEventData;
private bool _interactable = true;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 88631d58f15603847b5bde7ae5865d1f
timeCreated: 1457945442
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,35 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrToggleControlInput.InitState.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.UI.UnityInputModule.Controls
{
public partial class UxrToggleControlInput
{
#region Public Types & Data
/// <summary>
/// Enumerates the different initial states of the toggle control.
/// </summary>
public enum InitState
{
/// <summary>
/// Initially toggled off.
/// </summary>
ToggledOff = 0,
/// <summary>
/// Initially toggled on.
/// </summary>
ToggledOn = 1,
/// <summary>
/// Don't change the toggle.
/// </summary>
DontChange = 2
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bded173e3f90483c826317e3b6fe50ed
timeCreated: 1686586842

View File

@@ -0,0 +1,52 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrToggleControlInput.TextColorChange.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UnityEngine;
using UnityEngine.UI;
namespace UltimateXR.UI.UnityInputModule.Controls
{
public partial class UxrToggleControlInput
{
#region Private Types & Data
/// <summary>
/// Allows to specify separate selected/not-selected colors for a text.
/// </summary>
[Serializable]
private class TextColorChange
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Text _text;
[SerializeField] private Color _colorSelected;
[SerializeField] private Color _colorNotSelected;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the text component.
/// </summary>
public Text TextComponent => _text;
/// <summary>
/// Gets the color used when the element is selected.
/// </summary>
public Color ColorSelected => _colorSelected;
/// <summary>
/// Gets the color used when the element is not selected.
/// </summary>
public Color ColorNotSelected => _colorNotSelected;
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a26af86e3fe64c0fbc1ce1f0fb47d9cd
timeCreated: 1643734079

View File

@@ -0,0 +1,241 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrToggleControlInput.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Settings;
using UltimateXR.Extensions.Unity;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
using UnityEngine.UI;
#pragma warning disable 67 // Disable warnings due to unused events
namespace UltimateXR.UI.UnityInputModule.Controls
{
/// <summary>
/// Type of <see cref="UxrControlInput" /> that implements toggle functionality.
/// </summary>
public partial class UxrToggleControlInput : UxrControlInput
{
#region Inspector Properties/Serialized Fields
[FormerlySerializedAs("_initialStateIsSelected")] [SerializeField] private InitState _initialState = InitState.DontChange;
[SerializeField] private bool _canToggleOnlyOnce;
[SerializeField] private Text _text;
[SerializeField] private List<GameObject> _enableWhenSelected;
[SerializeField] private List<GameObject> _enableWhenNotSelected;
[SerializeField] private List<TextColorChange> _textColorChanges;
[SerializeField] private AudioClip _audioToggleOn;
[SerializeField] private AudioClip _audioToggleOff;
[SerializeField] [Range(0, 1)] private float _audioToggleOnVolume = 1.0f;
[SerializeField] [Range(0, 1)] private float _audioToggleOffVolume = 1.0f;
#endregion
#region Public Types & Data
/// <summary>
/// Event called whenever the state is toggled.
/// </summary>
public event Action<UxrToggleControlInput> Toggled;
/// <summary>
/// Gets or sets whether the current toggled state.
/// To set the state of the control without triggering any events, use <see cref="SetIsSelected" /> instead.
/// </summary>
public bool IsSelected
{
get => _isSelected;
set => SetIsSelected(value, true);
}
/// <summary>
/// Gets or sets whether the control can be toggled or not.
/// </summary>
public bool CanBeToggled { get; set; } = true;
/// <summary>
/// Gets or sets the text value. If no <see cref="Text" /> component is configured it will return
/// <see cref="string.Empty" />.
/// </summary>
public string Text
{
get => _text != null ? _text.text : string.Empty;
set
{
if (_text != null)
{
_text.text = value;
}
}
}
#endregion
#region Public Methods
/// <summary>
/// Changes the current state of the control like <see cref="IsSelected" /> but allowing to control whether
/// <see cref="Toggled" /> events are propagated or not.
/// </summary>
/// <param name="value">State (selected/not-selected)</param>
/// <param name="propagateEvents">Whether to propagate events</param>
public void SetIsSelected(bool value, bool propagateEvents)
{
if (_isSelected == value && _isInitialized)
{
return;
}
_isSelected = value;
foreach (GameObject goToEnable in _enableWhenSelected)
{
if (goToEnable == null)
{
if (UxrGlobalSettings.Instance.LogLevelUI >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.UiModule} {transform.GetPathUnderScene()} has null enableWhenSelected entry");
}
}
else
{
goToEnable.SetActive(_isSelected);
}
}
foreach (GameObject goToEnable in _enableWhenNotSelected)
{
if (goToEnable == null)
{
if (UxrGlobalSettings.Instance.LogLevelUI >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.UiModule} {transform.GetPathUnderScene()} has null enableWhenNotSelected entry");
}
}
else
{
goToEnable.SetActive(!_isSelected);
}
}
foreach (TextColorChange textEntry in _textColorChanges)
{
textEntry.TextComponent.color = _isSelected ? textEntry.ColorSelected : textEntry.ColorNotSelected;
}
_isInitialized = true;
if (propagateEvents)
{
Toggled?.Invoke(this);
}
}
#endregion
#region Unity
/// <summary>
/// Sets up the events and initializes the current state.
/// </summary>
protected override void Awake()
{
base.Awake();
if (!_isInitialized && _initialState != InitState.DontChange)
{
SetIsSelected(_initialState == InitState.ToggledOn, true);
}
_alreadyToggled = false;
}
/// <summary>
/// Called when the component is destroyed.
/// </summary>
protected override void OnDestroy()
{
base.OnDestroy();
_text = null;
_enableWhenSelected = null;
_enableWhenNotSelected = null;
_textColorChanges = null;
}
/// <summary>
/// Checks for a <see cref="UxrToggleGroup" /> in any parent object to refresh the content.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
UxrToggleGroup group = GetComponentInParent<UxrToggleGroup>();
if (group != null)
{
group.RefreshToggleChildrenList();
}
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Control was clicked. Toggle state.
/// </summary>
/// <param name="eventData">Event data</param>
protected override void OnClicked(PointerEventData eventData)
{
base.OnClicked(eventData);
if (!CanBeToggled || (_alreadyToggled && _canToggleOnlyOnce))
{
return;
}
if (Interactable)
{
_alreadyToggled = true;
if (_canToggleOnlyOnce)
{
Enabled = false;
}
Vector3 audioPosition = UxrAvatar.LocalAvatarCamera ? UxrAvatar.LocalAvatar.CameraPosition : transform.position;
if (_audioToggleOff && !_isSelected)
{
AudioSource.PlayClipAtPoint(_audioToggleOff, audioPosition, _audioToggleOffVolume);
}
else if (_audioToggleOn && _isSelected)
{
AudioSource.PlayClipAtPoint(_audioToggleOn, audioPosition, _audioToggleOnVolume);
}
SetIsSelected(!_isSelected, true);
}
}
#endregion
#region Private Types & Data
private bool _isInitialized;
private bool _isSelected;
private bool _alreadyToggled;
#endregion
}
}
#pragma warning restore 67

View File

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

View File

@@ -0,0 +1,30 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrToggleGroup.InitState.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.UI.UnityInputModule.Controls
{
public partial class UxrToggleGroup
{
#region Public Types & Data
/// <summary>
/// Enumerates the possible initial states, telling which child is currently selected.
/// </summary>
public enum InitState
{
/// <summary>
/// Initial state is determined by the state at edit-time.
/// </summary>
DontChange,
/// <summary>
/// First child is toggled on, the rest are toggled off.
/// </summary>
FirstChild
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 126143e2841b48b89285dfbf55f46861
timeCreated: 1686581341

View File

@@ -0,0 +1,52 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrToggleGroup.StateSave.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core;
using UltimateXR.Core.StateSave;
namespace UltimateXR.UI.UnityInputModule.Controls
{
public partial class UxrToggleGroup
{
#region Protected Overrides UxrComponent
protected override int SerializationOrder => UxrConstants.Serialization.SerializationOrderDefault + 1;
protected override void SerializeState(bool isReading, int stateSerializationVersion, UxrStateSaveLevel level, UxrStateSaveOptions options)
{
base.SerializeState(isReading, stateSerializationVersion, level, options);
if (level >= UxrStateSaveLevel.ChangesSinceBeginning)
{
int currentSelectionIndex = _toggles?.IndexOf(CurrentSelection) ?? -1;
SerializeStateValue(level, options, nameof(currentSelectionIndex), ref currentSelectionIndex);
if (isReading)
{
CurrentSelection = _toggles != null && currentSelectionIndex >= 0 && currentSelectionIndex < _toggles.Count ? _toggles[currentSelectionIndex] : null;
if (CurrentSelection != null)
{
CurrentSelection.IsSelected = true;
}
else
{
if (_toggles != null)
{
foreach (UxrToggleControlInput t in _toggles)
{
t.CanBeToggled = true;
t.SetIsSelected(false, false);
}
}
}
}
}
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0f30cc83c3d143fc965842a9f4269e94
timeCreated: 1710686450

View File

@@ -0,0 +1,199 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrToggleGroup.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.UI.UnityInputModule.Controls
{
/// <summary>
/// Component that, added to a GameObject, will make all child <see cref="UxrToggleControlInput" /> components behave
/// like a single group where only one selection can be active at a time.
/// </summary>
public partial class UxrToggleGroup : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private InitState _initialToggleState = InitState.DontChange;
#endregion
#region Public Types & Data
/// <summary>
/// Event called whenever the currently selected toggle control was changed.
/// </summary>
public event Action<UxrToggleControlInput> SelectionChanged;
/// <summary>
/// Gets all the current toggle components.
/// </summary>
public IList<UxrToggleControlInput> Toggles => _toggles;
/// <summary>
/// Gets the currently selected toggle component.
/// </summary>
public UxrToggleControlInput CurrentSelection { get; private set; }
#endregion
#region Public Methods
/// <summary>
/// Refreshes the internal list of children toggle components. This can be used to refresh the list whenever a new
/// child toggle component was added dynamically, or was removed.
/// </summary>
public void RefreshToggleChildrenList()
{
_toggles.ForEach(t => t.Toggled -= Toggle_Toggled);
_toggles.Clear();
_toggles = new List<UxrToggleControlInput>();
for (int i = 0; i < transform.childCount; ++i)
{
UxrToggleControlInput toggle = transform.GetChild(i).GetComponent<UxrToggleControlInput>();
if (toggle != null && !toggle.IsBeingDestroyed && toggle.isActiveAndEnabled)
{
_toggles.Add(toggle);
toggle.Toggled += Toggle_Toggled;
}
}
CurrentSelection = _toggles.FirstOrDefault(t => t.IsSelected);
_toggles.ForEach(t => t.CanBeToggled = t != CurrentSelection);
}
/// <summary>
/// Clears the current selection if there is any.
/// </summary>
public void ClearCurrentSelection()
{
BeginSync();
if (CurrentSelection != null)
{
CurrentSelection.IsSelected = false;
CurrentSelection.CanBeToggled = true;
CurrentSelection = null;
}
EndSyncMethod();
}
#endregion
#region Unity
/// <summary>
/// Refreshes the toggle list and subscribes to toggle events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
if (!_toggles.Any())
{
RefreshToggleChildrenList();
}
}
/// <summary>
/// Unsubscribes from toggle events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
_toggles.ForEach(t => t.Toggled -= Toggle_Toggled);
}
/// <summary>
/// Initializes the current selection if required.
/// </summary>
protected override void Start()
{
base.Start();
switch (_initialToggleState)
{
case InitState.DontChange: break;
case InitState.FirstChild:
if (_toggles.Any())
{
_toggles.First().IsSelected = true;
}
break;
}
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called whenever a toggle component was toggled.
/// </summary>
/// <param name="toggle">The toggle component that was toggled</param>
private void Toggle_Toggled(UxrToggleControlInput toggle)
{
NotifyToggle(_toggles.IndexOf(toggle), true);
}
#endregion
#region Private Methods
/// <summary>
/// Notifies that a toggle component was toggled.
/// </summary>
/// <param name="index">The index of the toggle</param>
/// <param name="isFromEvent">Whether the call comes from handling a toggle event</param>
private void NotifyToggle(int index, bool isFromEvent)
{
BeginSync();
UxrToggleControlInput toggle = index >= 0 && index < _toggles.Count ? _toggles[index] : null;
foreach (UxrToggleControlInput t in _toggles)
{
t.CanBeToggled = t != toggle;
if (t != null && t != toggle)
{
t.SetIsSelected(false, false);
}
}
CurrentSelection = toggle;
if (CurrentSelection != null && !isFromEvent)
{
// Since it doesn't come from an event, we need to update the selection manually.
CurrentSelection.SetIsSelected(true, false);
}
SelectionChanged?.Invoke(toggle);
EndSyncMethod(new object[] { index, false });
}
#endregion
#region Private Types & Data
private List<UxrToggleControlInput> _toggles = new List<UxrToggleControlInput>();
#endregion
}
}

View File

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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9ab744bd16f548f987238abb3edb36f6
timeCreated: 1643966807

View File

@@ -0,0 +1,159 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrButton3D.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UltimateXR.UI.UnityInputModule.Controls;
using UnityEngine;
using UnityEngine.EventSystems;
namespace UltimateXR.UI.UnityInputModule.Utils
{
/// <summary>
/// Base class to simplify interacting with 3D button objects by programming 2D UI elements.
/// A 2D Unity UI Canvas is placed on top of the 3D buttons. The Canvas will contain invisible
/// <see cref="UxrControlInput" /> UI components by using <see cref="UxrNonDrawingGraphic" /> instead of images.
/// The <see cref="UxrControlInput" /> components will get the user input and through child implementations of
/// <see cref="UxrButton3D" /> the 3D objects will be "pushed", "rotated" creating 3D behaviour using 2D logic.
/// </summary>
[RequireComponent(typeof(UxrControlInput))]
public class UxrButton3D : UxrComponent<Canvas, UxrButton3D>
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Transform _targetTransform;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the UI input component.
/// </summary>
public UxrControlInput ControlInput => GetCachedComponent<UxrControlInput>();
/// <summary>
/// Gets the <see cref="Transform" /> of the 3D object that is going to move, rotate, scale...
/// </summary>
public Transform Target => _targetTransform;
/// <summary>
/// Gets <see cref="Target" />'s local position during Awake().
/// </summary>
public Vector3 InitialTargetLocalPosition { get; private set; }
/// <summary>
/// Gets <see cref="Target" />'s local rotation during Awake().
/// </summary>
public Quaternion InitialTargetLocalRotation { get; private set; }
/// <summary>
/// Gets <see cref="Target" />'s world position during Awake().
/// </summary>
public Vector3 InitialTargetPosition { get; private set; }
/// <summary>
/// Gets <see cref="Target" />'s world rotation during Awake().
/// </summary>
public Quaternion InitialTargetRotation { get; private set; }
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
if (Target != null)
{
InitialTargetLocalPosition = Target.localPosition;
InitialTargetLocalRotation = Target.localRotation;
InitialTargetPosition = Target.position;
InitialTargetRotation = Target.rotation;
}
}
/// <summary>
/// Subscribes to the input control events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
if (ControlInput)
{
ControlInput.Pressed += ControlInput_Pressed;
ControlInput.Released += ControlInput_Released;
}
}
/// <summary>
/// Unsubscribes from the input control events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
if (ControlInput)
{
ControlInput.Pressed -= ControlInput_Pressed;
ControlInput.Released -= ControlInput_Released;
}
}
#endregion
#region Event Handling Methods
/// <summary>
/// Receives the key down event.
/// </summary>
/// <param name="controlInput">Control that triggered the event</param>
/// <param name="eventData">Input event data</param>
private void ControlInput_Pressed(UxrControlInput controlInput, PointerEventData eventData)
{
OnKeyPressed(controlInput, eventData);
}
/// <summary>
/// Receives the key up event.
/// </summary>
/// <param name="controlInput">Control that triggered the event</param>
/// <param name="eventData">Input event data</param>
private void ControlInput_Released(UxrControlInput controlInput, PointerEventData eventData)
{
OnKeyReleased(controlInput, eventData);
}
#endregion
#region Event Trigger Methods
/// <summary>
/// Event trigger for the key pressed event. It can be overridden in child classes to handle key presses without
/// subscribing to events.
/// </summary>
/// <param name="controlInput">Control that triggered the event</param>
/// <param name="eventData">Input event data</param>
protected virtual void OnKeyPressed(UxrControlInput controlInput, PointerEventData eventData)
{
}
/// <summary>
/// Event trigger for the key released event. It can be overridden in child classes to handle key releases without
/// subscribing to events.
/// </summary>
/// <param name="controlInput">Control that triggered the event</param>
/// <param name="eventData">Input event data</param>
protected virtual void OnKeyReleased(UxrControlInput controlInput, PointerEventData eventData)
{
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bb3459a5e0294a478dcf6712b27704b1
timeCreated: 1640709321

View File

@@ -0,0 +1,60 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrButton3DPress.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.UI.UnityInputModule.Controls;
using UnityEngine;
using UnityEngine.EventSystems;
namespace UltimateXR.UI.UnityInputModule.Utils
{
/// <summary>
/// Component that moves a 3D object when a given UI control is being pressed
/// </summary>
public class UxrButton3DPress : UxrButton3D
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Vector3 _pressedLocalOffset;
#endregion
#region Event Trigger Methods
/// <summary>
/// Key down event. The object is moved to the pressed local coordinates.
/// </summary>
/// <param name="controlInput">Control that triggered the event</param>
/// <param name="eventData">Input event data</param>
protected override void OnKeyPressed(UxrControlInput controlInput, PointerEventData eventData)
{
if (Target)
{
Vector3 pressedLocalOffset = _pressedLocalOffset;
if (Target.parent != null)
{
pressedLocalOffset = Target.parent.InverseTransformDirection(Target.TransformDirection(pressedLocalOffset));
}
Target.localPosition = InitialTargetLocalPosition + pressedLocalOffset;
}
}
/// <summary>
/// Key up event. The original object position is restored.
/// </summary>
/// <param name="controlInput">Control that triggered the event</param>
/// <param name="eventData">Input event data</param>
protected override void OnKeyReleased(UxrControlInput controlInput, PointerEventData eventData)
{
if (Target)
{
Target.localPosition = InitialTargetLocalPosition;
}
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 11f37e36dfbb79d448e633e26928b45b
timeCreated: 1531470375
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,58 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrButton3DRotate.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.UI.UnityInputModule.Controls;
using UnityEngine;
using UnityEngine.EventSystems;
namespace UltimateXR.UI.UnityInputModule.Utils
{
/// <summary>
/// Component that rotates a 3D object when a given UI control is being pressed.
/// This allows to model buttons that rotate depending on the point of pressure.
/// The axis of rotation will be computed automatically, the center will be given by <see cref="UxrButton3D.Target" />
/// and the pressure applied will be on the transform of this component.
/// </summary>
public class UxrButton3DRotate : UxrButton3D
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Vector3 _buttonLocalUpAxis = Vector3.up;
[SerializeField] private float _pressedDegrees = 2.0f;
#endregion
#region Event Trigger Methods
/// <summary>
/// Key down event. The object is rotated according to the pressing point.
/// </summary>
/// <param name="controlInput">Control that triggered the event</param>
/// <param name="eventData">Input event data</param>
protected override void OnKeyPressed(UxrControlInput controlInput, PointerEventData eventData)
{
if (Target)
{
Vector3 rotationAxis = Vector3.Cross(_buttonLocalUpAxis, Target.InverseTransformVector(Target.position - transform.position).normalized);
Target.Rotate(rotationAxis, -_pressedDegrees, Space.Self);
}
}
/// <summary>
/// Key up event. The original object rotation is restored.
/// </summary>
/// <param name="controlInput">Control that triggered the event</param>
/// <param name="eventData">Input event data</param>
protected override void OnKeyReleased(UxrControlInput controlInput, PointerEventData eventData)
{
if (Target)
{
Target.localRotation = InitialTargetLocalRotation;
}
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: b4edbb5eee02f6f41a8193c5b29c0421
timeCreated: 1531470375
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,67 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrControlInputDestroyOnPress.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UltimateXR.UI.UnityInputModule.Controls;
using UnityEngine;
using UnityEngine.EventSystems;
namespace UltimateXR.UI.UnityInputModule.Utils
{
/// <summary>
/// Component that, added to a <see cref="GameObject" /> with a <see cref="UxrControlInput" /> component, will destroy
/// the GameObject whenever the control is clicked.
/// </summary>
[RequireComponent(typeof(UxrControlInput))]
public class UxrControlInputDestroyOnPress : UxrComponent
{
#region Public Types & Data
/// <summary>
/// Gets the <see cref="UxrControlInput" /> component.
/// </summary>
public UxrControlInput ControlInput => GetCachedComponent<UxrControlInput>();
#endregion
#region Unity
/// <summary>
/// Subscribes to events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
ControlInput.Clicked += Control_Clicked;
}
/// <summary>
/// Unsubscribes from events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
ControlInput.Clicked -= Control_Clicked;
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called when the object was clicked.
/// </summary>
/// <param name="controlInput">Control that was clicked</param>
/// <param name="eventData">Event data</param>
private void Control_Clicked(UxrControlInput controlInput, PointerEventData eventData)
{
Destroy(gameObject);
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,92 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrDynamicPixelsPerUnit.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UnityEngine;
using UnityEngine.UI;
namespace UltimateXR.UI.UnityInputModule.Utils
{
/// <summary>
/// Component that adjusts the dynamic pixels per unit value in a <see cref="Canvas" /> component depending on
/// the distance to the avatar. It helps removing filtering artifacts when using composition layers is not
/// possible.
/// </summary>
[RequireComponent(typeof(CanvasScaler))]
public class UxrDynamicPixelsPerUnit : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private float _updateSeconds = 0.3f;
[SerializeField] private float _rangeNear = 0.3f;
[SerializeField] private float _rangeFar = 4.0f;
[SerializeField] private float _pixelsPerUnitNear = 1.0f;
[SerializeField] private float _pixelsPerUnitFar = 0.1f;
#endregion
#region Unity
/// <summary>
/// Caches components.
/// </summary>
protected override void Awake()
{
base.Awake();
_canvasScaler = GetComponent<CanvasScaler>();
}
/// <summary>
/// Subscribes to events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
UxrAvatar.GlobalAvatarMoved += UxrAvatar_GlobalAvatarMoved;
}
/// <summary>
/// Unsubscribes from events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
UxrAvatar.GlobalAvatarMoved += UxrAvatar_GlobalAvatarMoved;
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called when an avatar moved: Adjusts the dynamic pixels per unit.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event parameters</param>
private void UxrAvatar_GlobalAvatarMoved(object sender, UxrAvatarMoveEventArgs e)
{
UxrAvatar avatar = sender as UxrAvatar;
if (avatar == UxrAvatar.LocalAvatar && Time.time - _timeLastUpdate > _updateSeconds)
{
_timeLastUpdate = Time.time;
float distance = Vector3.Distance(avatar.CameraPosition, _canvasScaler.transform.position);
_canvasScaler.dynamicPixelsPerUnit = Mathf.Lerp(_pixelsPerUnitNear, _pixelsPerUnitFar, Mathf.Clamp01((distance - _rangeNear) / (_rangeFar - _rangeNear)));
}
}
#endregion
#region Private Types & Data
private float _timeLastUpdate = -1.0f;
private CanvasScaler _canvasScaler;
#endregion
}
}

View File

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

View File

@@ -0,0 +1,155 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrHoverTimerClick.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Core.Settings;
using UltimateXR.UI.UnityInputModule.Controls;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace UltimateXR.UI.UnityInputModule.Utils
{
/// <summary>
/// Component that, added to a <see cref="GameObject" /> with a <see cref="UxrControlInput" /> component, will
/// automatically generate a Click event on the control whenever the cursor spends a given amount of time over it.
/// It can be used to implement clicks using the gaze pointer (<see cref="UxrCameraPointer" />).
/// </summary>
[RequireComponent(typeof(UxrControlInput))]
public class UxrHoverTimerClick : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] [Tooltip("Number of seconds the user will need to hover over this element to trigger the Click event")] private float _lookAtSecondsToClick = 2.0f;
[SerializeField] [Tooltip("Unscaled time will use the real device timer. If this parameter is unchecked it will use the scaled timer affected by pauses, bullet-times etc.")] private bool _useUnscaledTime = true;
[SerializeField] [Tooltip("Will update the fill value of the Image component on this same GameObject to represent the timer progress. Needs an Image component.")] private bool _useFillImage = true;
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
_image = GetComponent<Image>();
_controlInput = GetComponent<UxrControlInput>();
_timer = _lookAtSecondsToClick;
if (_useFillImage && _image == null)
{
if (UxrGlobalSettings.Instance.LogLevelUI >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.UiModule} UseFillImage was specified on {GetType().Name} component of GameObject {name} but there is no Image component on it to update fill.");
}
}
else if (_useFillImage && _image != null && _image.type != Image.Type.Filled)
{
if (UxrGlobalSettings.Instance.LogLevelUI >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.UiModule} UseFillImage was specified on {GetType().Name} component of GameObject {name} but the Image component is not of a filled type (Image Type property).");
}
}
}
/// <summary>
/// Subscribes to events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
_controlInput.CursorEntered += Input_Entered;
_controlInput.CursorExited += Input_Exited;
}
/// <summary>
/// Unsubscribes from events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
_controlInput.CursorEntered -= Input_Entered;
_controlInput.CursorExited -= Input_Exited;
}
/// <summary>
/// Updates the progress and checks whether to generate the Click event.
/// </summary>
private void Update()
{
if (_timer > 0.0f)
{
_timer -= _useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime;
if (_timer <= 0.0f)
{
_timer = -1.0f;
if (_useFillImage && _image)
{
_image.fillAmount = 1.0f;
}
ExecuteEvents.ExecuteHierarchy(gameObject, new PointerEventData(EventSystem.current), ExecuteEvents.pointerClickHandler);
}
else
{
if (_useFillImage && _image)
{
_image.fillAmount = Mathf.Clamp01(1.0f - _timer / _lookAtSecondsToClick);
}
}
}
else
{
if (_useFillImage && _image)
{
_image.fillAmount = 0.0f;
}
}
}
#endregion
#region Event Handling Methods
/// <summary>
/// Called whenever the pointer entered the control's Rect.
/// </summary>
/// <param name="controlInput">Control input</param>
/// <param name="eventData">Event data</param>
private void Input_Entered(UxrControlInput controlInput, PointerEventData eventData)
{
_timer = _lookAtSecondsToClick;
}
/// <summary>
/// Called whenever the pointer exited the control's Rect.
/// </summary>
/// <param name="controlInput">Control input</param>
/// <param name="eventData">Event data</param>
private void Input_Exited(UxrControlInput controlInput, PointerEventData eventData)
{
_timer = -1.0f;
}
#endregion
#region Private Types & Data
private Image _image;
private UxrControlInput _controlInput;
private float _timer;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: bac92ce8624fe1144ab04b50f88194c8
timeCreated: 1521406790
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,326 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrRightToLeftSupport.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UltimateXR.Extensions.System.Collections;
using UnityEngine;
using UnityEngine.UI;
#if ULTIMATEXR_UNITY_TMPRO
using TMPro;
#endif
namespace UltimateXR.UI.UnityInputModule.Utils
{
/// <summary>
/// <para>
/// Component that, added to a UI element, will enable support for left-to-right and right-to-left languages.
/// Right-to-left languages not only work by setting text alignment to right, they also require the whole layout to
/// be right-to-left.
/// </para>
/// <para>
/// To switch from one to another use the static property <see cref="UseRightToLeft" />.
/// </para>
/// <para>
/// The supported UI components are:
/// </para>
/// <list type="bullet">
/// <item><see cref="Text" /> (Unity UI)</item>
/// <item>Text UI (TextMeshPro)</item>
/// <item><see cref="HorizontalLayoutGroup" /> (Unity UI)</item>
/// <item><see cref="VerticalLayoutGroup" /> (Unity UI)</item>
/// <item><see cref="GridLayoutGroup" /> (Unity UI)</item>
/// <item><see cref="Image" /> fill origin (Unity UI)</item>
/// </list>
/// </summary>
public class UxrRightToLeftSupport : UxrComponent<UxrRightToLeftSupport>
{
#region Public Types & Data
/// <summary>
/// Sets the global right-to-left setting, changing all <see cref="UxrRightToLeftSupport" /> components.
/// Disabled components, or newly instantiated components, will be aligned correctly too.
/// </summary>
public static bool UseRightToLeft
{
get => s_useRightToLeft;
set
{
s_useRightToLeft = value;
EnabledComponents.ForEach(c => c.SetRightToLeft(value));
}
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
CheckInitialize();
}
/// <summary>
/// Sets the RtoL setting when the component is enabled.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
SetRightToLeft(s_useRightToLeft);
}
#endregion
#region Private Methods
#if ULTIMATEXR_UNITY_TMPRO
/// <summary>
/// Transforms a TMPro alignment value from LtoR to RtoL.
/// </summary>
/// <param name="alignment">Alignment to transform</param>
/// <returns>RtoL value</returns>
private static TextAlignmentOptions GetRtoLAlignmentTMPro(TextAlignmentOptions alignment)
{
switch (alignment)
{
case TextAlignmentOptions.TopRight: return TextAlignmentOptions.TopLeft;
case TextAlignmentOptions.Right: return TextAlignmentOptions.Left;
case TextAlignmentOptions.BottomRight: return TextAlignmentOptions.BottomLeft;
case TextAlignmentOptions.BaselineRight: return TextAlignmentOptions.BaselineLeft;
case TextAlignmentOptions.MidlineRight: return TextAlignmentOptions.MidlineLeft;
case TextAlignmentOptions.CaplineRight: return TextAlignmentOptions.CaplineLeft;
case TextAlignmentOptions.TopLeft: return TextAlignmentOptions.TopRight;
case TextAlignmentOptions.Left: return TextAlignmentOptions.Right;
case TextAlignmentOptions.BottomLeft: return TextAlignmentOptions.BottomRight;
case TextAlignmentOptions.BaselineLeft: return TextAlignmentOptions.BaselineRight;
case TextAlignmentOptions.MidlineLeft: return TextAlignmentOptions.MidlineRight;
case TextAlignmentOptions.CaplineLeft: return TextAlignmentOptions.CaplineRight;
}
return alignment;
}
#endif
/// <summary>
/// Transforms a Unity UI alignment value from LtoR to RtoL.
/// </summary>
/// <param name="alignment">Alignment to transform</param>
/// <returns>RtoL value</returns>
private static TextAnchor GetRtoLAlignment(TextAnchor alignment)
{
switch (alignment)
{
case TextAnchor.UpperLeft: return TextAnchor.UpperRight;
case TextAnchor.UpperRight: return TextAnchor.UpperLeft;
case TextAnchor.MiddleLeft: return TextAnchor.MiddleRight;
case TextAnchor.MiddleRight: return TextAnchor.MiddleLeft;
case TextAnchor.LowerLeft: return TextAnchor.LowerRight;
case TextAnchor.LowerRight: return TextAnchor.LowerLeft;
}
return alignment;
}
/// <summary>
/// Transforms a Unity UI corner value from LtoR to RtoL.
/// </summary>
/// <param name="corner">Corner to transform</param>
/// <returns>RtoL value</returns>
private static GridLayoutGroup.Corner GetRtoLCorner(GridLayoutGroup.Corner corner)
{
switch (corner)
{
case GridLayoutGroup.Corner.UpperLeft: return GridLayoutGroup.Corner.UpperRight;
case GridLayoutGroup.Corner.UpperRight: return GridLayoutGroup.Corner.UpperLeft;
case GridLayoutGroup.Corner.LowerLeft: return GridLayoutGroup.Corner.LowerRight;
case GridLayoutGroup.Corner.LowerRight: return GridLayoutGroup.Corner.LowerLeft;
}
return corner;
}
/// <summary>
/// Transforms a Unity UI fill origin value from LtoR to RtoL.
/// </summary>
/// <param name="fillMethod">Fill method used</param>
/// <param name="fillOrigin">fill origin to transform</param>
/// <returns>RtoL value</returns>
private static int GetRtoLFillOrigin(Image.FillMethod fillMethod, int fillOrigin)
{
if (fillMethod == Image.FillMethod.Horizontal)
{
switch (fillOrigin)
{
case (int)Image.OriginHorizontal.Left: return (int)Image.OriginHorizontal.Right;
case (int)Image.OriginHorizontal.Right: return (int)Image.OriginHorizontal.Left;
}
}
else if (fillMethod == Image.FillMethod.Radial90)
{
switch (fillOrigin)
{
case (int)Image.Origin90.BottomLeft: return (int)Image.Origin90.BottomRight;
case (int)Image.Origin90.BottomRight: return (int)Image.Origin90.BottomLeft;
case (int)Image.Origin90.TopLeft: return (int)Image.Origin90.TopRight;
case (int)Image.Origin90.TopRight: return (int)Image.Origin90.TopLeft;
}
}
else if (fillMethod == Image.FillMethod.Radial180)
{
switch (fillOrigin)
{
case (int)Image.Origin180.Left: return (int)Image.Origin180.Right;
case (int)Image.Origin180.Right: return (int)Image.Origin180.Left;
}
}
else if (fillMethod == Image.FillMethod.Radial360)
{
switch (fillOrigin)
{
case (int)Image.Origin360.Left: return (int)Image.Origin360.Right;
case (int)Image.Origin360.Right: return (int)Image.Origin360.Left;
}
}
return fillOrigin;
}
/// <summary>
/// Gets the component references and stores the initial values.
/// </summary>
private void CheckInitialize()
{
if (_initialized)
{
return;
}
#if ULTIMATEXR_UNITY_TMPRO
_textTMPro = GetComponent<TextMeshProUGUI>();
if (_textTMPro != null)
{
_alignmentTMPro = _textTMPro.alignment;
}
#endif
_text = GetComponent<Text>();
if (_text != null)
{
_textAlignment = _text.alignment;
}
_horizontalLayout = GetComponent<HorizontalLayoutGroup>();
if (_horizontalLayout)
{
_horLayoutAlignment = _horizontalLayout.childAlignment;
_horLayoutReversed = _horizontalLayout.reverseArrangement;
}
_verticalLayout = GetComponent<VerticalLayoutGroup>();
if (_verticalLayout)
{
_verLayoutAlignment = _verticalLayout.childAlignment;
}
_gridLayout = GetComponent<GridLayoutGroup>();
if (_gridLayout)
{
_gridStartCorner = _gridLayout.startCorner;
_gridChildAlignment = _gridLayout.childAlignment;
}
_fillImage = GetComponent<Image>();
if (_fillImage != null && _fillImage.type == Image.Type.Filled)
{
_fillOrigin = _fillImage.fillOrigin;
}
else
{
_fillImage = null;
}
_initialized = true;
}
/// <summary>
/// Switches this component to LtoR or RtoL.
/// </summary>
/// <param name="useRightToLeft">Whether to use RtoL (true) or LtoR (false)</param>
private void SetRightToLeft(bool useRightToLeft)
{
CheckInitialize();
#if ULTIMATEXR_UNITY_TMPRO
if (_textTMPro != null)
{
_textTMPro.alignment = useRightToLeft ? GetRtoLAlignmentTMPro(_alignmentTMPro) : _alignmentTMPro;
}
#endif
if (_text != null)
{
_text.alignment = useRightToLeft ? GetRtoLAlignment(_textAlignment) : _textAlignment;
}
if (_horizontalLayout != null)
{
_horizontalLayout.childAlignment = useRightToLeft ? GetRtoLAlignment(_horLayoutAlignment) : _horLayoutAlignment;
_horizontalLayout.reverseArrangement = useRightToLeft ? !_horLayoutReversed : _horLayoutReversed;
}
if (_verticalLayout != null)
{
_verticalLayout.childAlignment = useRightToLeft ? GetRtoLAlignment(_verLayoutAlignment) : _verLayoutAlignment;
}
if (_gridLayout != null)
{
_gridLayout.startCorner = useRightToLeft ? GetRtoLCorner(_gridStartCorner) : _gridStartCorner;
_gridLayout.childAlignment = useRightToLeft ? GetRtoLAlignment(_gridChildAlignment) : _gridChildAlignment;
}
if (_fillImage != null)
{
_fillImage.fillOrigin = useRightToLeft ? GetRtoLFillOrigin(_fillImage.fillMethod, _fillOrigin) : _fillOrigin;
}
}
#endregion
#region Private Types & Data
private static bool s_useRightToLeft;
private bool _initialized;
private Text _text;
private TextAnchor _textAlignment;
private HorizontalLayoutGroup _horizontalLayout;
private TextAnchor _horLayoutAlignment;
private bool _horLayoutReversed;
private VerticalLayoutGroup _verticalLayout;
private TextAnchor _verLayoutAlignment;
private GridLayoutGroup _gridLayout;
private GridLayoutGroup.Corner _gridStartCorner;
private TextAnchor _gridChildAlignment;
private Image _fillImage;
private int _fillOrigin;
#if ULTIMATEXR_UNITY_TMPRO
private TextMeshProUGUI _textTMPro;
private TextAlignmentOptions _alignmentTMPro;
#endif
#endregion
}
}

View File

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

View File

@@ -0,0 +1,227 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrCanvas.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Extensions.Unity;
using UnityEngine;
using UnityEngine.UI;
namespace UltimateXR.UI.UnityInputModule
{
/// <summary>
/// Component that, added to a <see cref="GameObject" /> with a <see cref="Canvas" /> component, enables interaction
/// using <see cref="UxrFingerTip" /> components or <see cref="UxrLaserPointer" /> components.
/// </summary>
public class UxrCanvas : UxrComponent<UxrCanvas>
{
#region Inspector Properties/Serialized Fields
[SerializeField] protected UxrInteractionType _interactionType;
[SerializeField] protected float _fingerTipMinHoverDistance = UxrFingerTipRaycaster.FingerTipMinHoverDistanceDefault;
[SerializeField] protected bool _autoEnableLaserPointer;
[SerializeField] protected float _autoEnableDistance = 5.0f;
[SerializeField] protected bool _allowLeftHand = true;
[SerializeField] protected bool _allowRightHand = true;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the Unity <see cref="Canvas" /> component.
/// </summary>
public Canvas UnityCanvas => GetCachedComponent<Canvas>();
/// <summary>
/// Gets or sets whether the <see cref="UxrLaserPointer" /> components will automatically show their laser while
/// pointing towards the canvas.
/// </summary>
public bool AutoEnableLaserPointer
{
get => _autoEnableLaserPointer;
set => _autoEnableLaserPointer = value;
}
/// <summary>
/// Gets or sets the distance below which the <see cref="UxrLaserPointer" /> will automatically show the laser while
/// pointing towards the canvas.
/// </summary>
public float AutoEnableDistance
{
get => _autoEnableDistance;
set => _autoEnableDistance = value;
}
/// <summary>
/// Gets or sets the type of interaction with the UI components in the canvas.
/// </summary>
public UxrInteractionType CanvasInteractionType
{
get => _interactionType;
set
{
_interactionType = value;
if (_oldRaycaster != null)
{
DestroyVRCanvas();
CreateVRCanvas();
}
}
}
/// <summary>
/// Gets or sets the distance below which a <see cref="UxrFingerTip" /> component will generate hovering events.
/// </summary>
public float FingerTipMinHoverDistance
{
get => _fingerTipMinHoverDistance;
set => _fingerTipMinHoverDistance = value;
}
#endregion
#region Public Methods
/// <summary>
/// Checks if the canvas can be used with the given hand. This allows some canvases to work for the left or
/// right hand only.
/// </summary>
/// <param name="handSide">Which hand to check</param>
/// <returns>Boolean telling whether the given hand is compatible or not</returns>
public bool IsCompatible(UxrHandSide handSide)
{
return (handSide == UxrHandSide.Left && _allowLeftHand) || (handSide == UxrHandSide.Right && _allowRightHand);
}
/// <summary>
/// Sets up the canvas so that it can be used with <see cref="UxrPointerInputModule" />.
/// </summary>
/// <param name="inputModule">The input module</param>
public void SetupCanvas(UxrPointerInputModule inputModule)
{
CanvasInteractionType = inputModule.InteractionTypeOnAutoEnable;
if (_newRaycasterFingerTips != null)
{
_newRaycasterFingerTips.FingerTipMinHoverDistance = inputModule.FingerTipMinHoverDistance;
}
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Start()
{
base.Start();
if (UxrPointerInputModule.Instance && UxrPointerInputModule.Instance.AutoAssignEventCamera && UnityCanvas && UxrAvatar.LocalAvatar)
{
UnityCanvas.worldCamera = UxrAvatar.LocalAvatar.CameraComponent;
}
if (_newRaycasterFingerTips == null && _newRaycasterLaserPointer == null)
{
CreateVRCanvas();
}
}
#endregion
#region Private Methods
/// <summary>
/// Creates the raycaster required to use the <see cref="UxrCanvas" /> component with
/// <see cref="UxrPointerInputModule" />.
/// </summary>
private void CreateVRCanvas()
{
if (UnityCanvas == null)
{
return;
}
if (_oldRaycaster == null)
{
_oldRaycaster = UnityCanvas.gameObject.GetComponent<GraphicRaycaster>();
}
if (_interactionType == UxrInteractionType.FingerTips)
{
_newRaycasterFingerTips = GetOrAddRaycaster<UxrFingerTipRaycaster>(_oldRaycaster);
_newRaycasterFingerTips.FingerTipMinHoverDistance = _fingerTipMinHoverDistance;
}
else if (_interactionType == UxrInteractionType.LaserPointers)
{
_newRaycasterLaserPointer = GetOrAddRaycaster<UxrLaserPointerRaycaster>(_oldRaycaster);
}
}
/// <summary>
/// Sets up the new raycaster.
/// </summary>
/// <param name="oldRaycaster"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
private T GetOrAddRaycaster<T>(GraphicRaycaster oldRaycaster) where T : GraphicRaycaster
{
bool copyParameters = UnityCanvas.GetComponent<T>() == null;
T rayCaster = UnityCanvas.GetOrAddComponent<T>();
rayCaster.enabled = true;
if (oldRaycaster && rayCaster)
{
if (copyParameters)
{
rayCaster.ignoreReversedGraphics = oldRaycaster.ignoreReversedGraphics;
rayCaster.blockingObjects = GraphicRaycaster.BlockingObjects.All;
rayCaster.blockingMask = oldRaycaster.blockingMask;
}
oldRaycaster.enabled = false;
}
return rayCaster;
}
/// <summary>
/// Destroys the new raycaster and restores the old one.
/// </summary>
private void DestroyVRCanvas()
{
if (_newRaycasterFingerTips)
{
Destroy(_newRaycasterFingerTips);
}
if (_newRaycasterLaserPointer)
{
Destroy(_newRaycasterLaserPointer);
}
if (_oldRaycaster && _oldRaycaster.enabled == false)
{
_oldRaycaster.enabled = true;
}
}
#endregion
#region Private Types & Data
private GraphicRaycaster _oldRaycaster;
private UxrFingerTipRaycaster _newRaycasterFingerTips;
private UxrLaserPointerRaycaster _newRaycasterLaserPointer;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 07521b0732d327543ad102e617a223c6
timeCreated: 1499253107
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,205 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrFingerTipRaycaster.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace UltimateXR.UI.UnityInputModule
{
/// <summary>
/// Raycaster compatible with Unity UI to use <see cref="UxrFingerTip" /> components on canvases, enabling touch
/// interaction.
/// </summary>
[RequireComponent(typeof(Canvas))]
public class UxrFingerTipRaycaster : UxrGraphicRaycaster
{
#region Inspector Properties/Serialized Fields
[SerializeField] private float _fingerTipMinHoverDistance = FingerTipMinHoverDistanceDefault;
[SerializeField] private float _fingerTipMaxAllowedAngle = FingerTipMaxAllowedTipAngle;
#endregion
#region Public Types & Data
/// <summary>
/// Default maximum distance a <see cref="UxrFingerTip" /> can have to a canvas in order to start generating hovering
/// events.
/// </summary>
public const float FingerTipMinHoverDistanceDefault = 0.05f;
/// <summary>
/// Default maximum angle between the finger and the canvas for a <see cref="UxrFingerTip" /> component to generate
/// input events.
/// </summary>
public const float FingerTipMaxAllowedTipAngle = 65.0f;
/// <summary>
/// Gets or sets the maximum distance a <see cref="UxrFingerTip" /> can have to a canvas in order to generate hovering
/// events.
/// </summary>
public float FingerTipMinHoverDistance
{
get => _fingerTipMinHoverDistance;
set => _fingerTipMinHoverDistance = value;
}
#endregion
#region Public Overrides GraphicRaycaster
/// <inheritdoc />
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
{
// Check if it should be ray-casted
UxrPointerEventData pointerEventData = eventData as UxrPointerEventData;
if (pointerEventData == null || pointerEventData.FingerTip == null)
{
return;
}
// Initialize if necessary
if (_canvas == null)
{
_canvas = gameObject.GetComponent<Canvas>();
_canvasGroup = gameObject.GetComponent<CanvasGroup>();
if (_canvas == null)
{
return;
}
}
if (_canvasGroup != null && _canvasGroup.interactable == false)
{
return;
}
// Raycast against the canvas, gather all results and append to the list
_raycastResults.Clear();
// First check finger angle. This helps avoiding unwanted clicks.
if (Vector3.Angle(pointerEventData.FingerTip.WorldDir, _canvas.transform.forward) > _fingerTipMaxAllowedAngle)
{
return;
}
// Raycast
var ray = new Ray(pointerEventData.FingerTip.WorldPos, pointerEventData.FingerTip.WorldDir);
Raycast(_canvas, eventCamera, ray, ref _raycastResults, ref resultAppendList);
// Assign correct indices and get closest raycast
RaycastResult? raycastNearest = null;
for (int i = 0; i < _raycastResults.Count; ++i)
{
RaycastResult rayCastResult = _raycastResults[i];
rayCastResult.index = resultAppendList.Count + i;
if (!raycastNearest.HasValue || rayCastResult.distance < raycastNearest.Value.distance)
{
raycastNearest = rayCastResult;
}
}
// If a closest raycast was found, use it to compute the event delta
if (raycastNearest.HasValue)
{
eventData.position = raycastNearest.Value.screenPosition;
eventData.delta = eventData.position - _lastPosition;
_lastPosition = eventData.position;
eventData.pointerCurrentRaycast = raycastNearest.Value;
}
}
#endregion
#region Private Methods
/// <summary>
/// Performs a raycast to check which elements in a canvas were potentially interacted with.
/// </summary>
/// <param name="canvas">Canvas to check</param>
/// <param name="cam">Camera from which to perform the checks</param>
/// <param name="ray">Ray to use for intersection detection</param>
/// <param name="results">Returns the list of results sorted by increasing distance</param>
/// <param name="resultAppendList">Returns a list where the results have been appended to the existing content</param>
private void Raycast(Canvas canvas, Camera cam, Ray ray, ref List<RaycastResult> results, ref List<RaycastResult> resultAppendList)
{
if (cam == null)
{
return;
}
// Iterate over all canvas graphics
IList<Graphic> listGraphics = GraphicRegistry.GetGraphicsForCanvas(canvas);
for (int i = 0; i < listGraphics.Count; ++i)
{
if (listGraphics[i].depth == -1 || !listGraphics[i].raycastTarget)
{
continue;
}
float distance = Vector3.Dot(listGraphics[i].transform.forward, listGraphics[i].transform.position - ray.origin) / Vector3.Dot(listGraphics[i].transform.forward, ray.direction);
Vector3 position = ray.GetPoint(distance);
Vector2 pointerPosition = cam.WorldToScreenPoint(position);
if (distance > _fingerTipMinHoverDistance)
{
continue;
}
if (!RectTransformUtility.RectangleContainsScreenPoint(listGraphics[i].rectTransform, pointerPosition, cam))
{
continue;
}
if (listGraphics[i].Raycast(pointerPosition, cam))
{
var result = new RaycastResult
{
gameObject = listGraphics[i].gameObject,
module = this,
distance = distance,
screenPosition = pointerPosition,
worldPosition = position,
depth = listGraphics[i].depth,
sortingLayer = canvas.sortingLayerID,
sortingOrder = canvas.sortingOrder,
};
results.Add(result);
}
}
results.Sort((g1, g2) => g2.depth.CompareTo(g1.depth));
resultAppendList.AddRange(results);
}
#endregion
#region Private Types & Data
private Canvas _canvas;
private CanvasGroup _canvasGroup;
private List<RaycastResult> _raycastResults = new List<RaycastResult>();
private Vector2 _lastPosition;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 43772a8e9057f664b8d08fdebfb7a0f6
timeCreated: 1499253130
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrGraphicRaycaster.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace UltimateXR.UI.UnityInputModule
{
/// <summary>
/// Base Unity UI Graphic raycaster class.
/// </summary>
public abstract class UxrGraphicRaycaster : GraphicRaycaster
{
#region Public Methods
/// <summary>
/// Checks whether the raycast result describes a collision against a 2D or 3D object.
/// </summary>
/// <param name="result">Raycast result</param>
/// <returns>Whether collision is against a 2D or 3D object</returns>
public static bool Is2DOr3DRaycastResult(RaycastResult result)
{
return result.depth == UxrConstants.UI.Depth2DObject || result.depth == UxrConstants.UI.Depth3DObject;
}
/// <summary>
/// Compares two raycast results.
/// </summary>
/// <param name="a">Raycast result 1</param>
/// <param name="b">Raycast result 2</param>
/// <returns>Less than 0 if a is closer than b, 0 if they are at the same distance and greater than 0 if b is closer than a</returns>
public static int CompareDepth(RaycastResult a, RaycastResult b)
{
if (Is2DOr3DRaycastResult(a) || Is2DOr3DRaycastResult(b))
{
return a.distance.CompareTo(b.distance);
}
return a.depth.CompareTo(b.depth);
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,18 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrIgnoreCanvas.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
namespace UltimateXR.UI.UnityInputModule
{
/// <summary>
/// Component that, added to a <see cref="GameObject" /> with a Unity <see cref="Canvas" /> component, will ignore it
/// when the <see cref="UxrPointerInputModule" /> is set to automatically add <see cref="UxrCanvas" /> components that
/// enable user interaction with UltimateXR. See <see cref="UxrPointerInputModule.AutoEnableOnWorldCanvases" />.
/// </summary>
public class UxrIgnoreCanvas : UxrComponent
{
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 79994de9d5ee8844e811a98308b8a140
timeCreated: 1499253107
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,26 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrInteractionType.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Avatar;
namespace UltimateXR.UI.UnityInputModule
{
/// <summary>
/// Enumerates the types of interaction supported.
/// </summary>
public enum UxrInteractionType
{
/// <summary>
/// Interaction using <see cref="UxrFingerTip" /> components attached to the finger tips of an <see cref="UxrAvatar" />
/// . Enables touch interaction.
/// </summary>
FingerTips,
/// <summary>
/// Interaction using <see cref="UxrLaserPointer" /> components from a distance.
/// </summary>
LaserPointers
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 28efa05660db476d815944c84d9aec0a
timeCreated: 1648295346

View File

@@ -0,0 +1,249 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrLaserPointerRaycaster.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UltimateXR.Core;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace UltimateXR.UI.UnityInputModule
{
/// <summary>
/// Raycaster compatible with Unity UI to use <see cref="UxrLaserPointer" /> components on canvases, enabling
/// interaction using laser pointers from a distance.
/// </summary>
[RequireComponent(typeof(Canvas))]
public class UxrLaserPointerRaycaster : UxrGraphicRaycaster
{
#region Public Overrides GraphicRaycaster
/// <inheritdoc />
public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
{
// Check if it should be ray-casted
UxrPointerEventData pointerEventData = eventData as UxrPointerEventData;
if (pointerEventData == null || pointerEventData.LaserPointer == null)
{
return;
}
// Initialize if necessary
if (_canvas == null)
{
_canvas = gameObject.GetComponent<Canvas>();
_canvasGroup = gameObject.GetComponent<CanvasGroup>();
if (_canvas == null)
{
return;
}
}
if (_canvasGroup != null && _canvasGroup.interactable == false)
{
return;
}
// Raycast against the canvas, gather all results and append to the list
_raycastResults.Clear();
var ray = new Ray(pointerEventData.LaserPointer.LaserPos, pointerEventData.LaserPointer.LaserDir);
Raycast(_canvas, pointerEventData.LaserPointer, eventCamera, ray, ref _raycastResults, ref resultAppendList, out float _);
// Assign correct indices and get closest raycast
RaycastResult? raycastNearest = null;
for (int i = 0; i < _raycastResults.Count; ++i)
{
RaycastResult rayCastResult = _raycastResults[i];
rayCastResult.index = resultAppendList.Count + i;
if (!raycastNearest.HasValue || rayCastResult.distance < raycastNearest.Value.distance)
{
raycastNearest = rayCastResult;
}
}
// If a closest raycast was found, use it to compute the event delta
if (raycastNearest.HasValue)
{
eventData.position = raycastNearest.Value.screenPosition;
eventData.delta = eventData.position - _lastPosition;
_lastPosition = eventData.position;
eventData.pointerCurrentRaycast = raycastNearest.Value;
}
}
#endregion
#region Private Methods
/// <summary>
/// Performs a raycast to check which elements in a canvas were potentially interacted with.
/// </summary>
/// <param name="canvas">Canvas to check</param>
/// <param name="laserPointer">Source laser pointer or null if it could not be retrieved</param>
/// <param name="cam">Camera from which to perform the checks</param>
/// <param name="ray">Ray to use for intersection detection</param>
/// <param name="results">Returns the list of results sorted by increasing distance</param>
/// <param name="resultAppendList">Returns a list where the results have been appended to the existing content</param>
/// <param name="occluderDistance">Returns the occluder distance</param>
private void Raycast(Canvas canvas, UxrLaserPointer laserPointer, Camera cam, Ray ray, ref List<RaycastResult> results, ref List<RaycastResult> resultAppendList, out float occluderDistance)
{
occluderDistance = -1.0f;
if (cam == null)
{
return;
}
float hitDistance = float.MaxValue;
GameObject hitObject = null;
Vector3 hitPosition = Vector3.zero;
int hitDepth = 0;
// Check for objects in the path. Set hitDistance to the ray length to the closest hit.
if (canvas.renderMode != RenderMode.ScreenSpaceOverlay && blockingObjects != BlockingObjects.None)
{
RectTransform rectTransformCanvas = canvas.GetComponent<RectTransform>();
float maxDistance = Vector3.Distance(ray.origin, canvas.transform.position) +
Mathf.Max(rectTransformCanvas.localScale.x, rectTransformCanvas.localScale.y, rectTransformCanvas.localScale.z) *
Mathf.Max(rectTransformCanvas.rect.width, rectTransformCanvas.rect.height);
bool blocking3D = laserPointer != null ? laserPointer.TargetTypes.HasFlag(UxrLaserPointerTargetTypes.Colliders3D) : blockingObjects.HasFlag(BlockingObjects.ThreeD);
if (blocking3D)
{
if (Physics.Raycast(ray,
out RaycastHit hit,
maxDistance,
laserPointer != null ? laserPointer.BlockingMask : m_BlockingMask,
laserPointer != null ? laserPointer.TriggerCollidersInteraction : QueryTriggerInteraction.Ignore))
{
hitObject = hit.collider.gameObject;
hitPosition = hit.point;
hitDepth = UxrConstants.UI.Depth3DObject;
hitDistance = hit.distance;
occluderDistance = hitDistance;
}
}
bool blocking2D = laserPointer != null ? laserPointer.TargetTypes.HasFlag(UxrLaserPointerTargetTypes.Colliders2D) : blockingObjects.HasFlag(BlockingObjects.TwoD);
if (blocking2D)
{
RaycastHit2D hit = Physics2D.Raycast(ray.origin, ray.direction, maxDistance, laserPointer != null ? laserPointer.BlockingMask : m_BlockingMask);
if (hit.collider != null && hit.distance < hitDistance)
{
hitObject = hit.collider.gameObject;
hitPosition = hit.point;
hitDepth = UxrConstants.UI.Depth2DObject;
hitDistance = hit.fraction * maxDistance;
occluderDistance = hitDistance;
}
}
}
if (hitObject != null)
{
var result = new RaycastResult
{
gameObject = hitObject,
module = this,
distance = hitDistance,
screenPosition = cam.WorldToScreenPoint(hitPosition),
worldPosition = hitPosition,
depth = hitDepth,
sortingLayer = 0,
sortingOrder = 0,
};
results.Add(result);
}
// Iterate over all canvas graphics
bool processUI = laserPointer == null || laserPointer.TargetTypes.HasFlag(UxrLaserPointerTargetTypes.UI);
if (processUI)
{
IList<Graphic> listGraphics = GraphicRegistry.GetGraphicsForCanvas(canvas);
for (int i = 0; i < listGraphics.Count; i++)
{
Graphic graphic = listGraphics[i];
if (graphic.depth == -1 || !graphic.raycastTarget)
{
continue;
}
float distance = Vector3.Dot(graphic.transform.forward, graphic.transform.position - ray.origin) / Vector3.Dot(graphic.transform.forward, ray.direction);
if (distance < 0.0f)
{
continue;
}
if (distance - HitDistanceThreshold > hitDistance)
{
continue;
}
Vector3 position = ray.GetPoint(distance);
Vector2 pointerPosition = cam.WorldToScreenPoint(position);
if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, cam))
{
continue;
}
if (graphic.Raycast(pointerPosition, cam))
{
var result = new RaycastResult
{
gameObject = graphic.gameObject,
module = this,
distance = distance,
screenPosition = pointerPosition,
worldPosition = position,
depth = graphic.depth,
sortingLayer = canvas.sortingLayerID,
sortingOrder = canvas.sortingOrder,
};
results.Add(result);
}
}
}
results.Sort(CompareDepth);
resultAppendList.AddRange(results);
}
#endregion
#region Private Types & Data
private const float HitDistanceThreshold = 0.001f;
private Canvas _canvas;
private CanvasGroup _canvasGroup;
private List<RaycastResult> _raycastResults = new List<RaycastResult>();
private Vector2 _lastPosition;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 8e1f143194d28eb40a9b20bbce7be961
timeCreated: 1499253130
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,44 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrNonDrawingGraphic.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.UI.UnityInputModule.Controls;
using UnityEngine;
using UnityEngine.UI;
namespace UltimateXR.UI.UnityInputModule
{
/// <summary>
/// Graphic component that can be used together with <see cref="UxrControlInput" /> on a UI element that has no
/// <see cref="Graphic" /> attached. It is useful to handle input on controls that need to graphic rendering, in order
/// to save performance.
/// </summary>
[RequireComponent(typeof(CanvasRenderer))]
public class UxrNonDrawingGraphic : Graphic
{
#region Public Overrides Graphic
/// <inheritdoc />
public override void SetMaterialDirty()
{
}
/// <inheritdoc />
public override void SetVerticesDirty()
{
}
#endregion
#region Event Trigger Methods
/// <inheritdoc />
protected override void OnPopulateMesh(VertexHelper vh)
{
vh.Clear();
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 18e00cfb3078b67429a699920e0ff930
timeCreated: 1471359907
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,137 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrPointerEventData.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Avatar;
using UltimateXR.Core;
using UnityEngine;
using UnityEngine.EventSystems;
namespace UltimateXR.UI.UnityInputModule
{
/// <summary>
/// Event data class that adds information required by <see cref="UxrPointerInputModule" /> to facilitate the
/// processing of UI interaction events.
/// </summary>
public class UxrPointerEventData : PointerEventData
{
#region Public Types & Data
/// <summary>
/// Gets whether the event data contains valid information.
/// </summary>
public bool HasData => pointerCurrentRaycast.isValid || IgnoredGameObject != null || IsNonUI;
/// <summary>
/// Gets the <see cref="UxrAvatar" /> responsible for the interaction.
/// </summary>
public UxrAvatar Avatar { get; }
/// <summary>
/// Gets the hand responsible for the interaction.
/// </summary>
public UxrHandSide HandSide { get; }
/// <summary>
/// Gets the finger tip if this event is being processed by one. Null if not.
/// </summary>
public UxrFingerTip FingerTip { get; }
/// <summary>
/// Gets the laser pointer if this event is being processed by one. Null if not.
/// </summary>
public UxrLaserPointer LaserPointer { get; }
/// <summary>
/// Gets the current pointer world position.
/// </summary>
public Vector3 WorldPos { get; internal set; }
/// <summary>
/// Gets the pointer world position during the last frame.
/// </summary>
public Vector3 PreviousWorldPos { get; internal set; }
/// <summary>
/// Gets whether the world position has been initialized.
/// </summary>
public bool WorldPosInitialized { get; internal set; }
/// <summary>
/// Gets whether the pointer is pressing this frame.
/// </summary>
public bool PressedThisFrame { get; internal set; }
/// <summary>
/// Gets whether the pointer is pressing this frame.
/// </summary>
public bool ReleasedThisFrame { get; internal set; }
/// <summary>
/// Gets whether the current raycast UI element is interactive.
/// </summary>
public bool IsInteractive { get; internal set; }
/// <summary>
/// Gets whether the current raycast UI element is not a UI GameObject.
/// This happens when the raycast is valid and the object has either a 2D or 3D collider.
/// </summary>
public bool IsNonUI => GameObject2D != null || GameObject3D != null;
/// <summary>
/// Gets the UI gameObject that was ignored because it could not be interacted with.
/// </summary>
public GameObject IgnoredGameObject { get; internal set; }
/// <summary>
/// Gets the gameObject if the raycast element is an object with a 3D collider.
/// </summary>
public GameObject GameObject3D { get; internal set; }
/// <summary>
/// Gets the gameObject if the raycast element is an object with a 2D collider.
/// </summary>
public GameObject GameObject2D { get; internal set; }
/// <summary>
/// Gets the <see cref="GameObject" /> that was clicked, if there was one.
/// </summary>
public GameObject GameObjectClicked { get; internal set; }
/// <summary>
/// Gets the current cursor speed.
/// </summary>
public float Speed { get; internal set; }
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="eventSystem">Event system</param>
/// <param name="fingerTip">Finger tip responsible for the event</param>
public UxrPointerEventData(EventSystem eventSystem, UxrFingerTip fingerTip) : base(eventSystem)
{
Avatar = fingerTip.Avatar;
HandSide = fingerTip.Side;
FingerTip = fingerTip;
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="eventSystem">Event system</param>
/// <param name="laserPointer">Laser pointer responsible for the event</param>
public UxrPointerEventData(EventSystem eventSystem, UxrLaserPointer laserPointer) : base(eventSystem)
{
Avatar = laserPointer.Avatar;
HandSide = laserPointer.HandSide;
LaserPointer = laserPointer;
}
#endregion
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 217d6da9f2110c642952a97bcb13abd4
timeCreated: 1499253133
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: