Assignment for RMIT Mixed Reality in 2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

472 lines
13 KiB

  1. namespace Oculus.Platform.Samples.VrHoops
  2. {
  3. using UnityEngine;
  4. using UnityEngine.Assertions;
  5. using UnityEngine.UI;
  6. using System.Collections.Generic;
  7. using Oculus.Platform.Models;
  8. // This class coordinates playing matches. It mediates being idle
  9. // and entering a practice or online game match.
  10. public class MatchController : MonoBehaviour
  11. {
  12. // Text to display when the match will start or finish
  13. [SerializeField] private Text m_timerText = null;
  14. // the camera is moved between the idle position and the assigned court position
  15. [SerializeField] private Camera m_camera = null;
  16. // where the camera will be when not in a match
  17. [SerializeField] private Transform m_idleCameraTransform = null;
  18. // button that toggles between matchmaking and cancel
  19. [SerializeField] private Text m_matchmakeButtonText = null;
  20. // this should equal the maximum number of players configured on the Oculus Dashboard
  21. [SerializeField] private PlayerArea[] m_playerAreas = new PlayerArea[3];
  22. // the time to wait between selecting Practice and starting
  23. [SerializeField] private uint PRACTICE_WARMUP_TIME = 5;
  24. // seconds to wait to coordinate P2P setup with other match players before starting
  25. [SerializeField] private uint MATCH_WARMUP_TIME = 30;
  26. // seconds for the match
  27. [SerializeField] private uint MATCH_TIME = 20;
  28. // how long to remain in position after the match to view results
  29. [SerializeField] private uint MATCH_COOLDOWN_TIME = 10;
  30. // panel to add most-wins leaderboard entries to
  31. [SerializeField] private GameObject m_mostWinsLeaderboard = null;
  32. // panel to add high-score leaderboard entries to
  33. [SerializeField] private GameObject m_highestScoresLeaderboard = null;
  34. // leaderboard entry Text prefab
  35. [SerializeField] private GameObject m_leaderboardEntryPrefab = null;
  36. // Text prefab to use for achievements fly-text
  37. [SerializeField] private GameObject m_flytext = null;
  38. // the current state of the match controller
  39. private State m_currentState;
  40. // transition time for states that automatically transition to the next state,
  41. // for example ending the match when the timer expires
  42. private float m_nextStateTransitionTime;
  43. // the court the local player was assigned to
  44. private int m_localSlot;
  45. void Start()
  46. {
  47. PlatformManager.Matchmaking.EnqueueResultCallback = OnMatchFoundCallback;
  48. PlatformManager.Matchmaking.MatchPlayerAddedCallback = MatchPlayerAddedCallback;
  49. PlatformManager.P2P.StartTimeOfferCallback = StartTimeOfferCallback;
  50. PlatformManager.Leaderboards.MostWinsLeaderboardUpdatedCallback = MostWinsLeaderboardCallback;
  51. PlatformManager.Leaderboards.HighScoreLeaderboardUpdatedCallback = HighestScoreLeaderboardCallback;
  52. TransitionToState(State.NONE);
  53. }
  54. void Update()
  55. {
  56. UpdateCheckForNextTimedTransition();
  57. UpdateMatchTimer();
  58. }
  59. public float MatchStartTime
  60. {
  61. get
  62. {
  63. switch(m_currentState)
  64. {
  65. case State.WAITING_TO_START_PRACTICE:
  66. case State.WAITING_TO_SETUP_MATCH:
  67. return m_nextStateTransitionTime;
  68. default: return 0;
  69. }
  70. }
  71. private set { m_nextStateTransitionTime = value; }
  72. }
  73. #region State Management
  74. private enum State
  75. {
  76. UNKNOWN,
  77. // no current match, waiting for the local user to select something
  78. NONE,
  79. // user selected a practice match, waiting for the match timer to start
  80. WAITING_TO_START_PRACTICE,
  81. // playing a Practice match against AI players
  82. PRACTICING,
  83. // post practice match, time to view the scores
  84. VIEWING_RESULTS_PRACTICE,
  85. // selecting Player Online and waiting for the Matchmaking service to find and create a
  86. // match and join the assigned match room
  87. WAITING_FOR_MATCH,
  88. // match room is joined, waiting to coordinate with the other players
  89. WAITING_TO_SETUP_MATCH,
  90. // playing a competative match against other players
  91. PLAYING_MATCH,
  92. // match is complete, viewing the match scores
  93. VIEWING_MATCH_RESULTS,
  94. }
  95. void TransitionToState(State newState)
  96. {
  97. Debug.LogFormat("MatchController State {0} -> {1}", m_currentState, newState);
  98. if (m_currentState != newState)
  99. {
  100. var oldState = m_currentState;
  101. m_currentState = newState;
  102. // state transition logic
  103. switch (newState)
  104. {
  105. case State.NONE:
  106. SetupForIdle();
  107. MoveCameraToIdlePosition();
  108. PlatformManager.TransitionToState(PlatformManager.State.WAITING_TO_PRACTICE_OR_MATCHMAKE);
  109. m_matchmakeButtonText.text = "Play Online";
  110. break;
  111. case State.WAITING_TO_START_PRACTICE:
  112. Assert.AreEqual(oldState, State.NONE);
  113. SetupForPractice();
  114. MoveCameraToMatchPosition();
  115. PlatformManager.TransitionToState(PlatformManager.State.MATCH_TRANSITION);
  116. m_nextStateTransitionTime = Time.time + PRACTICE_WARMUP_TIME;
  117. break;
  118. case State.PRACTICING:
  119. Assert.AreEqual(oldState, State.WAITING_TO_START_PRACTICE);
  120. PlatformManager.TransitionToState(PlatformManager.State.PLAYING_A_LOCAL_MATCH);
  121. m_nextStateTransitionTime = Time.time + MATCH_TIME;
  122. break;
  123. case State.VIEWING_RESULTS_PRACTICE:
  124. Assert.AreEqual(oldState, State.PRACTICING);
  125. PlatformManager.TransitionToState(PlatformManager.State.MATCH_TRANSITION);
  126. m_nextStateTransitionTime = Time.time + MATCH_COOLDOWN_TIME;
  127. m_timerText.text = "0:00.00";
  128. break;
  129. case State.WAITING_FOR_MATCH:
  130. Assert.AreEqual(oldState, State.NONE);
  131. PlatformManager.TransitionToState(PlatformManager.State.MATCH_TRANSITION);
  132. m_matchmakeButtonText.text = "Cancel";
  133. break;
  134. case State.WAITING_TO_SETUP_MATCH:
  135. Assert.AreEqual(oldState, State.WAITING_FOR_MATCH);
  136. m_nextStateTransitionTime = Time.time + MATCH_WARMUP_TIME;
  137. break;
  138. case State.PLAYING_MATCH:
  139. Assert.AreEqual(oldState, State.WAITING_TO_SETUP_MATCH);
  140. PlatformManager.TransitionToState(PlatformManager.State.PLAYING_A_NETWORKED_MATCH);
  141. m_nextStateTransitionTime = Time.time + MATCH_TIME;
  142. break;
  143. case State.VIEWING_MATCH_RESULTS:
  144. Assert.AreEqual(oldState, State.PLAYING_MATCH);
  145. PlatformManager.TransitionToState(PlatformManager.State.MATCH_TRANSITION);
  146. m_nextStateTransitionTime = Time.time + MATCH_COOLDOWN_TIME;
  147. m_timerText.text = "0:00.00";
  148. CalculateMatchResults();
  149. break;
  150. }
  151. }
  152. }
  153. void UpdateCheckForNextTimedTransition()
  154. {
  155. if (m_currentState != State.NONE && Time.time >= m_nextStateTransitionTime)
  156. {
  157. switch (m_currentState)
  158. {
  159. case State.WAITING_TO_START_PRACTICE:
  160. TransitionToState(State.PRACTICING);
  161. break;
  162. case State.PRACTICING:
  163. TransitionToState(State.VIEWING_RESULTS_PRACTICE);
  164. break;
  165. case State.VIEWING_RESULTS_PRACTICE:
  166. TransitionToState(State.NONE);
  167. break;
  168. case State.WAITING_TO_SETUP_MATCH:
  169. TransitionToState(State.PLAYING_MATCH);
  170. break;
  171. case State.PLAYING_MATCH:
  172. TransitionToState(State.VIEWING_MATCH_RESULTS);
  173. break;
  174. case State.VIEWING_MATCH_RESULTS:
  175. PlatformManager.Matchmaking.EndMatch();
  176. TransitionToState(State.NONE);
  177. break;
  178. }
  179. }
  180. }
  181. void UpdateMatchTimer()
  182. {
  183. if (Time.time <= m_nextStateTransitionTime)
  184. {
  185. switch (m_currentState)
  186. {
  187. case State.WAITING_TO_START_PRACTICE:
  188. case State.WAITING_TO_SETUP_MATCH:
  189. m_timerText.text = string.Format("{0:0}", Mathf.Ceil(Time.time - MatchStartTime));
  190. break;
  191. case State.PRACTICING:
  192. case State.PLAYING_MATCH:
  193. var delta = m_nextStateTransitionTime - Time.time;
  194. m_timerText.text = string.Format("{0:#0}:{1:#00}.{2:00}",
  195. Mathf.Floor(delta / 60),
  196. Mathf.Floor(delta) % 60,
  197. Mathf.Floor(delta * 100) % 100);
  198. break;
  199. }
  200. }
  201. }
  202. #endregion
  203. #region Player Setup/Teardown
  204. void SetupForIdle()
  205. {
  206. for (int i = 0; i < m_playerAreas.Length; i++)
  207. {
  208. m_playerAreas[i].SetupForPlayer<AIPlayer>("* AI *");
  209. }
  210. }
  211. void SetupForPractice()
  212. {
  213. // randomly select a position for the local player
  214. m_localSlot = Random.Range(0,m_playerAreas.Length-1);
  215. for (int i=0; i < m_playerAreas.Length; i++)
  216. {
  217. if (i == m_localSlot)
  218. {
  219. m_playerAreas[i].SetupForPlayer<LocalPlayer>(PlatformManager.MyOculusID);
  220. }
  221. else
  222. {
  223. m_playerAreas[i].SetupForPlayer<AIPlayer>("* AI *");
  224. }
  225. }
  226. }
  227. Player MatchPlayerAddedCallback(int slot, User user)
  228. {
  229. Player player = null;
  230. if (m_currentState == State.WAITING_TO_SETUP_MATCH && slot < m_playerAreas.Length)
  231. {
  232. if (user.ID == PlatformManager.MyID)
  233. {
  234. var localPlayer = m_playerAreas[slot].SetupForPlayer<LocalPlayer>(user.OculusID);
  235. MoveCameraToMatchPosition();
  236. player = localPlayer;
  237. m_localSlot = slot;
  238. }
  239. else
  240. {
  241. var remotePlayer = m_playerAreas[slot].SetupForPlayer<RemotePlayer>(user.OculusID);
  242. remotePlayer.User = user;
  243. player = remotePlayer;
  244. }
  245. }
  246. return player;
  247. }
  248. #endregion
  249. #region Main Camera Movement
  250. void MoveCameraToIdlePosition()
  251. {
  252. var ejector = m_camera.gameObject.GetComponentInChildren<BallEjector>();
  253. if (ejector)
  254. {
  255. ejector.transform.SetParent(m_camera.transform.parent, false);
  256. m_camera.transform.SetParent(m_idleCameraTransform, false);
  257. }
  258. }
  259. void MoveCameraToMatchPosition()
  260. {
  261. foreach (var playerArea in m_playerAreas)
  262. {
  263. var player = playerArea.GetComponentInChildren<LocalPlayer>();
  264. if (player)
  265. {
  266. var ejector = player.GetComponentInChildren<BallEjector>();
  267. m_camera.transform.SetParent(player.transform, false);
  268. ejector.transform.SetParent(m_camera.transform, false);
  269. break;
  270. }
  271. }
  272. DisplayAchievementFlytext();
  273. }
  274. #endregion
  275. #region Match Initiation
  276. public void StartPracticeMatch()
  277. {
  278. if (m_currentState == State.NONE)
  279. {
  280. TransitionToState(State.WAITING_TO_START_PRACTICE);
  281. }
  282. }
  283. public void PlayOnlineOrCancel()
  284. {
  285. Debug.Log ("Play online or Cancel");
  286. if (m_currentState == State.NONE)
  287. {
  288. PlatformManager.Matchmaking.QueueForMatch();
  289. TransitionToState (State.WAITING_FOR_MATCH);
  290. }
  291. else if (m_currentState == State.WAITING_FOR_MATCH)
  292. {
  293. PlatformManager.Matchmaking.LeaveQueue();
  294. TransitionToState (State.NONE);
  295. }
  296. }
  297. // notification from the Matchmaking service if we succeeded in finding an online match
  298. void OnMatchFoundCallback(bool success)
  299. {
  300. if (success)
  301. {
  302. TransitionToState(State.WAITING_TO_SETUP_MATCH);
  303. }
  304. else
  305. {
  306. TransitionToState(State.NONE);
  307. }
  308. }
  309. // handle an offer from a remote player for a new match start time
  310. float StartTimeOfferCallback(float remoteTime)
  311. {
  312. if (m_currentState == State.WAITING_TO_SETUP_MATCH)
  313. {
  314. // if the remote start time is later use that, as long as it's not horribly wrong
  315. if (remoteTime > MatchStartTime && (remoteTime - 60) < MatchStartTime)
  316. {
  317. Debug.Log("Moving Start time by " + (remoteTime - MatchStartTime));
  318. MatchStartTime = remoteTime;
  319. }
  320. }
  321. return MatchStartTime;
  322. }
  323. #endregion
  324. #region Leaderboards and Achievements
  325. void MostWinsLeaderboardCallback(SortedDictionary<int, LeaderboardEntry> entries)
  326. {
  327. foreach (Transform entry in m_mostWinsLeaderboard.transform)
  328. {
  329. Destroy(entry.gameObject);
  330. }
  331. foreach (var entry in entries.Values)
  332. {
  333. GameObject label = Instantiate(m_leaderboardEntryPrefab);
  334. label.transform.SetParent(m_mostWinsLeaderboard.transform, false);
  335. label.GetComponent<Text>().text =
  336. string.Format("{0} - {1} - {2}", entry.Rank, entry.User.OculusID, entry.Score);
  337. }
  338. }
  339. void HighestScoreLeaderboardCallback(SortedDictionary<int, LeaderboardEntry> entries)
  340. {
  341. foreach (Transform entry in m_highestScoresLeaderboard.transform)
  342. {
  343. Destroy(entry.gameObject);
  344. }
  345. foreach (var entry in entries.Values)
  346. {
  347. GameObject label = Instantiate(m_leaderboardEntryPrefab);
  348. label.transform.SetParent(m_highestScoresLeaderboard.transform, false);
  349. label.GetComponent<Text>().text =
  350. string.Format("{0} - {1} - {2}", entry.Rank, entry.User.OculusID, entry.Score);
  351. }
  352. }
  353. void CalculateMatchResults()
  354. {
  355. LocalPlayer localPlayer = null;
  356. RemotePlayer remotePlayer = null;
  357. foreach (var court in m_playerAreas)
  358. {
  359. if (court.Player is LocalPlayer)
  360. {
  361. localPlayer = court.Player as LocalPlayer;
  362. }
  363. else if (court.Player is RemotePlayer &&
  364. (remotePlayer == null || court.Player.Score > remotePlayer.Score))
  365. {
  366. remotePlayer = court.Player as RemotePlayer;
  367. }
  368. }
  369. // ignore the match results if the player got into a session without an opponent
  370. if (!localPlayer || !remotePlayer)
  371. {
  372. return;
  373. }
  374. bool wonMatch = localPlayer.Score > remotePlayer.Score;
  375. PlatformManager.Leaderboards.SubmitMatchScores(wonMatch, localPlayer.Score);
  376. if (wonMatch)
  377. {
  378. PlatformManager.Achievements.RecordWinForLocalUser();
  379. }
  380. }
  381. void DisplayAchievementFlytext()
  382. {
  383. if (PlatformManager.Achievements.LikesToWin)
  384. {
  385. GameObject go = Instantiate(m_flytext);
  386. go.GetComponent<Text>().text = "Likes to Win!";
  387. go.transform.position = Vector3.up * 40;
  388. go.transform.SetParent(m_playerAreas[m_localSlot].NameText.transform, false);
  389. }
  390. }
  391. #endregion
  392. }
  393. }