Newer
Older
CGTrack / Assets / Oculus / Platform / Samples / VrHoops / Scripts / MatchController.cs
@Pascal Syma Pascal Syma on 25 Jul 2021 12 KB Initial Commit
namespace Oculus.Platform.Samples.VrHoops
{
	using UnityEngine;
	using UnityEngine.Assertions;
	using UnityEngine.UI;
	using System.Collections.Generic;
	using Oculus.Platform.Models;

	// This class coordinates playing matches.  It mediates being idle
	// and entering a practice or online game match.
	public class MatchController : MonoBehaviour
	{
		// Text to display when the match will start or finish
		[SerializeField] private Text m_timerText = null;

		// the camera is moved between the idle position and the assigned court position
		[SerializeField] private Camera m_camera = null;

		// where the camera will be when not in a match
		[SerializeField] private Transform m_idleCameraTransform = null;

		// button that toggles between matchmaking and cancel
		[SerializeField] private Text m_matchmakeButtonText = null;

		// this should equal the maximum number of players configured on the Oculus Dashboard
		[SerializeField] private PlayerArea[] m_playerAreas = new PlayerArea[3];

		// the time to wait between selecting Practice and starting
		[SerializeField] private uint PRACTICE_WARMUP_TIME = 5;

		// seconds to wait to coordinate P2P setup with other match players before starting
		[SerializeField] private uint MATCH_WARMUP_TIME = 30;

		// seconds for the match
		[SerializeField] private uint MATCH_TIME = 20;

		// how long to remain in position after the match to view results
		[SerializeField] private uint MATCH_COOLDOWN_TIME = 10;

		// panel to add most-wins leaderboard entries to
		[SerializeField] private GameObject m_mostWinsLeaderboard = null;

		// panel to add high-score leaderboard entries to
		[SerializeField] private GameObject m_highestScoresLeaderboard = null;

		// leaderboard entry Text prefab
		[SerializeField] private GameObject m_leaderboardEntryPrefab = null;

		// Text prefab to use for achievements fly-text
		[SerializeField] private GameObject m_flytext = null;

		// the current state of the match controller
		private State m_currentState;

		// transition time for states that automatically transition to the next state,
		// for example ending the match when the timer expires
		private float m_nextStateTransitionTime;

		// the court the local player was assigned to
		private int m_localSlot;

		void Start()
		{
			PlatformManager.Matchmaking.EnqueueResultCallback = OnMatchFoundCallback;
			PlatformManager.Matchmaking.MatchPlayerAddedCallback = MatchPlayerAddedCallback;
			PlatformManager.P2P.StartTimeOfferCallback = StartTimeOfferCallback;
			PlatformManager.Leaderboards.MostWinsLeaderboardUpdatedCallback = MostWinsLeaderboardCallback;
			PlatformManager.Leaderboards.HighScoreLeaderboardUpdatedCallback = HighestScoreLeaderboardCallback;

			TransitionToState(State.NONE);
		}

		void Update()
		{
			UpdateCheckForNextTimedTransition();
			UpdateMatchTimer();
		}

		public float MatchStartTime
		{
			get
			{
				switch(m_currentState)
				{
					case State.WAITING_TO_START_PRACTICE:
					case State.WAITING_TO_SETUP_MATCH:
						return m_nextStateTransitionTime;

					default: return 0;
				}
			}
			private set { m_nextStateTransitionTime = value; }
		}

		#region State Management

		private enum State
		{
			UNKNOWN,

			// no current match, waiting for the local user to select something
			NONE,

			// user selected a practice match, waiting for the match timer to start
			WAITING_TO_START_PRACTICE,

			// playing a Practice match against AI players
			PRACTICING,

			// post practice match, time to view the scores
			VIEWING_RESULTS_PRACTICE,

			// selecting Player Online and waiting for the Matchmaking service to find and create a
			// match and join the assigned match room
			WAITING_FOR_MATCH,

			// match room is joined, waiting to coordinate with the other players
			WAITING_TO_SETUP_MATCH,

			// playing a competative match against other players
			PLAYING_MATCH,

			// match is complete, viewing the match scores
			VIEWING_MATCH_RESULTS,
		}

