This document is about: QUANTUM 2
SWITCH TO

再接続

以下のドキュメントでは、実行中のQuantumセッションに再接続するためのあらゆる側面について解説します。基本的な流れは、Quantum SDKに同梱されているデモメニューのサンプルで実装されています。

再接続は、2つの主要な部分で構成されています:Photon Realtimeルームに戻る方法と、Quantumシミュレーションを行う方法です。

切断の検出

接続が切断される最も一般的なケースです。

  • IConnectionCallbacks.OnDisconnected(DisconnectCause cause)DisconnectCause.DisconnectByClientLogic 以外の理由で呼び出された場合。
  • モバイルアプリへのフォーカスが外れた場合
  • アプリが再始動する場合
  • 信号の損失を早期に検出するためのカスタム方法

切断をデバッグする方法

  • Unity Editorでは、Playを押すだけでアプリケーションの停止と起動が可能です。
  • LoadBalancingClient.SimulateConnectionLoss(true) は送受信を停止し、10秒後に DisconnectCause.ClientTimeout で切断されます。
  • LoadBalancingClient.LoadBalancingPeer.StopThread() は、直ちに切断 DisconnectCause.None を発生させます。
  • 外部のネットワークツール(clumsy など)を使ってゲームサーバのポートを遮断します。
Clumsy:
Filter  (udp.DstPort == 5056 or udp.SrcPort == 5056) or (tcp.DstPort == 4531 or tcp.SrcPort == 4531)
Drop    100%

Photon Realtime Fast Reconnect

ルームに戻るには、2つの方法があります。

  • ネームサーバー(Photon Cloud)を経由して接続し、マスターサーバーに到着したら、新しいPhotonアクターとしてルームに参加する(これは、デフォルト の方法と呼ばれる)。
  • 再接続して再参加する方法

C#

LoadBalancingClient.ReconnectAndRejoin()

このメソッドは、キャッシュされた認証データ、サーバーアドレス、ルーム名などを使用して、ゲームサーバーへの直接接続とルームへの再参加を試みます。この情報はLoadBalancingClientオブジェクトにキャッシュされます。

ルームに再参加すると、同じPhoton Actor IDがクライアントに割り当てられます。

ルームへの再参加は、マスターサーバへの再接続または接続後にも行うことができます。

C#

LoadBalancingClient.ReconnectToMaster()
// ..
public void IConnectionCallbacks.OnConnectedToMaster() {
    _client.OpReJoinRoom(roomName);
}

再参加の操作は、クライアントがまだルームを出ていない場合にのみ機能します。(次のセクションPlayerTTLを参照)

Caveat: Fast reconnect in Quantum version 2.0.x will only work when providing a local snapshot (StartParameters.InitialFrame and StartParameters.FrameData).

C#

var frameData = QuantumRunner.Default.Game.Frames.Verified.Serialize(DeterministicFrameSerializeMode.Blit);
var initialFrame = QuantumRunner.Default.Game.Frames.Verified.Number;

必須: PlayerTTL

ルーム内のクライアントは通常activeです。それらは inactive になります。

  • サーバーに応答しないまま10秒のタイムアウト(デフォルト)が経過すると、inactiveになります。
  • LoadBalancingClient.Disconnect() を呼び出した後。
  • LoadBalancingClient.OpLeaveRoom(true) を呼び出した後。

2つのオプションがあります:

A) ルームがプレイヤーの退室ロジックを実行する(PlayerTTLはデフォルトで0)

B) プレイヤーが inactive とマークされ、退室ルーチンを実行する前にPlayerTtl と呼ばれる別のタイムアウトの間、その状態が維持される。PlayerTtlの値は、ルーム作成時にRoomOptionsで明示的に設定する必要があります。通常は20秒(20000ms)から始めるべきです。

Fast Reconnect はクライアントがまだ active (OpJoinRoom() が許可しない10秒間のタイムアウトの前) と inactive (PlayerTTL) タイムアウトの間、ルームに戻れるようにします。

クライアントが正常に再参加すると IMatchmakingCallbacks.OnJoinedRoom() が呼び出されます。

デモメニューのサンプルでは、チェックアウトのための2つのオプション(UIConnect.OnReconnectClicked())を実装しています。

  1. PlayerTTL > 0の場合、ReconnectAndRejoin()をおこないます。
  2. PlayerTTL== 0の場合、ReconnectToMaster()をおこない、OpJoinRom()が続きます。

