289 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			289 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Collections.Generic;
 | |
| using UnityEngine.Events;
 | |
| using UnityEngine.XR.Interaction.Toolkit;
 | |
| 
 | |
| namespace UnityEngine.XR.Content.Walkthrough
 | |
| {
 | |
|     /// <summary>
 | |
|     /// Contains information needed to process one step of a walkthrough.
 | |
|     /// </summary>
 | |
|     public class WalkthroughStep : MonoBehaviour
 | |
|     {
 | |
|         /// Local method use only -- created here to reduce garbage collection. Collections must be cleared before use
 | |
|         static readonly List<WalkthroughTrigger> s_TriggersToRemove = new List<WalkthroughTrigger>();
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Camera target to reposition user.")]
 | |
|         GameObject m_CameraTarget;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("The Teleportation Provider used to reposition the user. Usually a component on the XR Origin.")]
 | |
|         TeleportationProvider m_TeleportationProvider;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Optional audio source for voiceover")]
 | |
|         AudioSource m_AudioSource;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Objects to enable when this step is active.")]
 | |
|         List<GameObject> m_Visuals = new List<GameObject>();
 | |
| 
 | |
| #pragma warning disable 649
 | |
|         [SerializeField]
 | |
|         [Tooltip("Actions to call when the step starts.")]
 | |
|         UnityEvent m_OnStepBegin;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Actions to call when the step completes.")]
 | |
|         UnityEvent m_OnStepComplete;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("The purpose of this step.")]
 | |
|         string m_Description;
 | |
| #pragma warning restore 649
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("If true, this step cannot be skipped until completed at least once.")]
 | |
|         bool m_BlockUntilComplete;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("If true, this step will automatically progress when complete - unless explicitly skipped to.")]
 | |
|         bool m_AutoProgressOnComplete = true;
 | |
| 
 | |
|         bool m_Started;
 | |
|         bool m_AutoProgressEnabled = true;
 | |
|         bool m_StepInvoked;
 | |
| 
 | |
|         Action<bool> m_OnComplete;
 | |
|         GameObject m_Waypoint;
 | |
|         GameObject m_Link;
 | |
| 
 | |
|         List<WalkthroughTrigger> m_Triggers = new List<WalkthroughTrigger>();
 | |
|         List<WalkthroughTrigger> m_RemainingTriggers = new List<WalkthroughTrigger>();
 | |
| 
 | |
|         /// <summary>
 | |
|         /// The purpose of this step. Appends a (Complete) if complete and normally has triggers.
 | |
|         /// </summary>
 | |
|         public string description => $"{m_Description}{(completed && m_Triggers.Count > 0 ? " (Complete)" : "") }";
 | |
| 
 | |
| 
 | |
|         public GameObject waypoint
 | |
|         {
 | |
|             get => m_Waypoint;
 | |
|             set => m_Waypoint = value;
 | |
|         }
 | |
| 
 | |
|         public GameObject link
 | |
