// 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
{
///
/// 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.
///
[RequireComponent(typeof(RadialView))]
[AddComponentMenu("Scripts/MRTK/SDK/FollowMeToggle")]
public class FollowMeToggle : MonoBehaviour
{
///
/// An enum specifying how the optional interactable should behave once the FollowMe behavior was changed.
///
public enum FollowMeBehaviorToInteractablesToggleState
{
ToggledWhenFollowing,
UntoggledWhenFollowing,
Manual
}
///
/// An optional object for visualizing the 'Follow Me' mode state.
///
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;
///
/// An optional Interactable to select/deselect when toggling the follow behavior.
///
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;
///
/// A way to indicate how should interactable react to the follow behavior state.
///
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;
///
/// Should following be automatically enabled when the user is further than a certain distance away?
///
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;
///
/// If autoFollowAtDistance is enabled, what distance to trigger auto following at.
///
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;
///
/// Optional transform to use when using autoFollowAtDistance. If not specified the local transform is used.
///
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();
///
/// Event that gets fired when auto follow is triggered.
///
public UnityEvent AutoFollowTriggered
{
get => autoFollowTriggered;
set => autoFollowTriggered = value;
}
#region MonoBehaviour Implementation
private void Awake()
{
radialView = GetComponent();
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
///
/// Toggles the current follow behavior of the solver.
///
public void ToggleFollowMeBehavior()
{
if (radialView != null)
{
SetFollowMeBehavior(!radialView.enabled);
}
}
///
/// Enables or disables the solver based on the follow parameter.
///
/// True if the solver should be active.
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;
}
}
}
}
///
/// Coroutine which checks how far away this transform is from the user and enables the follow behavior at a specified distance.
///
/// Coroutine enumerator.
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;
}
}
}
}