Skip to content

Commit

Permalink
Introduce Interactable reset API and behaviour using it (#144)
Browse files Browse the repository at this point in the history
* Define API to reset an interactable

* Implement AutoResetBehaviour based on new API

* Docs
  • Loading branch information
FejZa authored Jul 25, 2024
1 parent f032e67 commit f985d45
Show file tree
Hide file tree
Showing 27 changed files with 319 additions and 20 deletions.
33 changes: 33 additions & 0 deletions Editor/Inspectors/AutoResetBehaviourInspector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) Reality Collective. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using RealityToolkit.Input.InteractionBehaviours;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;

namespace RealityToolkit.Editor.Inspectors
{
[CustomEditor(typeof(AutoResetBehaviour), true)]
public class AutoResetBehaviourInspector : BaseInteractionBehaviourInspector
{
private const string resetDelay = "resetDelay";
private const string resetPose = "resetPose";

/// <summary>
/// The <see cref="AutoResetBehaviour"/> does not care what the interactor handedness
/// is and thus we can hide this setting from the user.
/// </summary>
protected override bool ShowHandedness => false;

public override VisualElement CreateInspectorGUI()
{
var inspector = base.CreateInspectorGUI();

inspector.Add(new PropertyField(serializedObject.FindProperty(resetDelay)));
inspector.Add(new PropertyField(serializedObject.FindProperty(resetPose)));

return inspector;
}
}
}
11 changes: 11 additions & 0 deletions Editor/Inspectors/AutoResetBehaviourInspector.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 12 additions & 1 deletion Editor/Inspectors/BaseInteractionBehaviourInspector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ public abstract class BaseInteractionBehaviourInspector : UnityEditor.Editor
private const string sortingOrderBindingPath = "sortingOrder";
private const string targetHandednessBindingPath = "targetHandedness";

/// <summary>
/// Used internally by some <see cref="BaseInteractionBehaviourInspector"/>
/// implementations to hide the handedness field from the inspector, likely because that
/// behaviour works without that setting impacting it.
/// </summary>
protected virtual bool ShowHandedness => true;

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand All @@ -19,7 +26,11 @@ public override VisualElement CreateInspectorGUI()
var inspector = new VisualElement();

inspector.Add(new PropertyField(serializedObject.FindProperty(sortingOrderBindingPath)));
inspector.Add(new PropertyField(serializedObject.FindProperty(targetHandednessBindingPath)));

if (ShowHandedness)
{
inspector.Add(new PropertyField(serializedObject.FindProperty(targetHandednessBindingPath)));
}

return inspector;
}
Expand Down
48 changes: 48 additions & 0 deletions Editor/Inspectors/InteractionEventsBehaviourInspector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Reality Collective. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using RealityToolkit.Editor.Utilities;
using RealityToolkit.Input.InteractionBehaviours;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;

namespace RealityToolkit.Editor.Inspectors
{
[CustomEditor(typeof(InteractionEventsBehaviour), true)]
public class InteractionEventsBehaviourInspector : BaseInteractionBehaviourInspector
{
/// <summary>
/// The events behaviour does not need to display handedness as it will and should
/// forward events for any handedness and leave it to the user to decide what to do with it.
/// </summary>
protected override bool ShowHandedness => false;

public override VisualElement CreateInspectorGUI()
{
var inspector = base.CreateInspectorGUI();

inspector.Add(new PropertyField(serializedObject.FindProperty("firstFocusEntered")));
inspector.Add(new PropertyField(serializedObject.FindProperty("focusEntered")));
inspector.Add(new PropertyField(serializedObject.FindProperty("focusExited")));
inspector.Add(new PropertyField(serializedObject.FindProperty("lastFocusExited")));

inspector.Add(UIElementsUtilities.VerticalSpace());
inspector.Add(new PropertyField(serializedObject.FindProperty("firstSelectEntered")));
inspector.Add(new PropertyField(serializedObject.FindProperty("selectEntered")));
inspector.Add(new PropertyField(serializedObject.FindProperty("selectExited")));
inspector.Add(new PropertyField(serializedObject.FindProperty("lastSelectExited")));

inspector.Add(UIElementsUtilities.VerticalSpace());
inspector.Add(new PropertyField(serializedObject.FindProperty("firstGrabEntered")));
inspector.Add(new PropertyField(serializedObject.FindProperty("grabEntered")));
inspector.Add(new PropertyField(serializedObject.FindProperty("grabExited")));
inspector.Add(new PropertyField(serializedObject.FindProperty("lastGrabExited")));

inspector.Add(UIElementsUtilities.VerticalSpace());
inspector.Add(new PropertyField(serializedObject.FindProperty("reset")));

return inspector;
}
}
}
11 changes: 11 additions & 0 deletions Editor/Inspectors/InteractionEventsBehaviourInspector.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Runtime/Input/Interactables/IInteractable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,11 @@ public interface IInteractable
/// </summary>
/// <param name="behaviour">The <see cref="IInteractionBehaviour"/>.</param>
void Remove(IInteractionBehaviour behaviour);

