Skip to content

Instantly share code, notes, and snippets.

@liveasnotes
Last active March 2, 2023 04:54
Show Gist options
  • Select an option

  • Save liveasnotes/f8d4693a4a4b261c449094b82511d6d2 to your computer and use it in GitHub Desktop.

Select an option

Save liveasnotes/f8d4693a4a4b261c449094b82511d6d2 to your computer and use it in GitHub Desktop.
AutoNetworkDiscovery.cs for Unity multi play with Mirror
/*
original: https://qiita.com/OKsaiyowa/items/ecef8e5d50c84664f2a2
modified by liveasnotes
> 登録ユーザーは、本サイトに投稿したコード、スニペットなどプログラムに類するものに限り、他の登録ユーザーが商用私用問わず無償で使用することを許諾し、他の登録ユーザーはこれを使用できるものとします。
> 前各項の登録ユーザーによる利用許諾には、地域制限、著作権表示義務その他付随条件はないものとします。
cf. https://qiita.com/terms (2023-01-04 checked)
*/
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using Mirror;
using Mirror.Discovery;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// Automation of Server Discovery and Connection
/// </summary>
public class AutoNetworkDiscovery : NetworkDiscovery
{
[SerializeField] private Button _multiPlayButton;
[SerializeField] private Button _backButton;
[SerializeField] private Button _playButton;
[SerializeField] private Text _playerCountText;
[SerializeField] private Text _connectionStateText;
[SerializeField, Scene] private string _gameSceneName;
// Attribute "Scene" is one of the features of Mirror.
// The referenced scene on the inspector can be used as a string in code.
private ServerResponse _discoveredServer;
private CancellationTokenSource _cancellationTokenSource;
private const int CONNECT_INTERVAL_TIME = 2;
private const int WAIT_TIME = 2;
private const int CONNECT_TRY_COUNT = 1;
private const string CONNECTION_STATUS_CLIENT_WAITING = "Waiting start...";
private const string CONNECTION_STATUS_HOST_WAITING = "Waiting other player...";
private const string CONNECTION_STATUS_SUCCESS = "Success!";
private bool _isHostReady;
private NetworkManager _networkManager;
private void OnDestroy()
{
// Stop server discovery before destroying self (e.g., scene transition).
StopDiscovery();
}
private void Awake()
{
// Prepare for receiving data
NetworkClient.RegisterHandler<SendHostReadyData>(ReceivedReadyInfo);
NetworkClient.RegisterHandler<SendPlayerCountData>(ReceivedPlayerCountInfo);
OnServerFound.AddListener(serverResponse =>
{
_discoveredServer = serverResponse;
Debug.Log("ServerFound");
});
// Start server discovery and connection
_multiPlayButton.onClick.AddListener(() =>
{
Debug.Log("Search Connection");
_backButton.transform.gameObject.SetActive(true);
_multiPlayButton.transform.gameObject.SetActive(false);
// Try connection
_cancellationTokenSource = new CancellationTokenSource();
CancellationToken token = _cancellationTokenSource.Token;
TryConnectAsync(token).Forget();
});
// Return to the first view
_backButton.onClick.AddListener(() =>
{
Debug.Log("Cancel");
// Leave server
StopDiscovery();
NetworkManager.singleton.StopHost();
// Stop asynchronous process
_cancellationTokenSource.Cancel();
_cancellationTokenSource.Dispose();
});
// This button is displayed only for Host. It completes preparation if the button is pressed.
_playButton.onClick.AddListener(() =>
{
Debug.Log("Ready Ok");
SendHostReadyData sendData = new SendHostReadyData() { IsHostReady = true };
NetworkServer.SendToAll(sendData);
_playButton.transform.gameObject.SetActive(false);
});
}
/// <summary>
/// Reflect the state of the host ready to the local client(s).
/// </summary>
private void ReceivedReadyInfo(SendHostReadyData receivedData)
{
_isHostReady = receivedData.IsHostReady;
}
/// <summary>
/// Reflect the state of connection amount to the local client(s).
/// </summary>
private void ReceivedPlayerCountInfo(SendPlayerCountData receivedData)
{
if (_playButton == null) return;
_playerCountText.text = receivedData.PlayerCount + "/" + _networkManager.maxConnections;
}
/// <summary>
/// Asynchronous trial of connection
/// </summary>
private async UniTaskVoid TryConnectAsync(CancellationToken token)
{
_networkManager = NetworkManager.singleton;
int tryCount = 0;
StartDiscovery();
// Loop until connected to a server
while (!_networkManager.isNetworkActive)
{
await UniTask.Delay(TimeSpan.FromSeconds(CONNECT_INTERVAL_TIME), cancellationToken: token);
// Run every n seconds
// Server found
if (_discoveredServer.uri != null)
{
Debug.Log("Start Client");
_networkManager.StartClient(_discoveredServer.uri);
_connectionStateText.text = CONNECTION_STATUS_CLIENT_WAITING;
StopDiscovery();
await UniTask.WaitUntil(() => _isHostReady, cancellationToken: token);
_connectionStateText.text = CONNECTION_STATUS_SUCCESS;
}
// Server not found
else
{
Debug.Log("Try Connect...");
tryCount++;
// Make self as host if the trial failed over n times.
if (tryCount > CONNECT_TRY_COUNT)
{
Debug.Log("Start Host");
_networkManager.StartHost();
AdvertiseServer();
// Send notice that this is a server (host).
_connectionStateText.text = CONNECTION_STATUS_HOST_WAITING;
_playButton.gameObject.SetActive(true);
await UniTask.WaitUntil(() => _isHostReady, cancellationToken: token);
_connectionStateText.text = CONNECTION_STATUS_SUCCESS;
await UniTask.Delay(TimeSpan.FromSeconds(WAIT_TIME), cancellationToken: token);
_networkManager.ServerChangeScene(_gameSceneName);
}
}
}
}
}
[Serializable]
public struct SendPlayerCountData : NetworkMessage
{
/// <summary>
/// Number of connected player
/// </summary>
public int PlayerCount;
}
[Serializable]
public struct SendHostReadyData : NetworkMessage
{
/// <summary>
/// Is the host ready
/// </summary>
public bool IsHostReady;
}
// source is same to above.
// some parts modified.
using Mirror;
using UnityEngine;
using UnityEngine.SceneManagement;
/// <summary>
/// 接続にまつわるいろいろ
/// </summary>
public class CustomNetworkManager : NetworkManager
{
[SerializeField, Scene] private string _titleScene;
[SerializeField, Scene] private string _mainScene;
private Transform _playerTransform;
private Material _playerMaterial;
/// <summary>
/// プレイヤー入室時にサーバー側が実行
/// </summary>
/// <param name="conn">接続されたプレイヤーのコネクション</param>
public override void OnServerAddPlayer(NetworkConnectionToClient conn)
{
Debug.Log("Add Player");
//タイトルシーンでのみ実行
if (_titleScene.Contains(SceneManager.GetActiveScene().name))
{
//接続中の人数表記を変える
SendPlayerCountData sendData = new SendPlayerCountData() { PlayerCount = NetworkServer.connections.Count };
NetworkServer.SendToAll(sendData);
}
//メインシーンでのみ実行
if (_mainScene.Contains(SceneManager.GetActiveScene().name))
{
Debug.Log("Spawn Player");
//プレイヤー生成
GameObject player = Instantiate(playerPrefab);
//今立ち上げているサーバーにプレイヤーを追加登録
NetworkServer.AddPlayerForConnection(conn, player);
}
}
/// <summary>
/// 各プレイヤー退室時にサーバー側が実行
/// </summary>
/// <param name="conn">切れたコネクション</param>
public override void OnServerDisconnect(NetworkConnectionToClient conn)
{
//接続中の人数表記を変える
SendPlayerCountData sendData = new SendPlayerCountData() { PlayerCount = NetworkServer.connections.Count };
NetworkServer.SendToAll(sendData);
Debug.Log("Anyone Disconnect");
base.OnServerDisconnect(conn);
}
/// <summary>
/// サーバーとの接続が切れた時にクライアント側で呼ばれる
/// </summary>
public override void OnStopClient()
{
SceneManager.LoadScene(_titleScene);
Debug.Log("Disconnect");
base.OnStopClient();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment