forked from cgvr/DeltaVR
		
	
		
			
				
	
	
		
			473 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			473 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
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
 | 
						|
	}
 | 
						|
}
 |