forked from cgvr/DeltaVR
		
	
		
			
				
	
	
		
			401 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			401 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
namespace Oculus.Platform.Samples.VrBoardGame
 | 
						|
{
 | 
						|
	using UnityEngine;
 | 
						|
	using Oculus.Platform;
 | 
						|
	using Oculus.Platform.Models;
 | 
						|
	using UnityEngine.UI;
 | 
						|
	using System.Collections.Generic;
 | 
						|
	using System;
 | 
						|
	using UnityEngine.Assertions;
 | 
						|
 | 
						|
	// This classes uses the Oculus Matchmaking Service to find opponents of a similar
 | 
						|
	// skill and play a match with them.  A skill pool is used with the matchmaking pool
 | 
						|
	// to coordinate the skill matching.  Follow the instructions in the Readme to setup
 | 
						|
	// the matchmaking pools.
 | 
						|
	// The Datastore for the Room is used to communicate between the clients.  This only
 | 
						|
	// works for relatively simple games with tolerance for latency.  For more complex
 | 
						|
	// or realtime requirements, you'll want to use the Oculus.Platform.Net API.
 | 
						|
	public class MatchmakingManager : MonoBehaviour
 | 
						|
	{
 | 
						|
		// GameController to notify about match completions or early endings
 | 
						|
		[SerializeField] private GameController m_gameController = null;
 | 
						|
 | 
						|
		// Text for the button that controls matchmaking
 | 
						|
		[SerializeField] private Text m_matchButtonText = null;
 | 
						|
 | 
						|
		// Test widget to render matmaking statistics
 | 
						|
		[SerializeField] private Text m_infoText = null;
 | 
						|
 | 
						|
		// name of the Quckmatch Pool configured on the Oculus Developer Dashboard
 | 
						|
		// which is expected to have an associated skill pool
 | 
						|
		private const string POOL = "VR_BOARD_GAME_POOL";
 | 
						|
 | 
						|
		// the ID of the room for the current match
 | 
						|
		private ulong m_matchRoom;
 | 
						|
 | 
						|
		// opponent User data
 | 
						|
		private User m_remotePlayer;
 | 
						|
 | 
						|
		// last time we've received a room update
 | 
						|
		private float m_lastUpdateTime;
 | 
						|
 | 
						|
		// how long to wait before polling for updates
 | 
						|
		private const float POLL_FREQUENCY = 30.0f;
 | 
						|
 | 
						|
		private enum MatchRoomState { None, Queued, Configuring, MyTurn, RemoteTurn }
 | 
						|
 | 
						|
		private MatchRoomState m_state;
 | 
						|
 | 
						|
		void Start()
 | 
						|
		{
 | 
						|
			Matchmaking.SetMatchFoundNotificationCallback(MatchFoundCallback);
 | 
						|
			Rooms.SetUpdateNotificationCallback(MatchmakingRoomUpdateCallback);
 | 
						|
 | 
						|
			TransitionToState(MatchRoomState.None);
 | 
						|
		}
 | 
						|
 | 
						|
		void Update()
 | 
						|
		{
 | 
						|
			switch (m_state)
 | 
						|
			{
 | 
						|
				case MatchRoomState.Configuring:
 | 
						|
				case MatchRoomState.MyTurn:
 | 
						|
				case MatchRoomState.RemoteTurn:
 | 
						|
					// if we're expecting an update form the remote player and we haven't
 | 
						|
					// heard from them in a while, check the datastore just-in-case
 | 
						|
					if (POLL_FREQUENCY < (Time.time - m_lastUpdateTime))
 | 
						|
					{
 | 
						|
						Debug.Log("Polling Room");
 | 
						|
						m_lastUpdateTime = Time.time;
 | 
						|
						Rooms.Get(m_matchRoom).OnComplete(MatchmakingRoomUpdateCallback);
 | 
						|
					}
 | 
						|
					break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		public void MatchButtonPressed()
 | 
						|
		{
 | 
						|
			switch (m_state)
 | 
						|
			{
 | 
						|
				case MatchRoomState.None:
 | 
						|
					TransitionToState(MatchRoomState.Queued);
 | 
						|
					break;
 | 
						|
 | 
						|
				default:
 | 
						|
					TransitionToState(MatchRoomState.None);
 | 
						|
					break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		public void EndMatch(int localScore, int remoteScore)
 | 
						|
		{
 | 
						|
			switch (m_state)
 | 
						|
			{
 | 
						|
				case MatchRoomState.MyTurn:
 | 
						|
				case MatchRoomState.RemoteTurn:
 | 
						|
					var myID = PlatformManager.MyID.ToString();
 | 
						|
					var remoteID = m_remotePlayer.ID.ToString();
 | 
						|
					var rankings = new Dictionary<string, int>();
 | 
						|
					if (localScore > remoteScore)
 | 
						|
					{
 | 
						|
						rankings[myID] = 1;
 | 
						|
						rankings[remoteID] = 2;
 | 
						|
					}
 | 
						|
					else if (localScore < remoteScore)
 | 
						|
					{
 | 
						|
						rankings[myID] = 2;
 | 
						|
						rankings[remoteID] = 1;
 | 
						|
					}
 | 
						|
					else
 | 
						|
					{
 | 
						|
						rankings[myID] = 1;
 | 
						|
						rankings[remoteID] = 1;
 | 
						|
					}
 | 
						|
 | 
						|
					// since there is no secure server to simulate the game and report
 | 
						|
					// verifiable results, each client needs to independently report their
 | 
						|
					// results for the service to compate for inconsistencies
 | 
						|
					Matchmaking.ReportResultsInsecure(m_matchRoom, rankings)
 | 
						|
						.OnComplete(GenericErrorCheckCallback);
 | 
						|
					break;
 | 
						|
			}
 | 
						|
 | 
						|
			TransitionToState(MatchRoomState.None);
 | 
						|
		}
 | 
						|
 | 
						|
		void OnApplicationQuit()
 | 
						|
		{
 | 
						|
			// be a good matchmaking citizen and leave any queue immediately
 | 
						|
			Matchmaking.Cancel();
 | 
						|
			if (m_matchRoom != 0)
 | 
						|
			{
 | 
						|
				Rooms.Leave(m_matchRoom);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		private void TransitionToState(MatchRoomState state)
 | 
						|
		{
 | 
						|
			var m_oldState = m_state;
 | 
						|
			m_state = state;
 | 
						|
 | 
						|
			switch (m_state)
 | 
						|
			{
 | 
						|
				case MatchRoomState.None:
 | 
						|
					m_matchButtonText.text = "Find Match";
 | 
						|
					// the player can abort from any of the other states to the None state
 | 
						|
					// so we need to be careful to clean up all state variables
 | 
						|
					m_remotePlayer = null;
 | 
						|
					Matchmaking.Cancel();
 | 
						|
					if (m_matchRoom != 0)
 | 
						|
					{
 | 
						|
						Rooms.Leave(m_matchRoom);
 | 
						|
						m_matchRoom = 0;
 | 
						|
					}
 | 
						|
					break;
 | 
						|
 | 
						|
				case MatchRoomState.Queued:
 | 
						|
					Assert.AreEqual(MatchRoomState.None, m_oldState);
 | 
						|
					m_matchButtonText.text = "Leave Queue";
 | 
						|
					Matchmaking.Enqueue2(POOL).OnComplete(MatchmakingEnqueueCallback);
 | 
						|
					break;
 | 
						|
 | 
						|
				case MatchRoomState.Configuring:
 | 
						|
					Assert.AreEqual(MatchRoomState.Queued, m_oldState);
 | 
						|
					m_matchButtonText.text = "Cancel Match";
 | 
						|
					break;
 | 
						|
 | 
						|
				case MatchRoomState.MyTurn:
 | 
						|
				case MatchRoomState.RemoteTurn:
 | 
						|
					Assert.AreNotEqual(MatchRoomState.None, m_oldState);
 | 
						|
					Assert.AreNotEqual(MatchRoomState.Queued, m_oldState);
 | 
						|
					m_matchButtonText.text = "Cancel Match";
 | 
						|
					break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		void MatchmakingEnqueueCallback(Message untyped_msg)
 | 
						|
		{
 | 
						|
			if (untyped_msg.IsError)
 | 
						|
			{
 | 
						|
				Debug.Log(untyped_msg.GetError().Message);
 | 
						|
				TransitionToState(MatchRoomState.None);
 | 
						|
				return;
 | 
						|
			}
 | 
						|
 | 
						|
			Message<MatchmakingEnqueueResult> msg = (Message<MatchmakingEnqueueResult>)untyped_msg;
 | 
						|
			MatchmakingEnqueueResult info = msg.Data;
 | 
						|
			m_infoText.text = string.Format(
 | 
						|
				"Avg Wait Time: {0}s\n" +
 | 
						|
				"Max Expected Wait: {1}s\n" +
 | 
						|
				"In Last Hour: {2}\n" +
 | 
						|
				"Recent Percentage: {3}%",
 | 
						|
				info.AverageWait, info.MaxExpectedWait, info.MatchesInLastHourCount,
 | 
						|
				info.RecentMatchPercentage);
 | 
						|
		}
 | 
						|
 | 
						|
		void MatchFoundCallback(Message<Room> msg)
 | 
						|
		{
 | 
						|
			if (msg.IsError)
 | 
						|
			{
 | 
						|
				Debug.Log(msg.GetError().Message);
 | 
						|
				TransitionToState(MatchRoomState.None);
 | 
						|
				return;
 | 
						|
			}
 | 
						|
 | 
						|
			if (m_state != MatchRoomState.Queued)
 | 
						|
			{
 | 
						|
				// ignore callback - user already cancelled
 | 
						|
				return;
 | 
						|
			}
 | 
						|
 | 
						|
			// since this example communicates via updates to the datastore, it's vital that
 | 
						|
			// we subscribe to room updates
 | 
						|
			Matchmaking.JoinRoom(msg.Data.ID, true /* subscribe to update notifications */)
 | 
						|
				.OnComplete(MatchmakingJoinRoomCallback);
 | 
						|
			m_matchRoom = msg.Data.ID;
 | 
						|
		}
 | 
						|
 | 
						|
		void MatchmakingJoinRoomCallback(Message<Room> msg)
 | 
						|
		{
 | 
						|
			if (msg.IsError)
 | 
						|
			{
 | 
						|
				Debug.Log(msg.GetError().Message);
 | 
						|
				TransitionToState(MatchRoomState.None);
 | 
						|
				return;
 | 
						|
			}
 | 
						|
 | 
						|
			if (m_state != MatchRoomState.Queued)
 | 
						|
			{
 | 
						|
				// ignore callback - user already cancelled
 | 
						|
				return;
 | 
						|
			}
 | 
						|
 | 
						|
			int numUsers = (msg.Data.UsersOptional != null) ? msg.Data.UsersOptional.Count : 0;
 | 
						|
			Debug.Log ("Match room joined: " + m_matchRoom + " count: " + numUsers);
 | 
						|
 | 
						|
			TransitionToState(MatchRoomState.Configuring);
 | 
						|
 | 
						|
			// only process the room data if the other user has already joined
 | 
						|
			if (msg.Data.UsersOptional != null && msg.Data.UsersOptional.Count == 2)
 | 
						|
			{
 | 
						|
				ProcessRoomData(msg.Data);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Room Datastore updates are used to send moves between players.  So if the MatchRoomState
 | 
						|
		// is RemoteTurn I'm looking for the other player's move in the Datastore.  If the
 | 
						|
		// MatchRoomState is MyTurn I'm waiting for the room ownership to change so that
 | 
						|
		// I have authority to write to the datastore.
 | 
						|
		void MatchmakingRoomUpdateCallback(Message<Room> msg)
 | 
						|
		{
 | 
						|
			if (msg.IsError)
 | 
						|
			{
 | 
						|
				Debug.Log(msg.GetError().Message);
 | 
						|
				TransitionToState(MatchRoomState.None);
 | 
						|
				return;
 | 
						|
			}
 | 
						|
 | 
						|
			string ownerOculusID = msg.Data.OwnerOptional != null ? msg.Data.OwnerOptional.OculusID : "";
 | 
						|
			int numUsers = (msg.Data.UsersOptional != null) ? msg.Data.UsersOptional.Count : 0;
 | 
						|
 | 
						|
			Debug.LogFormat(
 | 
						|
				"Room Update {0}\n" +
 | 
						|
				"  Owner {1}\n" +
 | 
						|
				"  User Count {2}\n" +
 | 
						|
				"  Datastore Count {3}\n",
 | 
						|
				msg.Data.ID, ownerOculusID, numUsers, msg.Data.DataStore.Count);
 | 
						|
 | 
						|
			// check to make sure the room is valid as there are a few odd timing issues (for
 | 
						|
			// example when leaving a room) that can trigger an uninteresting update
 | 
						|
			if (msg.Data.ID != m_matchRoom)
 | 
						|
			{
 | 
						|
				Debug.Log("Unexpected room update from: " + msg.Data.ID);
 | 
						|
				return;
 | 
						|
			}
 | 
						|
 | 
						|
			ProcessRoomData(msg.Data);
 | 
						|
		}
 | 
						|
 | 
						|
		private void ProcessRoomData(Room room)
 | 
						|
		{
 | 
						|
			m_lastUpdateTime = Time.time;
 | 
						|
 | 
						|
			if (m_state == MatchRoomState.Configuring)
 | 
						|
			{
 | 
						|
				// get the User info for the other player
 | 
						|
				if (room.UsersOptional != null)
 | 
						|
				{
 | 
						|
					foreach (var user in room.UsersOptional)
 | 
						|
					{
 | 
						|
						if (PlatformManager.MyID != user.ID)
 | 
						|
						{
 | 
						|
							Debug.Log("Found remote user: " + user.OculusID);
 | 
						|
							m_remotePlayer = user;
 | 
						|
							break;
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				if (m_remotePlayer == null)
 | 
						|
					return;
 | 
						|
 | 
						|
				bool i_go_first = DoesLocalUserGoFirst();
 | 
						|
				TransitionToState(i_go_first ? MatchRoomState.MyTurn : MatchRoomState.RemoteTurn);
 | 
						|
				Matchmaking.StartMatch(m_matchRoom).OnComplete(GenericErrorCheckCallback);
 | 
						|
				m_gameController.StartOnlineMatch(m_remotePlayer.OculusID, i_go_first);
 | 
						|
			}
 | 
						|
 | 
						|
			// if it's the remote player's turn, look for their move in the datastore
 | 
						|
			if (m_state == MatchRoomState.RemoteTurn &&
 | 
						|
				room.DataStore.ContainsKey(m_remotePlayer.OculusID) &&
 | 
						|
				room.DataStore[m_remotePlayer.OculusID] != "")
 | 
						|
			{
 | 
						|
				// process remote move
 | 
						|
				ProcessRemoteMove(room.DataStore[m_remotePlayer.OculusID]);
 | 
						|
				TransitionToState(MatchRoomState.MyTurn);
 | 
						|
			}
 | 
						|
 | 
						|
			// If the room ownership transferred to me, we can mark the remote turn complete.
 | 
						|
			// We don't do this when the remote move comes in if we aren't yet the owner because
 | 
						|
			// the local user will not be able to write to the datastore if they aren't the
 | 
						|
			// owner of the room.
 | 
						|
			if (m_state == MatchRoomState.MyTurn && room.OwnerOptional != null && room.OwnerOptional.ID == PlatformManager.MyID)
 | 
						|
			{
 | 
						|
				m_gameController.MarkRemoteTurnComplete();
 | 
						|
			}
 | 
						|
 | 
						|
			if (room.UsersOptional == null || (room.UsersOptional != null && room.UsersOptional.Count != 2))
 | 
						|
			{
 | 
						|
				Debug.Log("Other user quit the room");
 | 
						|
				m_gameController.RemoteMatchEnded();
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		private void ProcessRemoteMove(string moveString)
 | 
						|
		{
 | 
						|
			Debug.Log("Processing remote move string: " + moveString);
 | 
						|
			string[] tokens = moveString.Split(':');
 | 
						|
 | 
						|
			GamePiece.Piece piece = (GamePiece.Piece)Enum.Parse(typeof(GamePiece.Piece), tokens[0]);
 | 
						|
			int x = Int32.Parse(tokens[1]);
 | 
						|
			int y = Int32.Parse(tokens[2]);
 | 
						|
 | 
						|
			// swap the coordinates since each player assumes they are player 0
 | 
						|
			x = GameBoard.LENGTH_X-1 - x;
 | 
						|
			y = GameBoard.LENGTH_Y-1 - y;
 | 
						|
 | 
						|
			m_gameController.MakeRemoteMove(piece, x, y);
 | 
						|
		}
 | 
						|
 | 
						|
		public void SendLocalMove(GamePiece.Piece piece, int boardX, int boardY)
 | 
						|
		{
 | 
						|
			string moveString = string.Format("{0}:{1}:{2}", piece.ToString(), boardX, boardY);
 | 
						|
			Debug.Log("Sending move: " + moveString);
 | 
						|
 | 
						|
			var dict = new Dictionary<string, string>();
 | 
						|
			dict[PlatformManager.MyOculusID] = moveString;
 | 
						|
			dict[m_remotePlayer.OculusID] = "";
 | 
						|
 | 
						|
			Rooms.UpdateDataStore(m_matchRoom, dict).OnComplete(UpdateDataStoreCallback);
 | 
						|
			TransitionToState(MatchRoomState.RemoteTurn);
 | 
						|
		}
 | 
						|
 | 
						|
		private void UpdateDataStoreCallback(Message<Room> msg)
 | 
						|
		{
 | 
						|
			if (m_state != MatchRoomState.RemoteTurn)
 | 
						|
			{
 | 
						|
				// ignore calback - user already quit the match
 | 
						|
				return;
 | 
						|
			}
 | 
						|
 | 
						|
			// after I've updated the datastore with my move, change ownership so the other
 | 
						|
			// user can perform their move
 | 
						|
			Rooms.UpdateOwner(m_matchRoom, m_remotePlayer.ID);
 | 
						|
		}
 | 
						|
 | 
						|
		// deterministic but somewhat random selection for who goes first
 | 
						|
		private bool DoesLocalUserGoFirst()
 | 
						|
		{
 | 
						|
			// if the room ID is even, the lower ID goes first
 | 
						|
			if (m_matchRoom % 2 == 0)
 | 
						|
			{
 | 
						|
				return PlatformManager.MyID < m_remotePlayer.ID;
 | 
						|
			}
 | 
						|
			// otherwise the higher ID goes first
 | 
						|
			{
 | 
						|
				return PlatformManager.MyID > m_remotePlayer.ID;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		private void GenericErrorCheckCallback(Message msg)
 | 
						|
		{
 | 
						|
			if (msg.IsError)
 | 
						|
			{
 | 
						|
				Debug.Log(msg.GetError().Message);
 | 
						|
				TransitionToState(MatchRoomState.None);
 | 
						|
				return;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 |