		void TransitionToState(State newState)
		{
			Debug.LogFormat("MatchController State {0} -> {1}", m_currentState, newState);

			if (m_currentState != newState)
			{
				var oldState = m_currentState;
				m_currentState = newState;

				// state transition logic
				switch (newState)
				{
					case State.NONE:
						SetupForIdle();
						MoveCameraToIdlePosition();
						PlatformManager.TransitionToState(PlatformManager.State.WAITING_TO_PRACTICE_OR_MATCHMAKE);
						m_matchmakeButtonText.text = "Play Online";
						break;

					case State.WAITING_TO_START_PRACTICE:
						Assert.AreEqual(oldState, State.NONE);
						SetupForPractice();
						MoveCameraToMatchPosition();
						PlatformManager.TransitionToState(PlatformManager.State.MATCH_TRANSITION);
						m_nextStateTransitionTime = Time.time + PRACTICE_WARMUP_TIME;
						break;

					case State.PRACTICING:
						Assert.AreEqual(oldState, State.WAITING_TO_START_PRACTICE);
						PlatformManager.TransitionToState(PlatformManager.State.PLAYING_A_LOCAL_MATCH);
						m_nextStateTransitionTime = Time.time + MATCH_TIME;
						break;

					case State.VIEWING_RESULTS_PRACTICE:
						Assert.AreEqual(oldState, State.PRACTICING);
						PlatformManager.TransitionToState(PlatformManager.State.MATCH_TRANSITION);
						m_nextStateTransitionTime = Time.time + MATCH_COOLDOWN_TIME;
						m_timerText.text = "0:00.00";
						break;

					case State.WAITING_FOR_MATCH:
						Assert.AreEqual(oldState, State.NONE);
						PlatformManager.TransitionToState(PlatformManager.State.MATCH_TRANSITION);
						m_matchmakeButtonText.text = "Cancel";
						break;

					case State.WAITING_TO_SETUP_MATCH:
						Assert.AreEqual(oldState, State.WAITING_FOR_MATCH);
						m_nextStateTransitionTime = Time.time + MATCH_WARMUP_TIME;
						break;

					case State.PLAYING_MATCH:
						Assert.AreEqual(oldState, State.WAITING_TO_SETUP_MATCH);
						PlatformManager.TransitionToState(PlatformManager.State.PLAYING_A_NETWORKED_MATCH);
						m_nextStateTransitionTime = Time.time + MATCH_TIME;
						break;

					case State.VIEWING_MATCH_RESULTS:
						Assert.AreEqual(oldState, State.PLAYING_MATCH);
						PlatformManager.TransitionToState(PlatformManager.State.MATCH_TRANSITION);
						m_nextStateTransitionTime = Time.time + MATCH_COOLDOWN_TIME;
						m_timerText.text = "0:00.00";
						CalculateMatchResults();
						break;
				}
			}
		}

		void UpdateCheckForNextTimedTransition()
		{
			if (m_currentState != State.NONE && Time.time >= m_nextStateTransitionTime)
			{
				switch (m_currentState)
				{
					case State.WAITING_TO_START_PRACTICE:
						TransitionToState(State.PRACTICING);
						break;

					case State.PRACTICING:
						TransitionToState(State.VIEWING_RESULTS_PRACTICE);
						break;

					case State.VIEWING_RESULTS_PRACTICE:
						TransitionToState(State.NONE);
						break;

					case State.WAITING_TO_SETUP_MATCH:
						TransitionToState(State.PLAYING_MATCH);
						break;

					case State.PLAYING_MATCH:
						TransitionToState(State.VIEWING_MATCH_RESULTS);
						break;

					case State.VIEWING_MATCH_RESULTS:
						PlatformManager.Matchmaking.EndMatch();
						TransitionToState(State.NONE);
						break;
				}
			}
		}

		void UpdateMatchTimer()
		{
			if (Time.time <= m_nextStateTransitionTime)
			{
				switch (m_currentState)
				{
					case State.WAITING_TO_START_PRACTICE:
					case State.WAITING_TO_SETUP_MATCH:
						m_timerText.text = string.Format("{0:0}", Mathf.Ceil(Time.time - MatchStartTime));
						break;

					case State.PRACTICING:
					case State.PLAYING_MATCH:
						var delta = m_nextStateTransitionTime - Time.time;
						m_timerText.text = string.Format("{0:#0}:{1:#00}.{2:00}",
							Mathf.Floor(delta / 60),
							Mathf.Floor(delta) % 60,
							Mathf.Floor(delta * 100) % 100);
						break;
				}
			}
		}

		#endregion

		#region Player Setup/Teardown

		void SetupForIdle()
		{
			for (int i = 0; i < m_playerAreas.Length; i++)
			{
				m_playerAreas[i].SetupForPlayer<AIPlayer>("* AI *");
			}
		}

		void SetupForPractice()
		{
			// randomly select a position for the local player
			m_localSlot = Random.Range(0,m_playerAreas.Length-1);

			for (int i=0; i < m_playerAreas.Length; i++)
			{
				if (i == m_localSlot)
				{
					m_playerAreas[i].SetupForPlayer<LocalPlayer>(PlatformManager.MyOculusID);
				}
				else
				{
					m_playerAreas[i].SetupForPlayer<AIPlayer>("* AI *");
				}
			}
		}

		Player MatchPlayerAddedCallback(int slot, User user)
		{
			Player player = null;

			if (m_currentState == State.WAITING_TO_SETUP_MATCH && slot < m_playerAreas.Length)
			{
				if (user.ID == PlatformManager.MyID)
				{
					var localPlayer = m_playerAreas[slot].SetupForPlayer<LocalPlayer>(user.OculusID);
					MoveCameraToMatchPosition();
					player = localPlayer;
					m_localSlot = slot;
				}
				else
				{
					var remotePlayer = m_playerAreas[slot].SetupForPlayer<RemotePlayer>(user.OculusID);
					remotePlayer.User = user;
					player = remotePlayer;
				}
			}

			return player;
		}

