CanvasController.cs 24 KB

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