必須: RoomTtl (スナップショットを待機)

すべてのクライアントが inactive になったことを検出すると、ルームはすぐに閉じられます。これを防ぐには RoomOptions.EmptyRoomTTL を設定します。これはルームに少数のプレイヤーしかいなくて、すべてのプレイヤーに同時に接続問題が発生する確率がある場合に重要かもしれません。スナップショットを送信するために誰かが存在する必要があるため、これはカスタムサーバプラグインとサーバサイドスナップショットでのみ確実に動作します。

このようなケースを考えてみましょう。2人用のオンラインゲームで、一方のプレイヤーが再接続または遅れて参加し、スナップショットを待っている間に、もう一方のプレイヤーに接続の問題が発生し始めたとします。スナップショットは送信されず、プレイヤーは待ちぼうけを食らうことになります。

簡単な解決策は、ゲーム開始のタイムアウトを検出し、待機中のプレイヤーを切断することです。これは QuantumRunner.StartParameters.StartGameTimeoutInSeconds を渡して QuantumRunner.HasGameStartTimedOut をチェックし、最後にカスタムエラー処理(プレイヤーフィードバックを与えたりロビーUIに戻ったり)を実装すれば実現可能です。UIGame.Update()に示すように。

必須: Photon UserId

Photon Realtime: ロビーとマッチメイキング|ユーザーIDとフレンド

Photonではプレイヤーは一意のUserIDで識別されます。rejoinを使ってルームに戻るには、UserIdが同じである必要があります。UserIdが元々明示的に設定されているか、Photonによって設定されているかは関係ありません。

ルームに入ると、Quantumはプレイヤーの識別に別のIDを使用するため、UserIdは重要ではありません(Quantum CliendIdの項を参照)。

PhotonのUserIdは以下のようになります。

  1. 接続時にクライアントが設定する (AuthenticationValues.UserId)
  2. 空欄の場合、Photonによって設定される。
  3. あるいは、外部の認証サービスによって設定される。

Photon idsの背景情報:

  • Photon Actor Number (actor IDとも呼ばれます)は、現在のルームにいるプレイヤーを識別するもので、ルームごとに割り当てられ、そのコンテキストでのみ有効です。ルームを出たり戻ったりするクライアントは、新しいアクターID を取得します。OpRejoinRoom()またはReconnectAndRejoin()が成功した場合、アクターIDは保持されます。Quantum は、アクター ID をバックトレースして、プレイヤー Frame.PlayerToActorId(PlayerRef) にマッチさせる方法を提供します。ただし、プレイヤーの離脱や復帰(再加入ではありません)に伴って変更される可能性があることを覚えておいてください。

  • Photon NicknameはPhotonクライアントのプロパティで、他のクライアントについてより詳しく知るためにルーム内で伝搬されます。Quantumとは関係ありません。

考えられるエラー:ReconnectAndRejoinがFalseを返す

現在の接続ハンドラ LoadBalancingClient には、再接続を実行するための関連データが不足しています。デフォルトの接続シーケンスを実行し、通常の方法でルームへの参加または再参加を試みてください。

また、アプリ再起動後の再接続 のセクションも参照してください。

考えられるエラー: PlayerTTLのタイムアウト

PlayerTTLのタイムアウトを過ぎて再参加する場合、 ErrorCode.JoinFailedWithRejoinerNotFound がスローされます。

これはまた、MasterServerに接続されており、OpJoinRoom()でルームに参加できることを意味します。

C#

public void (IMatchmakingCallbacks.)OnJoinRoomFailed(short returnCode, string message) {
    switch (returnCode) {
        case ErrorCode.JoinFailedWithRejoinerNotFound:
            // Try to join the room without rejoining
            _client.OpJoinRoom(new EnterRoomParams { RoomName = roomName });
    break;
}

可能性のあるエラー:認証トークンのタイムアウト

認証チケットは1時間後に失効します。Quantumゲームセッションの途中で切れる前に自動的にリフレッシュされます (Photon Realtime: Encryption | Token Refresh)。 一般的なゲームセッションが長く、約20分後にプレイヤーの再接続に対応したい場合、このエラーを処理する必要があります。解決策は、デフォルトの接続ルーチンを再起動し、ルームへの再参加を試みることです。

C#

public void OnDisconnected(DisconnectCause cause) {
    switch (cause) {
        case DisconnectCause.AuthenticationTicketExpired:
        case DisconnectCause.InvalidAuthentication:
            // Restart with your default connection sequence
        break;

可能性のあるエラー:接続ができていない

もちろん、接続がまだ妨害されたり、他のエラーが発生したりすることもあります。いずれの場合も IConnectionCallbacks.OnDisconnected(DisconnectCause cause) が呼び出されます。

アプリ再起動後の再接続

LoadBalancingClient オブジェクトは、再参加操作に関連するデータ(トークン)をキャッシュしており、アプリケーションを再起動するとその情報が失われる可能性があります。

この場合、接続を再開し、同じUserId、FixedRegion、AppVersionを使用してネームサーバに接続し、マスターサーバに到着したら、再参加するか、ルームに戻る必要があります。

接続キャッシュが失われているため、サーバーがまだ切断を登録しておらず、再参加が ErrorCode.JoinFailedFoundActiveJoiner で失敗するかもしれません (10秒 タイムアウト)。この場合、再接続が成功するか、別のエラーが発生するまで再試行します。

デモメニューのサンプルクラスReconnectInformationは、Unity PlayerPrefsを使用して、関連する最小限の実行可能な再接続データがどのように保存され、復元されるかを示しています。

再接続処理は UIConnect.OnReconnectClicked()UIReconnecting.OnConnectedToMaster() の内部で適用されています。

PhotonのUserIdをPlayerPrefsに保存することは、もちろん、カスタム認証で置き換えることができます。

Photon の UserId を PlayerPrefs に保存することは、もちろんカスタム認証で置き換えることができます。また、PlayerPrefsにスナップショットを保存して読み込むことも可能で、プレイヤー数が非常に少ないゲームでは面白いかもしれません。バイナリデータを文字列としてPlayerPrefsに保存するには、base64エンコードとデコードを使用します。

異なるマスターサーバー

ReconnectAndRejoin()ReconnectToMaster()`は、クラウド経由で再接続したときに、クライアントが以前とは異なるマスターサーバーになってしまうようなフリンジケースを防ぎます。理由は以下の通りです。

  • 1つのアプリに複数のクラスターが存在する
  • マスターサーバーが交換された(ローテーションアウト)。
  • ベストリージョンのPingが新しい結果になっている

その他のPhoton Realtimeトピックス

これらの機能は再接続には重要ではありませんが、デモメニューのサンプルの一部なので、ここで取り上げます。

ベストリージョンまとめ

QuantumLoadBalancingClient クラスは、Photon Realtime の LoadBalancingClient をラップしています。これはベストリージョンのping結果を便利にキャッシュするためのものです。マスターサーバーへの接続が成功したら、それらをUnity PlayerPrefsに保存し、AppSettingsオブジェクトを通して次の接続試行時に提供し、最適なリージョンへの接続をスピードアップさせます。

リージョンの ping は時々強制されますが、プレイヤーが悪い ping 結果で立ち往生しないように、無効化を実装するのが賢明でしょう(たとえば、ping が閾値を超えたら、1 日おきに BestRegionSummary をクリアします)。また、プレイヤーが世界の他の地域に移動することもあり得ます。その場合、最も近い地域を見つけるために新しい ping が必要になります。

AppVersion

デモメニューのサンプルでは、ランダムマッチメイキングを使ってプレイヤーをマッチングしています。AppSettingsで指定するAppVersion`は、同じAppIdのプレイヤーベースを別々のグループに分類します。同じAppIdで異なるAppVersionに接続しているプレイヤーは、お互いを探せません。

これは、複数のバージョンのゲームをライブで運営している場合や、開発中のゲームに他のクライアント(コードベースが異なるため、即座にゲームをデシンクしてしまう)が参加するのを防ぐ場合に便利です。

UIConnect.csでは、一般的なAppVersionの追加と、テストを中断しないための秘密鍵の設定について説明しています。プライベートAppVersion文字列は、すべてのワークスペースに対して一度だけ生成されます(PhotonPrivateAppVersion.cs 参照)。そのワークスペースから作成されたすべてのビルドでプレイヤー同士のマッチングが可能になります。

参考文献

実行中のQuantumゲームに再接続する

Quantum ClientId

ClientId は、クライアントとサーバーの間の秘密です。他のクライアントがこれを知ることはありません。QuantumRunner を起動するときに渡されます。

C#

public static QuantumRunner StartGame(String clientId, StartParameters param)

Photonルームの新規アクターとして参加したか、再参加したかに関わらず、再接続するクライアントはClientIdで識別され、その間に他のプレイヤーによってスロットが埋められなかった場合、以前と同じプレイヤーインデックスに割り当てられることになります。要するに、再接続の際には**同じClientId**を使用しなければなりません。

Quantumは、同じClientIdを持つ他のアクティブなプレイヤーがルーム内にいて、切断タイムアウト(10秒)を待っている間は、クライアントにセッションを開始させません。

DISCONNECTED: Error #5: Duplicate client id

このため、短期間の接続切断から回復するためには、ReconnectAndRejoin()が必要となります

Further Readings

Quantum Session の再スタート

切断後は QuantumRunnerQuantumSession は使用不可となり、破棄して再作成する必要があります。

クライアントが Quantum ゲームが動作しているルームに参加または再参加した場合、 QuantumRunner を再起動する必要があります。シミュレーションは他のクライアントからスナップショットが届くまで一時停止します。その後、最新のゲーム時間に追いつき、同期します。

大まかな流れは以下の通りです。

  • 切断を検出し、QuantumRunner を破壊します。
  • 再接続、再入室
  • QuantumRunner.Start() を呼び出して Quantum を再スタートさせます。

QuantumSession を停止して破棄するには、以下のように呼び出します。

C#

QuantumRunner.ShutdownAll(true);

Unityのメインスレッドにいるときにのみこのメソッドをimmediate:trueで呼び出し、Quantumのコールバックの中からは決して呼び出さないでください。immediate:falseで呼び出すか、Unityのアップデートコールから拾われるまで手動で呼び出しを遅らせます。

デモメニューのサンプルでは、新しいゲームの開始、実行中のゲームへの遅れての参加、再参加が、非常に似た手順を必要とすることがわかります。UIRoom.OnShowScreen()では、ルームのプロパティを確認することで、そのゲームがすでに開始されていることを検出し、すぐにゲームを開始します。

EntityViewsとUI

遅れて参加したプレイヤーや再接続したプレイヤーは、ゲームの構成に柔軟性を求めます。ゲームを任意の時点から開始し、インスタンス化されたプレハブや UI を再利用したり、ゲームを任意の時点で停止してクリーンアップする必要があります。その代わり、読み込み時間が長くなる、新しいシーンに不要なVFXやアニメーションが入る、UI遷移で立ち往生する、などの影響があります。

もし、 EntityViewUpdaterEntityViews を生かして再利用したい場合は、手動で更新を停止して、新しい QuantumGame インスタンスと再マッチして、新しいコールバックをサブスクライブするなどの作業が必要になってきます。

一方、Quantumの処理は非常にシンプルです:shutdown runner, start runner.

イベント

プレイヤーが参加または再参加する前に発生した以前のイベントは、クライアントが受け取ることはありません。ゲームビューは、シミュレーションの現在の状態をポーリングすることで完全に初期化/リセットでき、将来のイベント/ポーリングを使用して自分自身を更新し続けることができる必要があります。

SetPlayerData

プレイヤーの再接続のために SetPlayerData() を呼び出すことは任意です。シミュレーションのアバター設定ロジックがこれを必要とするかどうかによります。

StartParameters.QuitBehaviour

Quantum のシャットダウンシーケンスが実行されるとき (QuantumRunner.ShutdownAll) 、QuantumNetworkCommunicator クラスはオプションで退室処理を実行するか LoadBalancing クライアントを切り離します。QuantumRunner.StartParameters で QuitBehaviour.None を設定し、自分で処理するようにしてください。

遅れて参加する場合とスナップショット

スナップショットとは、プラットフォームに依存しないデータの塊で、検証済み(すべてを受信した)ティックの後のゲームの完全な状態を含みます。Quantumシミュレーションはスナップショットから開始することができ、その状態からシームレスに継続することができます。

シミュレーションの実行中にクライアントが独自のスナップショットを作成したり(ローカルスナップショット)、他のクライアントからスナップショットを要求したり(バディスナップショット)、シミュレーションを実行するカスタムサーバープラグインからスナップショットを送信したりすることができます。

スナップショットからの起動や再起動は非常に便利で、Quantumからターンキーで提供されます。そうでなければ、遅れて参加したクライアントがゲームセッションを最初から開始し、サーバーから送られてくる入力履歴を早送りしなければならず、追いつくまでクライアントアプリが使えなくなる可能性がありますし、サーバーに保存される入力履歴は10分程度に制限されています。

バディスナップショットは、クライアントが QuantumRunner を起動するときに自動的に開始されます(クライアントが初めてセッションを開始するとき、遅れて参加したとき、再接続したときに関係ありません)。セッションは一時停止モード DeterministicSession.IsPaused になり、スナップショットが要求されます。レイトジョインに成功すると、次のようなメッセージが記録されます。

Waiting for snapshot. Clock paused.
Detected Resync. Verified tick: 6541

バディスナップショットは、最初の起動から5秒後に接続するクライアントに要求されます。

サーバは、個々のクライアントに過度の負担をかけないように、どのクライアントにバディスナップショットを要求するかを決めるために、ロードバランシング機構を使用します。

スナップショット処理中のエラーは Disconnect メッセージを使ってクライアントに転送されます(例えば、スナップショットの待機状態は15秒後にタイムアウトします)。

ゲーム開始ルーチン中にスナップショットから開始する場合、いくつかの相違点があります。

  • CallbackGameStarted の代わりに CallbackGameResynced というコールバックが実行されます。
  • System.OnInit() はスナップショットを受信する前に呼び出されます。

ローカルスナップショット

オプションの再接続戦略として、最後に検証されたティックのローカルスナップショットを保存し、新しい QuantumRunner を開始するときに使用することができます。これは予想されるオフラインの時間が短い場合に最も効果的です。ローカルスナップショットは一般的に帯域幅に優しく、より高速です。

ガイドライン

Quantumでは、古すぎるスナップショットからの起動はユーザー体験を低下させる可能性があるため、ローカルスナップショットの受け入れタイミングに厳しい制限を課しています。

デフォルトでは、10秒より古いローカルスナップショットはサーバに受け入れられず、代わりにバディスナップショットが要求されます。このプロセスは透過的に動作し、クライアントの観点からは、受信したスナップショットの古さのみが異なります。

ユーザー数が少ないゲーム(例:1対1)では、バディスナップショットを提供できる他のクライアントがオンラインに存在しない可能性が高くなります。このようなタイプのゲームでは、通常 EmptyRoomTTL 値を使用する必要があり、Quantum はローカルスナップショットの受信時間を EmptyRoomTTL` に延長しますが、最大 2 分 とします。

ワークフロー

  • 切断を検出
  • スナップショットの取得
  • QuantumRunnerをシャットダウン
  • Fast Photon Reconnect
  • スナップショットでQuantumを再起動

デモメニューの再接続シーケンスをお読みください。UIGame.OnDisconnect は、切断の理由がクライアント自身の切断以外の場合、スナップショットを作成します。10秒のタイムアウトを使用し、その後スナップショットは使用されず、キャッチアップ時間が長くなりすぎるため、他のクライアント/サーバーから新しいスナップショットが要求されます。

C#

_frameSnapshot = QuantumRunner.Default.Game.Frames.Verified.Serialize(DeterministicFrameSerializeMode.Blit);
_frameSnapshotNumber = QuantumRunner.Default.Game.Frames.Verified.Number;
_frameSnapshotTimeout = Time.time + 10.0f;

UIRoom.StartQuantumGame() の再接続時に、スナップショット情報が StartParameter として設定されます。

C#

var param = new QuantumRunner.StartParameters {
    FrameData    = IsRejoining ? UIGame.Instance?.FrameSnapshot : null,
    InitialFrame = IsRejoining ? (UIGame.Instance?.FrameSnapshotNumber).Value : 0,
    // ...
}

成功すると、Quantumは要求されたティック番号とともにこれを記録します。

Resetting to tick 4316
Detected Resync. Verified tick: 4316
Back to top