267 lines
9.1 KiB
C#
267 lines
9.1 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using Microsoft.MixedReality.Toolkit.Utilities;
|
|
using Microsoft.MixedReality.Toolkit.Utilities.Solvers;
|
|
using System.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
|
|
namespace Microsoft.MixedReality.Toolkit.UI
|
|
{
|
|
/// <summary>
|
|
/// A utility script for toggling the 'Follow Me' behavior by activating/deactivating the Radial View Solver.
|
|
/// This script also provides optional toggle at specified distance.
|
|
/// </summary>
|
|
[RequireComponent(typeof(RadialView))]
|
|
[AddComponentMenu("Scripts/MRTK/SDK/FollowMeToggle")]
|
|
public class FollowMeToggle : MonoBehaviour
|
|
{
|
|
/// <summary>
|
|
/// An enum specifying how the optional interactable should behave once the FollowMe behavior was changed.
|
|
/// </summary>
|
|
public enum FollowMeBehaviorToInteractablesToggleState
|
|
{
|
|
ToggledWhenFollowing,
|
|
UntoggledWhenFollowing,
|
|
Manual
|
|
}
|
|
|
|
/// <summary>
|
|
/// An optional object for visualizing the 'Follow Me' mode state.
|
|
/// </summary>
|
|
public GameObject VisualizationObject
|
|
{
|
|
get { return visualizationObject; }
|
|
set { visualizationObject = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
[Tooltip("An optional object for visualizing the 'Follow Me' mode state.")]
|
|
private GameObject visualizationObject = null;
|
|
|
|
/// <summary>
|
|
/// An optional Interactable to select/deselect when toggling the follow behavior.
|
|
/// </summary>
|
|
public Interactable InteractableObject
|
|
{
|
|
get { return interactableObject; }
|
|
set { interactableObject = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
[Tooltip("An optional Interactable to select/deselect when toggling the follow behavior.")]
|
|
private Interactable interactableObject = null;
|
|
|
|
/// <summary>
|
|
/// A way to indicate how should interactable react to the follow behavior state.
|
|
/// </summary>
|
|
public FollowMeBehaviorToInteractablesToggleState ButtonBehavior
|
|
{
|
|
get { return buttonBehavior; }
|
|
set { buttonBehavior = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
[Tooltip("Should following be automatically enabled when the user is further than a certain distance away?")]
|
|
private FollowMeBehaviorToInteractablesToggleState buttonBehavior = FollowMeBehaviorToInteractablesToggleState.ToggledWhenFollowing;
|
|
|
|
/// <summary>
|
|
/// Should following be automatically enabled when the user is further than a certain distance away?
|
|
/// </summary>
|
|
public bool AutoFollowAtDistance
|
|
{
|
|
get { return autoFollowAtDistance; }
|
|
set
|
|
{
|
|
autoFollowAtDistance = value;
|
|
|
|
if (!enabled || !gameObject.activeInHierarchy)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (autoFollowAtDistance)
|
|
{
|
|
if (autoFollowDistanceCheck == null)
|
|
{
|
|
autoFollowDistanceCheck = StartCoroutine(AutoFollowDistanceCheck());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (autoFollowDistanceCheck != null)
|
|
{
|
|
StopCoroutine(autoFollowDistanceCheck);
|
|
autoFollowDistanceCheck = null;
|
|
SetFollowMeBehavior(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
[Tooltip("Should following be automatically enabled when the user is further than a certain distance away?")]
|
|
private bool autoFollowAtDistance = false;
|
|
|
|
/// <summary>
|
|
/// If autoFollowAtDistance is enabled, what distance to trigger auto following at.
|
|
/// </summary>
|
|
public float AutoFollowDistance
|
|
{
|
|
get { return autoFollowDistance; }
|
|
set { autoFollowDistance = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
[Tooltip("If autoFollowAtDistance is enabled, what distance to trigger auto following at.")]
|
|
private float autoFollowDistance = 2.0f;
|
|
|
|
/// <summary>
|
|
/// Optional transform to use when using autoFollowAtDistance. If not specified the local transform is used.
|
|
/// </summary>
|
|
public Transform AutoFollowTransformTarget
|
|
{
|
|
get { return autoFollowTransformTarget; }
|
|
set
|
|
{
|
|
autoFollowTransformTarget = value;
|
|
|
|
if (autoFollowTransformTarget == null)
|
|
{
|
|
autoFollowTransformTarget = transform;
|
|
}
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
[Tooltip("Optional transform to use when using autoFollowAtDistance. If not specified the local transform is used.")]
|
|
private Transform autoFollowTransformTarget = null;
|
|
|
|
private RadialView radialView = null;
|
|
private Coroutine autoFollowDistanceCheck = null;
|
|
|
|
[SerializeField]
|
|
[Tooltip("Event that gets fired when auto follow is triggered.")]
|
|
private UnityEvent autoFollowTriggered = new UnityEvent();
|
|
/// <summary>
|
|
/// Event that gets fired when auto follow is triggered.
|
|
/// </summary>
|
|
public UnityEvent AutoFollowTriggered
|
|
{
|
|
get => autoFollowTriggered;
|
|
set => autoFollowTriggered = value;
|
|
}
|
|
|
|
|
|
#region MonoBehaviour Implementation
|
|
|
|
private void Awake()
|
|
{
|
|
radialView = GetComponent<RadialView>();
|
|
|
|
if (autoFollowTransformTarget == null)
|
|
{
|
|
autoFollowTransformTarget = transform;
|
|
}
|
|
|
|
// Begin the follow coroutine if requested at the beginning.
|
|
AutoFollowAtDistance = autoFollowAtDistance;
|
|
}
|
|
|
|
private void OnValidate()
|
|
{
|
|
// When playing make sure the coroutine starts and stops based on inspector updates.
|
|
if (Application.isPlaying)
|
|
{
|
|
AutoFollowAtDistance = autoFollowAtDistance;
|
|
}
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
// Begin the follow coroutine when enabled.
|
|
AutoFollowAtDistance = autoFollowAtDistance;
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
autoFollowDistanceCheck = null;
|
|
}
|
|
|
|
#endregion MonoBehaviour Implementation
|
|
|
|
/// <summary>
|
|
/// Toggles the current follow behavior of the solver.
|
|
/// </summary>
|
|
public void ToggleFollowMeBehavior()
|
|
{
|
|
if (radialView != null)
|
|
{
|
|
SetFollowMeBehavior(!radialView.enabled);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enables or disables the solver based on the follow parameter.
|
|
/// </summary>
|
|
/// <param name="follow">True if the solver should be active.</param>
|
|
public void SetFollowMeBehavior(bool follow)
|
|
{
|
|
if (radialView != null && radialView.enabled != follow)
|
|
{
|
|
// Toggle Radial Solver component
|
|
// You can tweak the detailed positioning behavior such as offset, lerping time, orientation type in the Inspector panel
|
|
radialView.enabled = follow;
|
|
|
|
if (visualizationObject != null)
|
|
{
|
|
visualizationObject.SetActive(follow);
|
|
}
|
|
|
|
if (interactableObject != null)
|
|
{
|
|
switch (ButtonBehavior)
|
|
{
|
|
case FollowMeBehaviorToInteractablesToggleState.ToggledWhenFollowing:
|
|
interactableObject.IsToggled = follow;
|
|
break;
|
|
case FollowMeBehaviorToInteractablesToggleState.UntoggledWhenFollowing:
|
|
interactableObject.IsToggled = !follow;
|
|
break;
|
|
case FollowMeBehaviorToInteractablesToggleState.Manual:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Coroutine which checks how far away this transform is from the user and enables the follow behavior at a specified distance.
|
|
/// </summary>
|
|
/// <returns>Coroutine enumerator.</returns>
|
|
private IEnumerator AutoFollowDistanceCheck()
|
|
{
|
|
while (true)
|
|
{
|
|
var mainCamera = CameraCache.Main;
|
|
|
|
if (mainCamera != null)
|
|
{
|
|
float autoFollowDistanceSq = autoFollowDistance * autoFollowDistance;
|
|
|
|
if (autoFollowTransformTarget != null)
|
|
{
|
|
if ((mainCamera.transform.position - autoFollowTransformTarget.position).sqrMagnitude >= autoFollowDistanceSq)
|
|
{
|
|
SetFollowMeBehavior(true);
|
|
AutoFollowTriggered?.Invoke();
|
|
}
|
|
}
|
|
}
|
|
yield return null;
|
|
}
|
|
}
|
|
}
|
|
}
|