/// <summary>
/// Resets the <see cref="IInteractable"/> and notifies any attached <see cref="IInteractionBehaviour"/>s
/// about it using the <see cref="IInteractionBehaviour.OnInteractableReset"/> hook.
/// </summary>
void ResetInteractable();
}
}
9 changes: 9 additions & 0 deletions Runtime/Input/Interactables/Interactable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,15 @@ public void Add(IInteractionBehaviour behaviour)
/// <inheritdoc/>
public void Remove(IInteractionBehaviour behaviour) => behaviours.SafeRemoveListItem(behaviour);

/// <inheritdoc/>
public void ResetInteractable()
{
for (var i = 0; i < behaviours.Count; i++)
{
behaviours[i].OnInteractableReset();
}
}

private bool IsValidInteractor(IInteractor interactor) =>
(interactor.IsFarInteractor && FarInteractionEnabled) || (!interactor.IsFarInteractor && DirectInteractionEnabled);

Expand Down
135 changes: 135 additions & 0 deletions Runtime/Input/InteractionBehaviours/AutoResetBehaviour.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright (c) Reality Collective. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using RealityToolkit.Input.Events;
using UnityEngine;

namespace RealityToolkit.Input.InteractionBehaviours
{
/// <summary>
/// This <see cref="IInteractionBehaviour"/> will monitor interactions and once ALL interaction on the interactable has ended it will execute a reset
/// using the <see cref="Interactables.IInteractable.ResetInteractable"/> API.
/// </summary>
[HelpURL(RealityToolkitRuntimePreferences.Toolkit_Docs_BaseUrl + "docs/interactions/interaction-behaviours/default-behaviours/auto-reset-behaviour")]
[AddComponentMenu(RealityToolkitRuntimePreferences.Toolkit_InteractionsAddComponentMenu + "/" + nameof(AutoResetBehaviour))]
public class AutoResetBehaviour : BaseInteractionBehaviour
{
[SerializeField, Tooltip("The delay in seconds after interaction has stopped before the reset will fire.")]
private float resetDelay = 5f;

[SerializeField, Tooltip("If set, the interactable will be reset to its initial pose upon reset.")]
private bool resetPose = true;

private bool didReset;
private float interactionStoppedTime;
private Pose initialPose;

/// <inheritdoc/>
protected override void Awake()
{
initialPose = new Pose(transform.position, transform.rotation);
base.Awake();
}

/// <inheritdoc/>
protected override void OnEnable()
{
didReset = true;
base.OnEnable();
}

/// <inheritdoc/>
protected override void Update()
{
base.Update();

if (didReset)
{
return;
}

var timePassed = Time.time - interactionStoppedTime;
if (timePassed > resetDelay)
{
didReset = true;
Interactable.ResetInteractable();
}
}

/// <inheritdoc/>
protected override void OnDisable()
{
didReset = true;
base.OnDisable();
}

/// <inheritdoc/>
protected override void OnFocusEntered(InteractionEventArgs eventArgs) => OnInteractionDetected();

/// <inheritdoc/>
protected override void OnLastFocusExited(InteractionExitEventArgs eventArgs)
{
if (Interactable.IsSelected || Interactable.IsGrabbed)
{
return;
}

OnInteractionEnded();
}

/// <inheritdoc/>
protected override void OnSelectEntered(InteractionEventArgs eventArgs) => OnInteractionDetected();

/// <inheritdoc/>
protected override void OnLastSelectExited(InteractionExitEventArgs eventArgs)
{
if (Interactable.IsFocused || Interactable.IsGrabbed)
{
return;
}

OnInteractionEnded();
}

/// <inheritdoc/>
protected override void OnGrabEntered(InteractionEventArgs eventArgs) => OnInteractionDetected();

/// <inheritdoc/>
protected override void OnLastGrabExited(InteractionExitEventArgs eventArgs)
{
if (Interactable.IsSelected || Interactable.IsFocused)
{
return;
}

OnInteractionEnded();
}

/// <inheritdoc/>
protected override void OnResetBehaviour()
{
didReset = true;

if (resetPose)
{
ResetPose();
}
}

private void OnInteractionDetected()
{
didReset = true;
}

private void OnInteractionEnded()
{
interactionStoppedTime = Time.time;
didReset = false;
}

private void ResetPose()
{
transform.SetPositionAndRotation(initialPose.position, initialPose.rotation);
}
}
}
11 changes: 11 additions & 0 deletions Runtime/Input/InteractionBehaviours/AutoResetBehaviour.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace RealityToolkit.Input.InteractionBehaviours
/// <summary>
/// Base implementation for <see cref="IInteractionBehaviour"/>s.
/// </summary>
[HelpURL("https://www.realitytoolkit.io/docs/interactions/interaction-behaviours/custom-behaviours")]
[HelpURL(RealityToolkitRuntimePreferences.Toolkit_Docs_BaseUrl + "docs/interactions/interaction-behaviours/custom-behaviours")]
[RequireComponent(typeof(Interactable))]
public abstract class BaseInteractionBehaviour : MonoBehaviour, IInteractionBehaviour
{
Expand Down Expand Up @@ -74,6 +74,12 @@ protected virtual void OnDestroy() { }
/// </summary>
protected virtual void OnValidate() { }

/// <inheritdoc/>
void IInteractionBehaviour.OnInteractableReset() => OnResetBehaviour();

/// <inheritdoc cref="IInteractionBehaviour.OnInteractableReset"/>
protected virtual void OnResetBehaviour() { }

/// <inheritdoc/>
void IInteractionBehaviour.OnFirstFocusEntered(InteractionEventArgs eventArgs)
{
Expand Down
2 changes: 1 addition & 1 deletion Runtime/Input/InteractionBehaviours/ButtonBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace RealityToolkit.Input.InteractionBehaviours
/// <summary>
/// A <see cref="IInteractionBehaviour"/> for creating <see cref="Interactables.IInteractable"/>s that mimick button behaviour.
/// </summary>
[HelpURL("https://www.realitytoolkit.io/docs/interactions/interaction-behaviours/default-behaviours/button-behaviour")]
[HelpURL(RealityToolkitRuntimePreferences.Toolkit_Docs_BaseUrl + "docs/interactions/interaction-behaviours/default-behaviours/button-behaviour")]
[AddComponentMenu(RealityToolkitRuntimePreferences.Toolkit_InteractionsAddComponentMenu + "/" + nameof(ButtonBehaviour))]
public class ButtonBehaviour : BaseInteractionBehaviour
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace RealityToolkit.Input.InteractionBehaviours
/// <summary>
/// This <see cref="IInteractionBehaviour"/> will change the main material used on a <see cref="MeshRenderer"/> on the <see cref="Interactables.IInteractable"/>.
/// </summary>
[HelpURL("https://www.realitytoolkit.io/docs/interactions/interaction-behaviours/default-behaviours/change-material-behaviour")]
[HelpURL(RealityToolkitRuntimePreferences.Toolkit_Docs_BaseUrl + "docs/interactions/interaction-behaviours/default-behaviours/change-material-behaviour")]
[AddComponentMenu(RealityToolkitRuntimePreferences.Toolkit_InteractionsAddComponentMenu + "/" + nameof(ChangeMaterialBehaviour))]
public class ChangeMaterialBehaviour : BaseInteractionBehaviour
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace RealityToolkit.Input.InteractionBehaviours
/// <summary>
/// The <see cref="FocusHandPoseBehaviour"/> will animate the <see cref="RiggedHandControllerVisualizer"/>
/// into the assigned <see cref="focusPose"/>, when the <see cref="Interactables.IInteractable"/> is focused.
[HelpURL("https://www.realitytoolkit.io/docs/interactions/interaction-behaviours/default-behaviours/focus-hand-pose-behaviour")]
[HelpURL(RealityToolkitRuntimePreferences.Toolkit_Docs_BaseUrl + "docs/interactions/interaction-behaviours/default-behaviours/focus-hand-pose-behaviour")]
[AddComponentMenu(RealityToolkitRuntimePreferences.Toolkit_InteractionsAddComponentMenu + "/" + nameof(FocusHandPoseBehaviour))]
public class FocusHandPoseBehaviour : BaseInteractionBehaviour, IProvideHandPose
{
Expand Down
2 changes: 1 addition & 1 deletion Runtime/Input/InteractionBehaviours/FocusLockBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace RealityToolkit.Input.InteractionBehaviours
/// This <see cref="IInteractionBehaviour"/> will focus lock <see cref="IInteractor"/>s on the <see cref="Interactables.IInteractable"/>,
/// when the <see cref="Interactables.IInteractable.IsSelected"/> or <see cref="Interactables.IInteractable.IsGrabbed"/>.
/// </summary>
[HelpURL("https://www.realitytoolkit.io/docs/interactions/interaction-behaviours/default-behaviours/focus-lock-behaviour")]
[HelpURL(RealityToolkitRuntimePreferences.Toolkit_Docs_BaseUrl + "docs/interactions/interaction-behaviours/default-behaviours/focus-lock-behaviour")]
[AddComponentMenu(RealityToolkitRuntimePreferences.Toolkit_InteractionsAddComponentMenu + "/" + nameof(FocusLockBehaviour))]
public class FocusLockBehaviour : BaseInteractionBehaviour
{
Expand Down
2 changes: 1 addition & 1 deletion Runtime/Input/InteractionBehaviours/GrabBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace RealityToolkit.Input.InteractionBehaviours
/// <see cref="IDirectInteractor"/>s. It allows to "pick up" the <see cref="Interactables.IInteractable"/>
/// and carry it around.
/// </summary>
[HelpURL("https://www.realitytoolkit.io/docs/interactions/interaction-behaviours/default-behaviours/grab-behaviour")]
[HelpURL(RealityToolkitRuntimePreferences.Toolkit_Docs_BaseUrl + "docs/interactions/interaction-behaviours/default-behaviours/grab-behaviour")]
[AddComponentMenu(RealityToolkitRuntimePreferences.Toolkit_InteractionsAddComponentMenu + "/" + nameof(GrabBehaviour))]
public class GrabBehaviour : BaseInteractionBehaviour
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace RealityToolkit.Input.InteractionBehaviours
/// <summary>
/// The <see cref="GrabHandPoseBehaviour"/> will animate the <see cref="RiggedHandControllerVisualizer"/>
/// into the assigned <see cref="grabPose"/>, when the <see cref="Interactables.IInteractable"/> is grabbed.
[HelpURL("https://www.realitytoolkit.io/docs/interactions/interaction-behaviours/default-behaviours/grab-hand-pose-behaviour")]
[HelpURL(RealityToolkitRuntimePreferences.Toolkit_Docs_BaseUrl + "docs/interactions/interaction-behaviours/default-behaviours/grab-hand-pose-behaviour")]
[AddComponentMenu(RealityToolkitRuntimePreferences.Toolkit_InteractionsAddComponentMenu + "/" + nameof(GrabHandPoseBehaviour))]
public class GrabHandPoseBehaviour : BaseInteractionBehaviour, IProvideHandPose
{
Expand Down
6 changes: 6 additions & 0 deletions Runtime/Input/InteractionBehaviours/IInteractionBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ public interface IInteractionBehaviour
/// </summary>
IInteractable Interactable { get; }

/// <summary>
/// The <see cref="IInteractable"/> was reset. Use this hook to implement any custom reset
/// behaviour your <see cref="IInteractionBehaviour"/> might need to perform when <see cref="IInteractable"/> resets.
/// </summary>
void OnInteractableReset();

#region Focus

/// <summary>
Expand Down
Loading

0 comments on commit f985d45

Please sign in to comment.