using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets; /// /// Onboarding goal to be achieved as part of the . /// public struct Goal { /// /// Goal state this goal represents. /// public GoalManager.OnboardingGoals CurrentGoal; /// /// This denotes whether a goal has been completed. /// public bool Completed; /// /// Creates a new Goal with the specified . /// /// The state to assign to this Goal. public Goal(GoalManager.OnboardingGoals goal) { CurrentGoal = goal; Completed = false; } } /// /// The GoalManager cycles through a list of Goals, each representing /// an state to be completed by the user. /// public class GoalManager : MonoBehaviour { /// /// State representation for the onboarding goals for the GoalManager. /// public enum OnboardingGoals { /// /// Current empty scene /// Empty, /// /// Find/scan for AR surfaces /// FindSurfaces, /// /// Tap a surface to spawn an object /// TapSurface, /// /// Show movement hints /// Hints, /// /// Show scale and rotate hints /// Scale } /// /// Individual step instructions to show as part of a goal. /// [Serializable] public class Step { /// /// The GameObject to enable and show the user in order to complete the goal. /// [SerializeField] public GameObject stepObject; /// /// The text to display on the button shown in the step instructions. /// [SerializeField] public string buttonText; /// /// This indicates whether to show an additional button to skip the current goal/step. /// [SerializeField] public bool includeSkipButton; } [Tooltip("List of Goals/Steps to complete as part of the user onboarding.")] [SerializeField] List m_StepList = new List(); /// /// List of Goals/Steps to complete as part of the user onboarding. /// public List stepList { get => m_StepList; set => m_StepList = value; } [Tooltip("Object Spawner used to detect whether the spawning goal has been achieved.")] [SerializeField] ObjectSpawner m_ObjectSpawner; /// /// Object Spawner used to detect whether the spawning goal has been achieved. /// public ObjectSpawner objectSpawner { get => m_ObjectSpawner; set => m_ObjectSpawner = value; } [Tooltip("The greeting prompt Game Object to show when onboarding begins.")] [SerializeField] GameObject m_GreetingPrompt; /// /// The greeting prompt Game Object to show when onboarding begins. /// public GameObject greetingPrompt { get => m_GreetingPrompt; set => m_GreetingPrompt = value; } [Tooltip("The Options Button to enable once the greeting prompt is dismissed.")] [SerializeField] GameObject m_OptionsButton; /// /// The Options Button to enable once the greeting prompt is dismissed. /// public GameObject optionsButton { get => m_OptionsButton; set => m_OptionsButton = value; } [Tooltip("The Create Button to enable once the greeting prompt is dismissed.")] [SerializeField] GameObject m_CreateButton; /// /// The Create Button to enable once the greeting prompt is dismissed. /// public GameObject createButton { get => m_CreateButton; set => m_CreateButton = value; } [Tooltip("The AR Template Menu Manager object to enable once the greeting prompt is dismissed.")] [SerializeField] ARTemplateMenuManager m_MenuManager; /// /// The AR Template Menu Manager object to enable once the greeting prompt is dismissed. /// public ARTemplateMenuManager menuManager { get => m_MenuManager; set => m_MenuManager = value; } const int k_NumberOfSurfacesTappedToCompleteGoal = 1; Queue m_OnboardingGoals; Coroutine m_CurrentCoroutine; Goal m_CurrentGoal; bool m_AllGoalsFinished; int m_SurfacesTapped; int m_CurrentGoalIndex = 0; void Update() { if (Pointer.current != null && Pointer.current.press.wasPressedThisFrame && !m_AllGoalsFinished && (m_CurrentGoal.CurrentGoal == OnboardingGoals.FindSurfaces || m_CurrentGoal.CurrentGoal == OnboardingGoals.Hints || m_CurrentGoal.CurrentGoal == OnboardingGoals.Scale)) { if (m_CurrentCoroutine != null) { StopCoroutine(m_CurrentCoroutine); } CompleteGoal(); } } void CompleteGoal() { if (m_CurrentGoal.CurrentGoal == OnboardingGoals.TapSurface) m_ObjectSpawner.objectSpawned -= OnObjectSpawned; m_CurrentGoal.Completed = true; m_CurrentGoalIndex++; if (m_OnboardingGoals.Count > 0) { m_CurrentGoal = m_OnboardingGoals.Dequeue(); m_StepList[m_CurrentGoalIndex - 1].stepObject.SetActive(false); m_StepList[m_CurrentGoalIndex].stepObject.SetActive(true); } else { m_StepList[m_CurrentGoalIndex - 1].stepObject.SetActive(false); m_AllGoalsFinished = true; return; } PreprocessGoal(); } void PreprocessGoal() { if (m_CurrentGoal.CurrentGoal == OnboardingGoals.FindSurfaces) { m_CurrentCoroutine = StartCoroutine(WaitUntilNextCard(5f)); } else if (m_CurrentGoal.CurrentGoal == OnboardingGoals.Hints) { m_CurrentCoroutine = StartCoroutine(WaitUntilNextCard(6f)); } else if (m_CurrentGoal.CurrentGoal == OnboardingGoals.Scale) { m_CurrentCoroutine = StartCoroutine(WaitUntilNextCard(8f)); } else if (m_CurrentGoal.CurrentGoal == OnboardingGoals.TapSurface) { m_SurfacesTapped = 0; m_ObjectSpawner.objectSpawned += OnObjectSpawned; } } /// /// Tells the Goal Manager to wait for a specific number of seconds before completing /// the goal and showing the next card. /// /// The number of seconds to wait before showing the card. /// Returns an IEnumerator for the current coroutine running. public IEnumerator WaitUntilNextCard(float seconds) { yield return new WaitForSeconds(seconds); if (!Pointer.current.press.wasPressedThisFrame) { m_CurrentCoroutine = null; CompleteGoal(); } } /// /// Forces the completion of the current goal and moves to the next. /// public void ForceCompleteGoal() { CompleteGoal(); } void OnObjectSpawned(GameObject spawnedObject) { m_SurfacesTapped++; if (m_CurrentGoal.CurrentGoal == OnboardingGoals.TapSurface && m_SurfacesTapped >= k_NumberOfSurfacesTappedToCompleteGoal) { CompleteGoal(); } } /// /// Triggers a restart of the onboarding/coaching process. /// public void StartCoaching() { if (m_OnboardingGoals != null) { m_OnboardingGoals.Clear(); } m_OnboardingGoals = new Queue(); if (!m_AllGoalsFinished) { var findSurfaceGoal = new Goal(OnboardingGoals.FindSurfaces); m_OnboardingGoals.Enqueue(findSurfaceGoal); } int startingStep = m_AllGoalsFinished ? 1 : 0; var tapSurfaceGoal = new Goal(OnboardingGoals.TapSurface); var translateHintsGoal = new Goal(OnboardingGoals.Hints); var scaleHintsGoal = new Goal(OnboardingGoals.Scale); var rotateHintsGoal = new Goal(OnboardingGoals.Hints); m_OnboardingGoals.Enqueue(tapSurfaceGoal); m_OnboardingGoals.Enqueue(translateHintsGoal); m_OnboardingGoals.Enqueue(scaleHintsGoal); m_OnboardingGoals.Enqueue(rotateHintsGoal); m_CurrentGoal = m_OnboardingGoals.Dequeue(); m_AllGoalsFinished = false; m_CurrentGoalIndex = startingStep; m_GreetingPrompt.SetActive(false); m_OptionsButton.SetActive(true); m_CreateButton.SetActive(true); m_MenuManager.enabled = true; for (int i = startingStep; i < m_StepList.Count; i++) { if (i == startingStep) { m_StepList[i].stepObject.SetActive(true); PreprocessGoal(); } else { m_StepList[i].stepObject.SetActive(false); } } } }