// 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; } } } }