315 lines
11 KiB
C#
315 lines
11 KiB
C#
|
using System.Collections;
|
|||
|
using System.Collections.Generic;
|
|||
|
using UnityEngine;
|
|||
|
using UnityEngine.UI;
|
|||
|
|
|||
|
namespace Mirror.Examples.MultipleMatch
|
|||
|
{
|
|||
|
[RequireComponent(typeof(NetworkMatch))]
|
|||
|
public class MatchController : NetworkBehaviour
|
|||
|
{
|
|||
|
internal readonly SyncDictionary<NetworkIdentity, MatchPlayerData> matchPlayerData = new SyncDictionary<NetworkIdentity, MatchPlayerData>();
|
|||
|
internal readonly Dictionary<CellValue, CellGUI> MatchCells = new Dictionary<CellValue, CellGUI>();
|
|||
|
|
|||
|
CellValue boardScore = CellValue.None;
|
|||
|
bool playAgain = false;
|
|||
|
|
|||
|
[Header("GUI References")]
|
|||
|
public CanvasGroup canvasGroup;
|
|||
|
public Text gameText;
|
|||
|
public Button exitButton;
|
|||
|
public Button playAgainButton;
|
|||
|
public Text winCountLocal;
|
|||
|
public Text winCountOpponent;
|
|||
|
|
|||
|
[Header("Diagnostics")]
|
|||
|
[ReadOnly, SerializeField] internal CanvasController canvasController;
|
|||
|
[ReadOnly, SerializeField] internal NetworkIdentity player1;
|
|||
|
[ReadOnly, SerializeField] internal NetworkIdentity player2;
|
|||
|
[ReadOnly, SerializeField] internal NetworkIdentity startingPlayer;
|
|||
|
|
|||
|
[SyncVar(hook = nameof(UpdateGameUI))]
|
|||
|
[ReadOnly, SerializeField] internal NetworkIdentity currentPlayer;
|
|||
|
|
|||
|
void Awake()
|
|||
|
{
|
|||
|
#if UNITY_2022_2_OR_NEWER
|
|||
|
canvasController = GameObject.FindAnyObjectByType<CanvasController>();
|
|||
|
#else
|
|||
|
// Deprecated in Unity 2023.1
|
|||
|
canvasController = GameObject.FindObjectOfType<CanvasController>();
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
public override void OnStartServer()
|
|||
|
{
|
|||
|
StartCoroutine(AddPlayersToMatchController());
|
|||
|
}
|
|||
|
|
|||
|
// For the SyncDictionary to properly fire the update callback, we must
|
|||
|
// wait a frame before adding the players to the already spawned MatchController
|
|||
|
IEnumerator AddPlayersToMatchController()
|
|||
|
{
|
|||
|
yield return null;
|
|||
|
|
|||
|
matchPlayerData.Add(player1, new MatchPlayerData { playerIndex = CanvasController.playerInfos[player1.connectionToClient].playerIndex });
|
|||
|
matchPlayerData.Add(player2, new MatchPlayerData { playerIndex = CanvasController.playerInfos[player2.connectionToClient].playerIndex });
|
|||
|
}
|
|||
|
|
|||
|
public override void OnStartClient()
|
|||
|
{
|
|||
|
matchPlayerData.Callback += UpdateWins;
|
|||
|
|
|||
|
canvasGroup.alpha = 1f;
|
|||
|
canvasGroup.interactable = true;
|
|||
|
canvasGroup.blocksRaycasts = true;
|
|||
|
|
|||
|
exitButton.gameObject.SetActive(false);
|
|||
|
playAgainButton.gameObject.SetActive(false);
|
|||
|
}
|
|||
|
|
|||
|
[ClientCallback]
|
|||
|
public void UpdateGameUI(NetworkIdentity _, NetworkIdentity newPlayerTurn)
|
|||
|
{
|
|||
|
if (!newPlayerTurn) return;
|
|||
|
|
|||
|
if (newPlayerTurn.gameObject.GetComponent<NetworkIdentity>().isLocalPlayer)
|
|||
|
{
|
|||
|
gameText.text = "Your Turn";
|
|||
|
gameText.color = Color.blue;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
gameText.text = "Their Turn";
|
|||
|
gameText.color = Color.red;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
[ClientCallback]
|
|||
|
public void UpdateWins(SyncDictionary<NetworkIdentity, MatchPlayerData>.Operation op, NetworkIdentity key, MatchPlayerData matchPlayerData)
|
|||
|
{
|
|||
|
if (key.gameObject.GetComponent<NetworkIdentity>().isLocalPlayer)
|
|||
|
winCountLocal.text = $"Player {matchPlayerData.playerIndex}\n{matchPlayerData.wins}";
|
|||
|
else
|
|||
|
winCountOpponent.text = $"Player {matchPlayerData.playerIndex}\n{matchPlayerData.wins}";
|
|||
|
}
|
|||
|
|
|||
|
[Command(requiresAuthority = false)]
|
|||
|
public void CmdMakePlay(CellValue cellValue, NetworkConnectionToClient sender = null)
|
|||
|
{
|
|||
|
// If wrong player or cell already taken, ignore
|
|||
|
if (sender.identity != currentPlayer || MatchCells[cellValue].playerIdentity != null)
|
|||
|
return;
|
|||
|
|
|||
|
MatchCells[cellValue].playerIdentity = currentPlayer;
|
|||
|
RpcUpdateCell(cellValue, currentPlayer);
|
|||
|
|
|||
|
MatchPlayerData mpd = matchPlayerData[currentPlayer];
|
|||
|
mpd.currentScore = mpd.currentScore | cellValue;
|
|||
|
matchPlayerData[currentPlayer] = mpd;
|
|||
|
|
|||
|
boardScore |= cellValue;
|
|||
|
|
|||
|
if (CheckWinner(mpd.currentScore))
|
|||
|
{
|
|||
|
mpd.wins += 1;
|
|||
|
matchPlayerData[currentPlayer] = mpd;
|
|||
|
RpcShowWinner(currentPlayer);
|
|||
|
currentPlayer = null;
|
|||
|
}
|
|||
|
else if (boardScore == CellValue.Full)
|
|||
|
{
|
|||
|
RpcShowWinner(null);
|
|||
|
currentPlayer = null;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Set currentPlayer SyncVar so clients know whose turn it is
|
|||
|
currentPlayer = currentPlayer == player1 ? player2 : player1;
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
[ServerCallback]
|
|||
|
bool CheckWinner(CellValue currentScore)
|
|||
|
{
|
|||
|
if ((currentScore & CellValue.TopRow) == CellValue.TopRow)
|
|||
|
return true;
|
|||
|
if ((currentScore & CellValue.MidRow) == CellValue.MidRow)
|
|||
|
return true;
|
|||
|
if ((currentScore & CellValue.BotRow) == CellValue.BotRow)
|
|||
|
return true;
|
|||
|
if ((currentScore & CellValue.LeftCol) == CellValue.LeftCol)
|
|||
|
return true;
|
|||
|
if ((currentScore & CellValue.MidCol) == CellValue.MidCol)
|
|||
|
return true;
|
|||
|
if ((currentScore & CellValue.RightCol) == CellValue.RightCol)
|
|||
|
return true;
|
|||
|
if ((currentScore & CellValue.Diag1) == CellValue.Diag1)
|
|||
|
return true;
|
|||
|
if ((currentScore & CellValue.Diag2) == CellValue.Diag2)
|
|||
|
return true;
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
[ClientRpc]
|
|||
|
public void RpcUpdateCell(CellValue cellValue, NetworkIdentity player)
|
|||
|
{
|
|||
|
MatchCells[cellValue].SetPlayer(player);
|
|||
|
}
|
|||
|
|
|||
|
[ClientRpc]
|
|||
|
public void RpcShowWinner(NetworkIdentity winner)
|
|||
|
{
|
|||
|
foreach (CellGUI cellGUI in MatchCells.Values)
|
|||
|
cellGUI.GetComponent<Button>().interactable = false;
|
|||
|
|
|||
|
if (winner == null)
|
|||
|
{
|
|||
|
gameText.text = "Draw!";
|
|||
|
gameText.color = Color.yellow;
|
|||
|
}
|
|||
|
else if (winner.gameObject.GetComponent<NetworkIdentity>().isLocalPlayer)
|
|||
|
{
|
|||
|
gameText.text = "Winner!";
|
|||
|
gameText.color = Color.blue;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
gameText.text = "Loser!";
|
|||
|
gameText.color = Color.red;
|
|||
|
}
|
|||
|
|
|||
|
exitButton.gameObject.SetActive(true);
|
|||
|
playAgainButton.gameObject.SetActive(true);
|
|||
|
}
|
|||
|
|
|||
|
// Assigned in inspector to ReplayButton::OnClick
|
|||
|
[ClientCallback]
|
|||
|
public void RequestPlayAgain()
|
|||
|
{
|
|||
|
playAgainButton.gameObject.SetActive(false);
|
|||
|
CmdPlayAgain();
|
|||
|
}
|
|||
|
|
|||
|
[Command(requiresAuthority = false)]
|
|||
|
public void CmdPlayAgain(NetworkConnectionToClient sender = null)
|
|||
|
{
|
|||
|
if (!playAgain)
|
|||
|
playAgain = true;
|
|||
|
else
|
|||
|
{
|
|||
|
playAgain = false;
|
|||
|
RestartGame();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
[ServerCallback]
|
|||
|
public void RestartGame()
|
|||
|
{
|
|||
|
foreach (CellGUI cellGUI in MatchCells.Values)
|
|||
|
cellGUI.SetPlayer(null);
|
|||
|
|
|||
|
boardScore = CellValue.None;
|
|||
|
|
|||
|
NetworkIdentity[] keys = new NetworkIdentity[matchPlayerData.Keys.Count];
|
|||
|
matchPlayerData.Keys.CopyTo(keys, 0);
|
|||
|
|
|||
|
foreach (NetworkIdentity identity in keys)
|
|||
|
{
|
|||
|
MatchPlayerData mpd = matchPlayerData[identity];
|
|||
|
mpd.currentScore = CellValue.None;
|
|||
|
matchPlayerData[identity] = mpd;
|
|||
|
}
|
|||
|
|
|||
|
RpcRestartGame();
|
|||
|
|
|||
|
startingPlayer = startingPlayer == player1 ? player2 : player1;
|
|||
|
currentPlayer = startingPlayer;
|
|||
|
}
|
|||
|
|
|||
|
[ClientRpc]
|
|||
|
public void RpcRestartGame()
|
|||
|
{
|
|||
|
foreach (CellGUI cellGUI in MatchCells.Values)
|
|||
|
cellGUI.SetPlayer(null);
|
|||
|
|
|||
|
exitButton.gameObject.SetActive(false);
|
|||
|
playAgainButton.gameObject.SetActive(false);
|
|||
|
}
|
|||
|
|
|||
|
// Assigned in inspector to BackButton::OnClick
|
|||
|
[Client]
|
|||
|
public void RequestExitGame()
|
|||
|
{
|
|||
|
exitButton.gameObject.SetActive(false);
|
|||
|
playAgainButton.gameObject.SetActive(false);
|
|||
|
CmdRequestExitGame();
|
|||
|
}
|
|||
|
|
|||
|
[Command(requiresAuthority = false)]
|
|||
|
public void CmdRequestExitGame(NetworkConnectionToClient sender = null)
|
|||
|
{
|
|||
|
StartCoroutine(ServerEndMatch(sender, false));
|
|||
|
}
|
|||
|
|
|||
|
[ServerCallback]
|
|||
|
public void OnPlayerDisconnected(NetworkConnectionToClient conn)
|
|||
|
{
|
|||
|
// Check that the disconnecting client is a player in this match
|
|||
|
if (player1 == conn.identity || player2 == conn.identity)
|
|||
|
StartCoroutine(ServerEndMatch(conn, true));
|
|||
|
}
|
|||
|
|
|||
|
[ServerCallback]
|
|||
|
public IEnumerator ServerEndMatch(NetworkConnectionToClient conn, bool disconnected)
|
|||
|
{
|
|||
|
RpcExitGame();
|
|||
|
|
|||
|
canvasController.OnPlayerDisconnected -= OnPlayerDisconnected;
|
|||
|
|
|||
|
// Wait for the ClientRpc to get out ahead of object destruction
|
|||
|
yield return new WaitForSeconds(0.1f);
|
|||
|
|
|||
|
// Mirror will clean up the disconnecting client so we only need to clean up the other remaining client.
|
|||
|
// If both players are just returning to the Lobby, we need to remove both connection Players
|
|||
|
|
|||
|
if (!disconnected)
|
|||
|
{
|
|||
|
NetworkServer.RemovePlayerForConnection(player1.connectionToClient, true);
|
|||
|
CanvasController.waitingConnections.Add(player1.connectionToClient);
|
|||
|
|
|||
|
NetworkServer.RemovePlayerForConnection(player2.connectionToClient, true);
|
|||
|
CanvasController.waitingConnections.Add(player2.connectionToClient);
|
|||
|
}
|
|||
|
else if (conn == player1.connectionToClient)
|
|||
|
{
|
|||
|
// player1 has disconnected - send player2 back to Lobby
|
|||
|
NetworkServer.RemovePlayerForConnection(player2.connectionToClient, true);
|
|||
|
CanvasController.waitingConnections.Add(player2.connectionToClient);
|
|||
|
}
|
|||
|
else if (conn == player2.connectionToClient)
|
|||
|
{
|
|||
|
// player2 has disconnected - send player1 back to Lobby
|
|||
|
NetworkServer.RemovePlayerForConnection(player1.connectionToClient, true);
|
|||
|
CanvasController.waitingConnections.Add(player1.connectionToClient);
|
|||
|
}
|
|||
|
|
|||
|
// Skip a frame to allow the Removal(s) to complete
|
|||
|
yield return null;
|
|||
|
|
|||
|
// Send latest match list
|
|||
|
canvasController.SendMatchList();
|
|||
|
|
|||
|
NetworkServer.Destroy(gameObject);
|
|||
|
}
|
|||
|
|
|||
|
[ClientRpc]
|
|||
|
public void RpcExitGame()
|
|||
|
{
|
|||
|
canvasController.OnMatchEnded();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|