アヒルのある日

株式会社AHIRUの社員ブログです。毎週更新!社員が自由に思いついたことを書きます。

Unity Lobby/Relay入門 (#2)

こんにちは、ちゃらいプログラマです。
前回の続きでLobby/Relayへの接続周りを説明していきます。

※前回の記事はこちら
Unity Lobby/Relay入門 (#1) - アヒルのある日

処理の流れ

ホスト、クライアントで若干異なります。
MultiGameManagerクラスを作成して処理を作成します。

接続
// 認証済判定
private bool m_isAuthentication = false;
// 認証ID
private string m_authenticationId = string.Empty;

// 接続
public async UniTask ConnectAsync()
{
    if(!m_isAuthentication)
    {
        // "ユーザ識別ID"はアプリ毎に適切なIDを設定してください
        var option = new InitializationOptions();
        option.SetProfile( "ユーザ識別ID" );
        await UnityServices.InitializeAsync( option );
        await AuthenticationService.Instance.SignInAnonymouslyAsync();

        // 上記で渡す"ユーザ識別ID"がゲーム起動時から変わらないのであれば
        // 本処理は一度のみで問題ありません
        m_isAuthentication = true;

        // 参照時の処理負荷が激しいのでキャッシュしておきます
        m_authenticationId = AuthenticationService.Instance.PlayerId;

        // コールバック登録
        Unity.Netcode.NetworkManager.Singleton.OnServerStarted += OnServerStarted;
        Unity.Netcode.NetworkManager.Singleton.OnServerStopped+= OnServerStopped;
        Unity.Netcode.NetworkManager.Singleton.OnClientStarted+= OnClientStarted;
        Unity.Netcode.NetworkManager.Singleton.OnClientStopped+= OnClientStopped;
        Unity.Netcode.NetworkManager.Singleton.OnTransportFailure+= OnTransportFailure;
    }
}

// ホスト起動コールバック
private void OnServerStarted()
{
    // ホストモードでゲーム開始した際に呼び出されます
}

// ホスト停止コールバック
private void OnServerStopped(bool isStop)
{
    // ホストモードで接続が解除された際に呼び出されます
}

// クライアント起動コールバック
private void OnClientStarted()
{
    // クライアントモードでゲーム開始した際に呼び出されます
    // ホストモードでゲーム開始した際にも呼び出されます
}

// クライアント停止コールバック
private void OnClientStopped( bool isStop )
{
    // クライアントモードで接続が解除された際に呼び出されます
}

// 切断コールバック
private void OnTransportFailure()
{
    // ネットワークが切断された際に呼び出されます
}
ホスト側
  • 接続
  • Relayネットワークを生成
private async UniTask<string> CreateRelayAsync( int connectionNumMax )
{
    // 接続最大数を渡してネットワークを生成します
    var allocation = await RelayService.Instance.CreateAllocationAsync( connectionNumMax );
    // 接続用のIDを取得しておきます
    string roomId = await RelayService.Instance.GetJoinCodeAsync( allocation.AllocationId );
    
    // 接続フォーマットを設定します
    var serverData = new RelayServerData( allocation, "dtls" );
    var transport = Unity.Netcode.NetworkManager.Singleton.GetComponent<UnityTransport>();
    transport.SetRelayServerData( serverData );

    return roomId;
}
  • Lobbyを生成
// 接続ロビー情報
private Lobby m_lobby = null;

// ロビー生成
private async UniTask<Lobby> CreateLobbyAsync( string lobbyName, string roomId, int connectionNumMax )
{
    // 接続用のIDを生成時のオプションに設定します
    // アプリ毎に設定数は異なります(最小設定だと以下のようになります)
    var options = new CreateLobbyOptions();
    options.Data = new()
    {
        // フォーマットは
        // "任意の文字列", "DataObject型"
        {
            "RoomID",
            new DataObject(
                visibility: DataObject.VisibilityOptions.Public,
                value: roomId
        },
    };

    // ロビー生成
    m_lobby = await LobbyService.Instance.CreateLobbyAsync( lobbyName, connectionNumMax, options );
}
  • ホストモードでゲーム開始
Unity.Netcode.NetworkManager.Singleton.StartHost();
  • 一定時間毎にLobbyへHeartBeat送信
// ロビー接続ポーリングコルーチン
private Coroutine m_lobbyCoroutine = null;

// ロビー接続継続開始
private void StartLobbyHeartbeat()
{
    StopLobbyHeartbeat();
    m_lobbyCoroutine = StartCoroutine( LobbyHeartbeatCoroutine() );
}

// ロビー接続継続停止
private void StopLobbyHeartbeat()
{
    if (m_lobbyCoroutine != null)
    {
        StopCoroutine( m_lobbyCoroutine );
        m_lobbyCoroutine = null;
    }
}

// ロビー接続ポーリング
private IEnumerator LobbyHeartbeatCoroutine()
{
    var delay = new WaitForSeconds( 15 );
    while (true)
    {
         LobbyService.Instance.SendHeartbeatPingAsync( m_lobby.Id );
         yield return delay;
    }
}
  • 切断(任意のタイミングで)
クライアント側
  • 接続
  • Lobbyリスト取得と参加
private async UniTask<string> JoinLobbyAsync()
{
    // 空いているロビーへ接続します
    var queryLobbyOptions = new QueryLobbiesOptions();
    queryLobbyOptions.Count = 20; // 検索数
    queryLobbyOptions.Order= new()
    {
        new QueryOrder(
            asc: false,
            field: QueryOrder.FieldOptions.Created
        ),
    };
    var lobbies = await Lobbies.Instance.QueryLobbiesAsync( queryLobbyOptions );

    string lobbyId = string.Empty;
    string roomId = string.Empty;
    foreach (var result in lobbies.Results)
    {
        lobbyId = result.Id;
        // CreateLobbyAsync()にてオプションに設定した
        // "RoomID"を参照して接続用IDを取得します
        roomId = result.Data["RoomID"].Value;
        break;
    }

    // ロビー参加
    if(lobbyId != "")
    {
        m_lobby = await LobbyService.Instance.JoinLobbyByIdAsync( lobbyId );
    }

    return roomId;
}
  • Relayネットワークへ接続
private async UniTask JoinRelayAsync( string roomId )
{
    // 接続用のIDを元にネットワークへ接続します
    var allocation = await RelayService.Instance.JoinAllocationAsync( roomId );
    
    // 接続フォーマットを設定します
    var serverData = new RelayServerData( allocation, "dtls" );
    var transport = Unity.Netcode.NetworkManager.Singleton.GetComponent<UnityTransport>();
    transport.SetRelayServerData( serverData );
}
  • クライアントモードでゲーム開始
Unity.Netcode.NetworkManager.Singleton.StartClient();
  • 切断(任意のタイミングで)
切断
// 切断
public async UniTask DisconnectAsync()
{
    if(m_isAuthentication)
    {
        // ロビー接続継続停止
        StopLobbyHeartbeat();

        // ロビーから抜ける
        await LobbyService.Instance.RemovePlayerAsync( m_lobby.Id, m_authenticationId );
        m_lobby = null;

        // Relay接続解除
        Unity.Netcode.NetworkManager.Singleton?.Shutdown();
    }
}

駆け足で説明しましたが、上記でホスト端末へ接続できるようになります。
次回はRPC周りの説明ができればと… また次回。