GoalManager.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine.InputSystem;
  5. using UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets;
  6. namespace UnityEngine.XR.Templates.AR
  7. {
  8. /// <summary>
  9. /// Onboarding goal to be achieved as part of the <see cref="GoalManager"/>.
  10. /// </summary>
  11. public struct Goal
  12. {
  13. /// <summary>
  14. /// Goal state this goal represents.
  15. /// </summary>
  16. public GoalManager.OnboardingGoals CurrentGoal;
  17. /// <summary>
  18. /// This denotes whether a goal has been completed.
  19. /// </summary>
  20. public bool Completed;
  21. /// <summary>
  22. /// Creates a new Goal with the specified <see cref="GoalManager.OnboardingGoals"/>.
  23. /// </summary>
  24. /// <param name="goal">The <see cref="GoalManager.OnboardingGoals"/> state to assign to this Goal.</param>
  25. public Goal(GoalManager.OnboardingGoals goal)
  26. {
  27. CurrentGoal = goal;
  28. Completed = false;
  29. }
  30. }
  31. /// <summary>
  32. /// The GoalManager cycles through a list of Goals, each representing
  33. /// an <see cref="GoalManager.OnboardingGoals"/> state to be completed by the user.
  34. /// </summary>
  35. public class GoalManager : MonoBehaviour
  36. {
  37. /// <summary>
  38. /// State representation for the onboarding goals for the GoalManager.
  39. /// </summary>
  40. public enum OnboardingGoals
  41. {
  42. /// <summary>
  43. /// Current empty scene
  44. /// </summary>
  45. Empty,
  46. /// <summary>
  47. /// Find/scan for AR surfaces
  48. /// </summary>
  49. FindSurfaces,
  50. /// <summary>
  51. /// Tap a surface to spawn an object
  52. /// </summary>
  53. TapSurface,
  54. /// <summary>
  55. /// Show movement hints
  56. /// </summary>
  57. Hints,
  58. /// <summary>
  59. /// Show scale and rotate hints
  60. /// </summary>
  61. Scale
  62. }
  63. /// <summary>
  64. /// Individual step instructions to show as part of a goal.
  65. /// </summary>
  66. [Serializable]
  67. public class Step
  68. {
  69. /// <summary>
  70. /// The GameObject to enable and show the user in order to complete the goal.
  71. /// </summary>
  72. [SerializeField]
  73. public GameObject stepObject;
  74. /// <summary>
  75. /// The text to display on the button shown in the step instructions.
  76. /// </summary>
  77. [SerializeField]
  78. public string buttonText;
  79. /// <summary>
  80. /// This indicates whether to show an additional button to skip the current goal/step.
  81. /// </summary>
  82. [SerializeField]
  83. public bool includeSkipButton;
  84. }
  85. [Tooltip("List of Goals/Steps to complete as part of the user onboarding.")]
  86. [SerializeField]
  87. List<Step> m_StepList = new List<Step>();
  88. /// <summary>
  89. /// List of Goals/Steps to complete as part of the user onboarding.
  90. /// </summary>
  91. public List<Step> stepList
  92. {
  93. get => m_StepList;
  94. set => m_StepList = value;
  95. }
  96. [Tooltip("Object Spawner used to detect whether the spawning goal has been achieved.")]
  97. [SerializeField]
  98. ObjectSpawner m_ObjectSpawner;
  99. /// <summary>
  100. /// Object Spawner used to detect whether the spawning goal has been achieved.
  101. /// </summary>
  102. public ObjectSpawner objectSpawner
  103. {
  104. get => m_ObjectSpawner;
  105. set => m_ObjectSpawner = value;
  106. }
  107. [Tooltip("The greeting prompt Game Object to show when onboarding begins.")]
  108. [SerializeField]
  109. GameObject m_GreetingPrompt;
  110. /// <summary>
  111. /// The greeting prompt Game Object to show when onboarding begins.
  112. /// </summary>
  113. public GameObject greetingPrompt
  114. {
  115. get => m_GreetingPrompt;
  116. set => m_GreetingPrompt = value;
  117. }
  118. [Tooltip("The Options Button to enable once the greeting prompt is dismissed.")]
  119. [SerializeField]
  120. GameObject m_OptionsButton;
  121. /// <summary>
  122. /// The Options Button to enable once the greeting prompt is dismissed.
  123. /// </summary>
  124. public GameObject optionsButton
  125. {
  126. get => m_OptionsButton;
  127. set => m_OptionsButton = value;
  128. }
  129. [Tooltip("The Create Button to enable once the greeting prompt is dismissed.")]
  130. [SerializeField]
  131. GameObject m_CreateButton;
  132. /// <summary>
  133. /// The Create Button to enable once the greeting prompt is dismissed.
  134. /// </summary>
  135. public GameObject createButton
  136. {
  137. get => m_CreateButton;
  138. set => m_CreateButton = value;
  139. }
  140. [Tooltip("The AR Template Menu Manager object to enable once the greeting prompt is dismissed.")]
  141. [SerializeField]
  142. ARTemplateMenuManager m_MenuManager;
  143. /// <summary>
  144. /// The AR Template Menu Manager object to enable once the greeting prompt is dismissed.
  145. /// </summary>
  146. public ARTemplateMenuManager menuManager
  147. {
  148. get => m_MenuManager;
  149. set => m_MenuManager = value;
  150. }
  151. const int k_NumberOfSurfacesTappedToCompleteGoal = 1;
  152. Queue<Goal> m_OnboardingGoals;
  153. Coroutine m_CurrentCoroutine;
  154. Goal m_CurrentGoal;
  155. bool m_AllGoalsFinished;
  156. int m_SurfacesTapped;
  157. int m_CurrentGoalIndex = 0;
  158. void Update()
  159. {
  160. 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))
  161. {
  162. if (m_CurrentCoroutine != null)
  163. {
  164. StopCoroutine(m_CurrentCoroutine);
  165. }
  166. CompleteGoal();
  167. }
  168. }
  169. void CompleteGoal()
  170. {
  171. if (m_CurrentGoal.CurrentGoal == OnboardingGoals.TapSurface)
  172. m_ObjectSpawner.objectSpawned -= OnObjectSpawned;
  173. m_CurrentGoal.Completed = true;
  174. m_CurrentGoalIndex++;
  175. if (m_OnboardingGoals.Count > 0)
  176. {
  177. m_CurrentGoal = m_OnboardingGoals.Dequeue();
  178. m_StepList[m_CurrentGoalIndex - 1].stepObject.SetActive(false);
  179. m_StepList[m_CurrentGoalIndex].stepObject.SetActive(true);
  180. }
  181. else
  182. {
  183. m_StepList[m_CurrentGoalIndex - 1].stepObject.SetActive(false);
  184. m_AllGoalsFinished = true;
  185. return;
  186. }
  187. PreprocessGoal();
  188. }
  189. void PreprocessGoal()
  190. {
  191. if (m_CurrentGoal.CurrentGoal == OnboardingGoals.FindSurfaces)
  192. {
  193. m_CurrentCoroutine = StartCoroutine(WaitUntilNextCard(5f));
  194. }
  195. else if (m_CurrentGoal.CurrentGoal == OnboardingGoals.Hints)
  196. {
  197. m_CurrentCoroutine = StartCoroutine(WaitUntilNextCard(6f));
  198. }
  199. else if (m_CurrentGoal.CurrentGoal == OnboardingGoals.Scale)
  200. {
  201. m_CurrentCoroutine = StartCoroutine(WaitUntilNextCard(8f));
  202. }
  203. else if (m_CurrentGoal.CurrentGoal == OnboardingGoals.TapSurface)
  204. {
  205. m_SurfacesTapped = 0;
  206. m_ObjectSpawner.objectSpawned += OnObjectSpawned;
  207. }
  208. }
  209. /// <summary>
  210. /// Tells the Goal Manager to wait for a specific number of seconds before completing
  211. /// the goal and showing the next card.
  212. /// </summary>
  213. /// <param name="seconds">The number of seconds to wait before showing the card.</param>
  214. /// <returns>Returns an IEnumerator for the current coroutine running.</returns>
  215. public IEnumerator WaitUntilNextCard(float seconds)
  216. {
  217. yield return new WaitForSeconds(seconds);
  218. if (!Pointer.current.press.wasPressedThisFrame)
  219. {
  220. m_CurrentCoroutine = null;
  221. CompleteGoal();
  222. }
  223. }
  224. /// <summary>
  225. /// Forces the completion of the current goal and moves to the next.
  226. /// </summary>
  227. public void ForceCompleteGoal()
  228. {
  229. CompleteGoal();
  230. }
  231. void OnObjectSpawned(GameObject spawnedObject)
  232. {
  233. m_SurfacesTapped++;
  234. if (m_CurrentGoal.CurrentGoal == OnboardingGoals.TapSurface && m_SurfacesTapped >= k_NumberOfSurfacesTappedToCompleteGoal)
  235. {
  236. CompleteGoal();
  237. }
  238. }
  239. /// <summary>
  240. /// Triggers a restart of the onboarding/coaching process.
  241. /// </summary>
  242. public void StartCoaching()
  243. {
  244. if (m_OnboardingGoals != null)
  245. {
  246. m_OnboardingGoals.Clear();
  247. }
  248. m_OnboardingGoals = new Queue<Goal>();
  249. if (!m_AllGoalsFinished)
  250. {
  251. var findSurfaceGoal = new Goal(OnboardingGoals.FindSurfaces);
  252. m_OnboardingGoals.Enqueue(findSurfaceGoal);
  253. }
  254. int startingStep = m_AllGoalsFinished ? 1 : 0;
  255. var tapSurfaceGoal = new Goal(OnboardingGoals.TapSurface);
  256. var translateHintsGoal = new Goal(OnboardingGoals.Hints);
  257. var scaleHintsGoal = new Goal(OnboardingGoals.Scale);
  258. var rotateHintsGoal = new Goal(OnboardingGoals.Hints);
  259. m_OnboardingGoals.Enqueue(tapSurfaceGoal);
  260. m_OnboardingGoals.Enqueue(translateHintsGoal);
  261. m_OnboardingGoals.Enqueue(scaleHintsGoal);
  262. m_OnboardingGoals.Enqueue(rotateHintsGoal);
  263. m_CurrentGoal = m_OnboardingGoals.Dequeue();
  264. m_AllGoalsFinished = false;
  265. m_CurrentGoalIndex = startingStep;
  266. m_GreetingPrompt.SetActive(false);
  267. m_OptionsButton.SetActive(true);
  268. m_CreateButton.SetActive(true);
  269. m_MenuManager.enabled = true;
  270. for (int i = startingStep; i < m_StepList.Count; i++)
  271. {
  272. if (i == startingStep)
  273. {
  274. m_StepList[i].stepObject.SetActive(true);
  275. PreprocessGoal();
  276. }
  277. else
  278. {
  279. m_StepList[i].stepObject.SetActive(false);
  280. }
  281. }
  282. }
  283. }
  284. }