|         {
 | |
|             get => m_Link;
 | |
|             set => m_Link = value;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Ensures the step visuals are hidden until active and that all triggers are accounted for
 | |
|         /// </summary>
 | |
|         public void Initialize()
 | |
|         {
 | |
|             if (!m_Started)
 | |
|                 SetVisualsState(false);
 | |
| 
 | |
|             GetComponents(m_Triggers);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Returns true if this step does not currently have any triggers remaining to fire
 | |
|         /// </summary>
 | |
|         public bool canProgress => (!m_BlockUntilComplete || (m_RemainingTriggers.Count == 0));
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Returns true if this step does not block, or has been completed at least once.
 | |
|         /// </summary>
 | |
|         public bool canSkip => (!m_BlockUntilComplete || completed);
 | |
| 
 | |
|         /// <summary>
 | |
|         /// True if this step's triggers have been activated at least once
 | |
|         /// </summary>
 | |
|         public bool completed { get; private set; }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Makes this step and its triggers the active focus of a walkthrough
 | |
|         /// </summary>
 | |
|         /// <param name="onComplete">Callback to fire when this step's triggers are complete</param>
 | |
|         /// <param name="allowAutoProgress">If this step is allow to auto-progress during this activation</param>
 | |
|         public void StartStep(Action<bool> onComplete, bool allowAutoProgress = true)
 | |
|         {
 | |
|             if (m_Started)
 | |
|                 return;
 | |
| 
 | |
|             // Autoprogression is enabled only if the step AND walkthrough allow it
 | |
|             m_AutoProgressEnabled = allowAutoProgress && m_AutoProgressOnComplete;
 | |
| 
 | |
|             SetVisualsState(true);
 | |
|             SetAudioSource(true);
 | |
|             if (m_Waypoint != null)
 | |
|             {
 | |
|                 m_Waypoint.SetActive(false);
 | |
|             }
 | |
| 
 | |
|             if (m_CameraTarget != null && m_TeleportationProvider != null)
 | |
|             {
 | |
|                 SetCameraPosition();
 | |
|             }
 | |
| 
 | |
|             m_OnComplete = onComplete;
 | |
| 
 | |
|             m_Started = true;
 | |
| 
 | |
|             if (m_Triggers.Count == 0 && m_AutoProgressEnabled)
 | |
|             {
 | |
|                 CompleteStep();
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             foreach (var currentTrigger in m_Triggers)
 | |
|             {
 | |
|                 if (currentTrigger.ResetTrigger())
 | |
|                     m_RemainingTriggers.Add(currentTrigger);
 | |
|             }
 | |
| 
 | |
|             if (m_RemainingTriggers.Count == 0)
 | |
|             {
 | |
|                 CompleteStep();
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             if (m_RemainingTriggers.Count > 0)
 | |
|                 m_AutoProgressEnabled = m_AutoProgressOnComplete;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Ends this step being the focus of the current walkthrough
 | |
|         /// </summary>
 | |
|         public void CancelStep()
 | |
|         {
 | |
|             SetVisualsState(false);
 | |
|             SetAudioSource(false);
 | |
|             if (m_Waypoint != null)
 | |
|             {
 | |
|                 m_Waypoint.SetActive(true);
 | |
|             }
 | |
| 
 | |
|             if (!m_Started)
 | |
|                 return;
 | |
| 
 | |
|             m_OnComplete = null;
 | |
|             m_Started = false;
 | |
| 
 | |
|             m_RemainingTriggers.Clear();
 | |
|         }
 | |
| 
 | |
|         void CompleteStep()
 | |
|         {
 | |
|             if (!m_Started)
 | |
|                 return;
 | |
| 
 | |
|             completed = true;
 | |
| 
 | |
|             m_OnComplete?.Invoke(m_AutoProgressEnabled);
 | |
|             m_OnComplete = null;
 | |
|             m_Started = false;
 | |
| 
 | |
|             // We disable visuals if the the next step is being activated
 | |
|             if (m_AutoProgressEnabled)
 | |
|             {
 | |
|                 SetVisualsState(false);
 | |
|                 SetAudioSource(false);
 | |
|                 if (m_Waypoint != null)
 | |
|                 {
 | |
|                     m_Waypoint.SetActive(true);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             m_RemainingTriggers.Clear();
 | |
|         }
 | |
| 
 | |
|         void Update()
 | |
|         {
 | |
|             // If this step is running, check remaining triggers.  Any triggers that are now met get removed.
 | |
|             // If there are no triggers left, then the step is complete.
 | |
|             if (!m_Started)
 | |
|                 return;
 | |
| 
 | |
|             if (m_RemainingTriggers.Count == 0)
 | |
|                 return;
 | |
| 
 | |
|             s_TriggersToRemove.Clear();
 | |
|             foreach (var currentTrigger in m_RemainingTriggers)
 | |
|             {
 | |
|                 if (currentTrigger.Check())
 | |
|                     s_TriggersToRemove.Add(currentTrigger);
 | |
|             }
 | |
| 
 | |
|             foreach (var toRemove in s_TriggersToRemove)
 | |
|             {
 | |
|                 m_RemainingTriggers.Remove(toRemove);
 | |
|             }
 | |
|             s_TriggersToRemove.Clear();
 | |
| 
 | |
|             if (m_RemainingTriggers.Count == 0)
 | |
|             {
 | |
|                 CompleteStep();
 | |
|                 return;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void SetVisualsState(bool enabled)
 | |
|         {
 | |
|             if (m_Visuals != null)
 | |
|             {
 | |
|                 foreach (var currentVisual in m_Visuals)
 | |
|                 {
 | |
|                     if (currentVisual != null)
 | |
|                         currentVisual.SetActive(enabled);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (m_StepInvoked == enabled)
 | |
|                 return;
 | |
| 
 | |
|             m_StepInvoked = enabled;
 | |
| 
 | |
|             if (enabled && m_OnStepBegin != null)
 | |
|                 m_OnStepBegin.Invoke();
 | |
| 
 | |
|             if (!enabled && m_OnStepComplete != null)
 | |
|                 m_OnStepComplete.Invoke();
 | |
|         }
 | |
| 
 | |
|         void SetAudioSource(bool enabled)
 | |
|         {
 | |
|             if (m_AudioSource != null)
 | |
|             {
 | |
|                 if (enabled)
 | |
|                 {
 | |
|                     m_AudioSource.Play();
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     m_AudioSource.Stop();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void SetCameraPosition()
 | |
|         {
 | |
|             TeleportRequest request = new TeleportRequest()
 | |
|             {
 | |
|                 requestTime = Time.time,
 | |
|                 matchOrientation = MatchOrientation.TargetUpAndForward,
 | |
| 
 | |
|                 destinationPosition = m_CameraTarget.transform.position,
 | |
|                 destinationRotation = m_CameraTarget.transform.rotation
 | |
|             };
 | |
| 
 | |
|             m_TeleportationProvider.QueueTeleportRequest(request);
 | |
|         }
 | |
|     }
 | |
| }
 |