		#endregion

		#region Main Camera Movement

		void MoveCameraToIdlePosition()
		{
			var ejector = m_camera.gameObject.GetComponentInChildren<BallEjector>();
			if (ejector)
			{
				ejector.transform.SetParent(m_camera.transform.parent, false);
				m_camera.transform.SetParent(m_idleCameraTransform, false);
			}
		}

		void MoveCameraToMatchPosition()
		{
			foreach (var playerArea in m_playerAreas)
			{
				var player = playerArea.GetComponentInChildren<LocalPlayer>();
				if (player)
				{
					var ejector = player.GetComponentInChildren<BallEjector>();
					m_camera.transform.SetParent(player.transform, false);
					ejector.transform.SetParent(m_camera.transform, false);
					break;
				}
			}
			DisplayAchievementFlytext();
		}

		#endregion

		#region Match Initiation

		public void StartPracticeMatch()
		{
			if (m_currentState == State.NONE)
			{
				TransitionToState(State.WAITING_TO_START_PRACTICE);
			}
		}

		public void PlayOnlineOrCancel()
		{
			Debug.Log ("Play online or Cancel");

			if (m_currentState == State.NONE)
			{
				PlatformManager.Matchmaking.QueueForMatch();
				TransitionToState (State.WAITING_FOR_MATCH);
			}
			else if (m_currentState == State.WAITING_FOR_MATCH)
			{
				PlatformManager.Matchmaking.LeaveQueue();
				TransitionToState (State.NONE);
			}
		}

		// notification from the Matchmaking service if we succeeded in finding an online match
		void OnMatchFoundCallback(bool success)
		{
			if (success)
			{
				TransitionToState(State.WAITING_TO_SETUP_MATCH);
			}
			else
			{
				TransitionToState(State.NONE);
			}
		}

		// handle an offer from a remote player for a new match start time
		float StartTimeOfferCallback(float remoteTime)
		{
			if (m_currentState == State.WAITING_TO_SETUP_MATCH)
			{
				// if the remote start time is later use that, as long as it's not horribly wrong
				if (remoteTime > MatchStartTime && (remoteTime - 60) < MatchStartTime)
				{
					Debug.Log("Moving Start time by " + (remoteTime - MatchStartTime));
					MatchStartTime = remoteTime;
				}
			}
			return MatchStartTime;
		}

		#endregion

		#region Leaderboards and Achievements

		void MostWinsLeaderboardCallback(SortedDictionary<int, LeaderboardEntry> entries)
		{
			foreach (Transform entry in m_mostWinsLeaderboard.transform)
			{
				Destroy(entry.gameObject);
			}
			foreach (var entry in entries.Values)
			{
				GameObject label = Instantiate(m_leaderboardEntryPrefab);
				label.transform.SetParent(m_mostWinsLeaderboard.transform, false);
				label.GetComponent<Text>().text =
					string.Format("{0} - {1} - {2}", entry.Rank, entry.User.OculusID, entry.Score);
			}
		}

		void HighestScoreLeaderboardCallback(SortedDictionary<int, LeaderboardEntry> entries)
		{
			foreach (Transform entry in m_highestScoresLeaderboard.transform)
			{
				Destroy(entry.gameObject);
			}
			foreach (var entry in entries.Values)
			{
				GameObject label = Instantiate(m_leaderboardEntryPrefab);
				label.transform.SetParent(m_highestScoresLeaderboard.transform, false);
				label.GetComponent<Text>().text =
					string.Format("{0} - {1} - {2}", entry.Rank, entry.User.OculusID, entry.Score);
			}
		}

		void CalculateMatchResults()
		{
			LocalPlayer localPlayer = null;
			RemotePlayer remotePlayer = null;

			foreach (var court in m_playerAreas)
			{
				if (court.Player is LocalPlayer)
				{
					localPlayer = court.Player as LocalPlayer;
				}
				else if (court.Player is RemotePlayer &&
					(remotePlayer == null || court.Player.Score > remotePlayer.Score))
				{
					remotePlayer = court.Player as RemotePlayer;
				}
			}

			// ignore the match results if the player got into a session without an opponent
			if (!localPlayer || !remotePlayer)
			{
				return;
			}

			bool wonMatch = localPlayer.Score > remotePlayer.Score;
			PlatformManager.Leaderboards.SubmitMatchScores(wonMatch, localPlayer.Score);

			if (wonMatch)
			{
				PlatformManager.Achievements.RecordWinForLocalUser();
			}
		}

		void DisplayAchievementFlytext()
		{
			if (PlatformManager.Achievements.LikesToWin)
			{
				GameObject go = Instantiate(m_flytext);
				go.GetComponent<Text>().text = "Likes to Win!";
				go.transform.position = Vector3.up * 40;
				go.transform.SetParent(m_playerAreas[m_localSlot].NameText.transform, false);
			}
		}

		#endregion
	}
}