CanvasController.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using UnityEngine;
  6. using UnityEngine.UI;
  7. namespace Mirror.Examples.MultipleMatch
  8. {
  9. public class CanvasController : MonoBehaviour
  10. {
  11. /// <summary>
  12. /// Match Controllers listen for this to terminate their match and clean up
  13. /// </summary>
  14. public event Action<NetworkConnectionToClient> OnPlayerDisconnected;
  15. /// <summary>
  16. /// Cross-reference of client that created the corresponding match in openMatches below
  17. /// </summary>
  18. internal static readonly Dictionary<NetworkConnectionToClient, Guid> playerMatches = new Dictionary<NetworkConnectionToClient, Guid>();
  19. /// <summary>
  20. /// Open matches that are available for joining
  21. /// </summary>
  22. internal static readonly Dictionary<Guid, MatchInfo> openMatches = new Dictionary<Guid, MatchInfo>();
  23. /// <summary>
  24. /// Network Connections of all players in a match
  25. /// </summary>
  26. internal static readonly Dictionary<Guid, HashSet<NetworkConnectionToClient>> matchConnections = new Dictionary<Guid, HashSet<NetworkConnectionToClient>>();
  27. /// <summary>
  28. /// Player informations by Network Connection
  29. /// </summary>
  30. internal static readonly Dictionary<NetworkConnection, PlayerInfo> playerInfos = new Dictionary<NetworkConnection, PlayerInfo>();
  31. /// <summary>
  32. /// Network Connections that have neither started nor joined a match yet
  33. /// </summary>
  34. internal static readonly List<NetworkConnectionToClient> waitingConnections = new List<NetworkConnectionToClient>();
  35. /// <summary>
  36. /// GUID of a match the local player has created
  37. /// </summary>
  38. internal Guid localPlayerMatch = Guid.Empty;
  39. /// <summary>
  40. /// GUID of a match the local player has joined
  41. /// </summary>
  42. internal Guid localJoinedMatch = Guid.Empty;
  43. /// <summary>
  44. /// GUID of a match the local player has selected in the Toggle Group match list
  45. /// </summary>
  46. internal Guid selectedMatch = Guid.Empty;
  47. // Used in UI for "Player #"
  48. int playerIndex = 1;
  49. [Header("GUI References")]
  50. public GameObject matchList;
  51. public GameObject matchPrefab;
  52. public GameObject matchControllerPrefab;
  53. public Button createButton;
  54. public Button joinButton;
  55. public GameObject lobbyView;
  56. public GameObject roomView;
  57. public RoomGUI roomGUI;
  58. public ToggleGroup toggleGroup;
  59. // RuntimeInitializeOnLoadMethod -> fast playmode without domain reload
  60. [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
  61. static void ResetStatics()
  62. {
  63. playerMatches.Clear();
  64. openMatches.Clear();
  65. matchConnections.Clear();
  66. playerInfos.Clear();
  67. waitingConnections.Clear();
  68. }
  69. #region UI Functions
  70. // Called from several places to ensure a clean reset
  71. // - MatchNetworkManager.Awake
  72. // - OnStartServer
  73. // - OnStartClient
  74. // - OnClientDisconnect
  75. // - ResetCanvas
  76. internal void InitializeData()
  77. {
  78. playerMatches.Clear();
  79. openMatches.Clear();
  80. matchConnections.Clear();
  81. waitingConnections.Clear();
  82. localPlayerMatch = Guid.Empty;
  83. localJoinedMatch = Guid.Empty;
  84. }
  85. // Called from OnStopServer and OnStopClient when shutting down
  86. void ResetCanvas()
  87. {
  88. InitializeData();
  89. lobbyView.SetActive(false);
  90. roomView.SetActive(false);
  91. gameObject.SetActive(false);
  92. }
  93. #endregion
  94. #region Button Calls
  95. /// <summary>
  96. /// Called from <see cref="MatchGUI.OnToggleClicked"/>
  97. /// </summary>
  98. /// <param name="matchId"></param>
  99. [ClientCallback]
  100. public void SelectMatch(Guid matchId)
  101. {
  102. if (matchId == Guid.Empty)
  103. {
  104. selectedMatch = Guid.Empty;
  105. joinButton.interactable = false;
  106. }
  107. else
  108. {
  109. if (!openMatches.Keys.Contains(matchId))
  110. {
  111. joinButton.interactable = false;
  112. return;
  113. }
  114. selectedMatch = matchId;
  115. MatchInfo infos = openMatches[matchId];
  116. joinButton.interactable = infos.players < infos.maxPlayers;
  117. }
  118. }
  119. /// <summary>
  120. /// Assigned in inspector to Create button
  121. /// </summary>
  122. [ClientCallback]
  123. public void RequestCreateMatch()
  124. {
  125. NetworkClient.Send(new ServerMatchMessage { serverMatchOperation = ServerMatchOperation.Create });
  126. }
  127. /// <summary>
  128. /// Assigned in inspector to Cancel button
  129. /// </summary>
  130. [ClientCallback]
  131. public void RequestCancelMatch()
  132. {
  133. if (localPlayerMatch == Guid.Empty) return;
  134. NetworkClient.Send(new ServerMatchMessage { serverMatchOperation = ServerMatchOperation.Cancel });
  135. }
  136. /// <summary>
  137. /// Assigned in inspector to Join button
  138. /// </summary>
  139. [ClientCallback]
  140. public void RequestJoinMatch()
  141. {
  142. if (selectedMatch == Guid.Empty) return;
  143. NetworkClient.Send(new ServerMatchMessage { serverMatchOperation = ServerMatchOperation.Join, matchId = selectedMatch });
  144. }
  145. /// <summary>
  146. /// Assigned in inspector to Leave button
  147. /// </summary>
  148. [ClientCallback]
  149. public void RequestLeaveMatch()
  150. {
  151. if (localJoinedMatch == Guid.Empty) return;
  152. NetworkClient.Send(new ServerMatchMessage { serverMatchOperation = ServerMatchOperation.Leave, matchId = localJoinedMatch });
  153. }
  154. /// <summary>
  155. /// Assigned in inspector to Ready button
  156. /// </summary>
  157. [ClientCallback]
  158. public void RequestReadyChange()
  159. {
  160. if (localPlayerMatch == Guid.Empty && localJoinedMatch == Guid.Empty) return;
  161. Guid matchId = localPlayerMatch == Guid.Empty ? localJoinedMatch : localPlayerMatch;
  162. NetworkClient.Send(new ServerMatchMessage { serverMatchOperation = ServerMatchOperation.Ready, matchId = matchId });
  163. }
  164. /// <summary>
  165. /// Assigned in inspector to Start button
  166. /// </summary>
  167. [ClientCallback]
  168. public void RequestStartMatch()
  169. {
  170. if (localPlayerMatch == Guid.Empty) return;
  171. NetworkClient.Send(new ServerMatchMessage { serverMatchOperation = ServerMatchOperation.Start });
  172. }
  173. /// <summary>
  174. /// Called from <see cref="MatchController.RpcExitGame"/>
  175. /// </summary>
  176. [ClientCallback]
  177. public void OnMatchEnded()
  178. {
  179. localPlayerMatch = Guid.Empty;
  180. localJoinedMatch = Guid.Empty;
  181. ShowLobbyView();
  182. }
  183. #endregion
  184. #region Server & Client Callbacks
  185. // Methods in this section are called from MatchNetworkManager's corresponding methods
  186. [ServerCallback]
  187. internal void OnStartServer()
  188. {
  189. InitializeData();
  190. NetworkServer.RegisterHandler<ServerMatchMessage>(OnServerMatchMessage);
  191. }
  192. [ServerCallback]
  193. internal void OnServerReady(NetworkConnectionToClient conn)
  194. {
  195. waitingConnections.Add(conn);
  196. playerInfos.Add(conn, new PlayerInfo { playerIndex = this.playerIndex, ready = false });
  197. playerIndex++;
  198. SendMatchList();
  199. }
  200. [ServerCallback]
  201. internal IEnumerator OnServerDisconnect(NetworkConnectionToClient conn)
  202. {
  203. // Invoke OnPlayerDisconnected on all instances of MatchController
  204. OnPlayerDisconnected?.Invoke(conn);
  205. if (playerMatches.TryGetValue(conn, out Guid matchId))
  206. {
  207. playerMatches.Remove(conn);
  208. openMatches.Remove(matchId);
  209. foreach (NetworkConnectionToClient playerConn in matchConnections[matchId])
  210. {
  211. PlayerInfo _playerInfo = playerInfos[playerConn];
  212. _playerInfo.ready = false;
  213. _playerInfo.matchId = Guid.Empty;
  214. playerInfos[playerConn] = _playerInfo;
  215. playerConn.Send(new ClientMatchMessage { clientMatchOperation = ClientMatchOperation.Departed });
  216. }
  217. }
  218. foreach (KeyValuePair<Guid, HashSet<NetworkConnectionToClient>> kvp in matchConnections)
  219. kvp.Value.Remove(conn);
  220. PlayerInfo playerInfo = playerInfos[conn];
  221. if (playerInfo.matchId != Guid.Empty)
  222. {
  223. if (openMatches.TryGetValue(playerInfo.matchId, out MatchInfo matchInfo))
  224. {
  225. matchInfo.players--;
  226. openMatches[playerInfo.matchId] = matchInfo;
  227. }
  228. HashSet<NetworkConnectionToClient> connections;
  229. if (matchConnections.TryGetValue(playerInfo.matchId, out connections))
  230. {
  231. PlayerInfo[] infos = connections.Select(playerConn => playerInfos[playerConn]).ToArray();
  232. foreach (NetworkConnectionToClient playerConn in matchConnections[playerInfo.matchId])
  233. if (playerConn != conn)
  234. playerConn.Send(new ClientMatchMessage { clientMatchOperation = ClientMatchOperation.UpdateRoom, playerInfos = infos });
  235. }
  236. }
  237. SendMatchList();
  238. yield return null;
  239. }
  240. [ServerCallback]
  241. internal void OnStopServer()
  242. {
  243. ResetCanvas();
  244. }
  245. [ClientCallback]
  246. internal void OnClientConnect()
  247. {
  248. playerInfos.Add(NetworkClient.connection, new PlayerInfo { playerIndex = this.playerIndex, ready = false });
  249. }
  250. [ClientCallback]
  251. internal void OnStartClient()
  252. {
  253. InitializeData();
  254. ShowLobbyView();
  255. createButton.gameObject.SetActive(true);
  256. joinButton.gameObject.SetActive(true);
  257. NetworkClient.RegisterHandler<ClientMatchMessage>(OnClientMatchMessage);
  258. }
  259. [ClientCallback]
  260. internal void OnClientDisconnect()
  261. {
  262. InitializeData();
  263. }
  264. [ClientCallback]
  265. internal void OnStopClient()
  266. {
  267. ResetCanvas();
  268. }
  269. #endregion
  270. #region Server Match Message Handlers
  271. [ServerCallback]
  272. void OnServerMatchMessage(NetworkConnectionToClient conn, ServerMatchMessage msg)
  273. {
  274. switch (msg.serverMatchOperation)
  275. {
  276. case ServerMatchOperation.None:
  277. {
  278. Debug.LogWarning("Missing ServerMatchOperation");
  279. break;
  280. }
  281. case ServerMatchOperation.Create:
  282. {
  283. OnServerCreateMatch(conn);
  284. break;
  285. }
  286. case ServerMatchOperation.Cancel:
  287. {
  288. OnServerCancelMatch(conn);
  289. break;
  290. }
  291. case ServerMatchOperation.Join:
  292. {
  293. OnServerJoinMatch(conn, msg.matchId);
  294. break;
  295. }
  296. case ServerMatchOperation.Leave:
  297. {
  298. OnServerLeaveMatch(conn, msg.matchId);
  299. break;
  300. }
  301. case ServerMatchOperation.Ready:
  302. {
  303. OnServerPlayerReady(conn, msg.matchId);
  304. break;
  305. }
  306. case ServerMatchOperation.Start:
  307. {
  308. OnServerStartMatch(conn);
  309. break;
  310. }
  311. }
  312. }
  313. [ServerCallback]
  314. void OnServerCreateMatch(NetworkConnectionToClient conn)
  315. {
  316. if (playerMatches.ContainsKey(conn)) return;
  317. Guid newMatchId = Guid.NewGuid();
  318. matchConnections.Add(newMatchId, new HashSet<NetworkConnectionToClient>());
  319. matchConnections[newMatchId].Add(conn);
  320. playerMatches.Add(conn, newMatchId);
  321. openMatches.Add(newMatchId, new MatchInfo { matchId = newMatchId, maxPlayers = 2, players = 1 });
  322. PlayerInfo playerInfo = playerInfos[conn];
  323. playerInfo.ready = false;
  324. playerInfo.matchId = newMatchId;
  325. playerInfos[conn] = playerInfo;
  326. PlayerInfo[] infos = matchConnections[newMatchId].Select(playerConn => playerInfos[playerConn]).ToArray();
  327. conn.Send(new ClientMatchMessage { clientMatchOperation = ClientMatchOperation.Created, matchId = newMatchId, playerInfos = infos });
  328. SendMatchList();
  329. }
  330. [ServerCallback]
  331. void OnServerCancelMatch(NetworkConnectionToClient conn)
  332. {
  333. if (!playerMatches.ContainsKey(conn)) return;
  334. conn.Send(new ClientMatchMessage { clientMatchOperation = ClientMatchOperation.Cancelled });
  335. Guid matchId;
  336. if (playerMatches.TryGetValue(conn, out matchId))
  337. {
  338. playerMatches.Remove(conn);
  339. openMatches.Remove(matchId);
  340. foreach (NetworkConnectionToClient playerConn in matchConnections[matchId])
  341. {
  342. PlayerInfo playerInfo = playerInfos[playerConn];
  343. playerInfo.ready = false;
  344. playerInfo.matchId = Guid.Empty;
  345. playerInfos[playerConn] = playerInfo;
  346. playerConn.Send(new ClientMatchMessage { clientMatchOperation = ClientMatchOperation.Departed });
  347. }
  348. SendMatchList();
  349. }
  350. }
  351. [ServerCallback]
  352. void OnServerJoinMatch(NetworkConnectionToClient conn, Guid matchId)
  353. {
  354. if (!matchConnections.ContainsKey(matchId) || !openMatches.ContainsKey(matchId)) return;
  355. MatchInfo matchInfo = openMatches[matchId];
  356. matchInfo.players++;
  357. openMatches[matchId] = matchInfo;
  358. matchConnections[matchId].Add(conn);
  359. PlayerInfo playerInfo = playerInfos[conn];
  360. playerInfo.ready = false;
  361. playerInfo.matchId = matchId;
  362. playerInfos[conn] = playerInfo;
  363. PlayerInfo[] infos = matchConnections[matchId].Select(playerConn => playerInfos[playerConn]).ToArray();
  364. SendMatchList();
  365. conn.Send(new ClientMatchMessage { clientMatchOperation = ClientMatchOperation.Joined, matchId = matchId, playerInfos = infos });
  366. foreach (NetworkConnectionToClient playerConn in matchConnections[matchId])
  367. playerConn.Send(new ClientMatchMessage { clientMatchOperation = ClientMatchOperation.UpdateRoom, playerInfos = infos });
  368. }
  369. [ServerCallback]
  370. void OnServerLeaveMatch(NetworkConnectionToClient conn, Guid matchId)
  371. {
  372. MatchInfo matchInfo = openMatches[matchId];
  373. matchInfo.players--;
  374. openMatches[matchId] = matchInfo;
  375. PlayerInfo playerInfo = playerInfos[conn];
  376. playerInfo.ready = false;
  377. playerInfo.matchId = Guid.Empty;
  378. playerInfos[conn] = playerInfo;
  379. foreach (KeyValuePair<Guid, HashSet<NetworkConnectionToClient>> kvp in matchConnections)
  380. kvp.Value.Remove(conn);
  381. HashSet<NetworkConnectionToClient> connections = matchConnections[matchId];
  382. PlayerInfo[] infos = connections.Select(playerConn => playerInfos[playerConn]).ToArray();
  383. foreach (NetworkConnectionToClient playerConn in matchConnections[matchId])
  384. playerConn.Send(new ClientMatchMessage { clientMatchOperation = ClientMatchOperation.UpdateRoom, playerInfos = infos });
  385. SendMatchList();
  386. conn.Send(new ClientMatchMessage { clientMatchOperation = ClientMatchOperation.Departed });
  387. }
  388. [ServerCallback]
  389. void OnServerPlayerReady(NetworkConnectionToClient conn, Guid matchId)
  390. {
  391. PlayerInfo playerInfo = playerInfos[conn];
  392. playerInfo.ready = !playerInfo.ready;
  393. playerInfos[conn] = playerInfo;
  394. HashSet<NetworkConnectionToClient> connections = matchConnections[matchId];
  395. PlayerInfo[] infos = connections.Select(playerConn => playerInfos[playerConn]).ToArray();
  396. foreach (NetworkConnectionToClient playerConn in matchConnections[matchId])
  397. playerConn.Send(new ClientMatchMessage { clientMatchOperation = ClientMatchOperation.UpdateRoom, playerInfos = infos });
  398. }
  399. [ServerCallback]
  400. void OnServerStartMatch(NetworkConnectionToClient conn)
  401. {
  402. if (!playerMatches.ContainsKey(conn)) return;
  403. Guid matchId;
  404. if (playerMatches.TryGetValue(conn, out matchId))
  405. {
  406. GameObject matchControllerObject = Instantiate(matchControllerPrefab);
  407. matchControllerObject.GetComponent<NetworkMatch>().matchId = matchId;
  408. NetworkServer.Spawn(matchControllerObject);
  409. MatchController matchController = matchControllerObject.GetComponent<MatchController>();
  410. foreach (NetworkConnectionToClient playerConn in matchConnections[matchId])
  411. {
  412. playerConn.Send(new ClientMatchMessage { clientMatchOperation = ClientMatchOperation.Started });
  413. GameObject player = Instantiate(NetworkManager.singleton.playerPrefab);
  414. player.GetComponent<NetworkMatch>().matchId = matchId;
  415. NetworkServer.AddPlayerForConnection(playerConn, player);
  416. if (matchController.player1 == null)
  417. matchController.player1 = playerConn.identity;
  418. else
  419. matchController.player2 = playerConn.identity;
  420. /* Reset ready state for after the match. */
  421. PlayerInfo playerInfo = playerInfos[playerConn];
  422. playerInfo.ready = false;
  423. playerInfos[playerConn] = playerInfo;
  424. }
  425. matchController.startingPlayer = matchController.player1;
  426. matchController.currentPlayer = matchController.player1;
  427. playerMatches.Remove(conn);
  428. openMatches.Remove(matchId);
  429. matchConnections.Remove(matchId);
  430. SendMatchList();
  431. OnPlayerDisconnected += matchController.OnPlayerDisconnected;
  432. }
  433. }
  434. /// <summary>
  435. /// Sends updated match list to all waiting connections or just one if specified
  436. /// </summary>
  437. /// <param name="conn"></param>
  438. [ServerCallback]
  439. internal void SendMatchList(NetworkConnectionToClient conn = null)
  440. {
  441. if (conn != null)
  442. conn.Send(new ClientMatchMessage { clientMatchOperation = ClientMatchOperation.List, matchInfos = openMatches.Values.ToArray() });
  443. else
  444. foreach (NetworkConnectionToClient waiter in waitingConnections)
  445. waiter.Send(new ClientMatchMessage { clientMatchOperation = ClientMatchOperation.List, matchInfos = openMatches.Values.ToArray() });
  446. }
  447. #endregion
  448. #region Client Match Message Handler
  449. [ClientCallback]
  450. void OnClientMatchMessage(ClientMatchMessage msg)
  451. {
  452. switch (msg.clientMatchOperation)
  453. {
  454. case ClientMatchOperation.None:
  455. {
  456. Debug.LogWarning("Missing ClientMatchOperation");
  457. break;
  458. }
  459. case ClientMatchOperation.List:
  460. {
  461. openMatches.Clear();
  462. foreach (MatchInfo matchInfo in msg.matchInfos)
  463. openMatches.Add(matchInfo.matchId, matchInfo);
  464. RefreshMatchList();
  465. break;
  466. }
  467. case ClientMatchOperation.Created:
  468. {
  469. localPlayerMatch = msg.matchId;
  470. ShowRoomView();
  471. roomGUI.RefreshRoomPlayers(msg.playerInfos);
  472. roomGUI.SetOwner(true);
  473. break;
  474. }
  475. case ClientMatchOperation.Cancelled:
  476. {
  477. localPlayerMatch = Guid.Empty;
  478. ShowLobbyView();
  479. break;
  480. }
  481. case ClientMatchOperation.Joined:
  482. {
  483. localJoinedMatch = msg.matchId;
  484. ShowRoomView();
  485. roomGUI.RefreshRoomPlayers(msg.playerInfos);
  486. roomGUI.SetOwner(false);
  487. break;
  488. }
  489. case ClientMatchOperation.Departed:
  490. {
  491. localJoinedMatch = Guid.Empty;
  492. ShowLobbyView();
  493. break;
  494. }
  495. case ClientMatchOperation.UpdateRoom:
  496. {
  497. roomGUI.RefreshRoomPlayers(msg.playerInfos);
  498. break;
  499. }
  500. case ClientMatchOperation.Started:
  501. {
  502. lobbyView.SetActive(false);
  503. roomView.SetActive(false);
  504. break;
  505. }
  506. }
  507. }
  508. [ClientCallback]
  509. void ShowLobbyView()
  510. {
  511. lobbyView.SetActive(true);
  512. roomView.SetActive(false);
  513. foreach (Transform child in matchList.transform)
  514. if (child.gameObject.GetComponent<MatchGUI>().GetMatchId() == selectedMatch)
  515. {
  516. Toggle toggle = child.gameObject.GetComponent<Toggle>();
  517. toggle.isOn = true;
  518. }
  519. }
  520. [ClientCallback]
  521. void ShowRoomView()
  522. {
  523. lobbyView.SetActive(false);
  524. roomView.SetActive(true);
  525. }
  526. [ClientCallback]
  527. void RefreshMatchList()
  528. {
  529. foreach (Transform child in matchList.transform)
  530. Destroy(child.gameObject);
  531. joinButton.interactable = false;
  532. foreach (MatchInfo matchInfo in openMatches.Values)
  533. {
  534. GameObject newMatch = Instantiate(matchPrefab, Vector3.zero, Quaternion.identity);
  535. newMatch.transform.SetParent(matchList.transform, false);
  536. newMatch.GetComponent<MatchGUI>().SetMatchInfo(matchInfo);
  537. Toggle toggle = newMatch.GetComponent<Toggle>();
  538. toggle.group = toggleGroup;
  539. if (matchInfo.matchId == selectedMatch)
  540. toggle.isOn = true;
  541. }
  542. }
  543. #endregion
  544. }
  545. }