This document is about: QUANTUM 3
SWITCH TO

概要

はじめに

Quantumは、プレイヤーエンティティの概念を持ちません。シミュレーション観点では、すべてのエンティティは同じものです。したがって、このドキュメントで「プレイヤー」と呼ぶ場合は「プレイヤーが操作するエンティティ」を指します。

プレイヤーの識別

プレイヤーは2つの方法で識別できます。

  • プレイヤーインデックス
  • PlayerRef

プレイヤーインデックスの割り当て

Quantumのplayer indexの割り当ては、QuantumGame.AddPlayer()命令がサーバーに到着した順序で行われます。Photonのルームに参加したプレイヤー順に基づくPhoton/Actor Idと混同しないようにしてください。

「好きなQuantum Id」を設定することはできません。

注意: 切断されたクライアントが同じClientIdで再接続する(public static QuantumRunner StartGame(String clientId, Int32 playerCount, StartParameters param))場合、同じプレイヤーインデックスを取得することが保証されています。これはPhoton Idとは関係ありません。

プレイヤーインデックス vs PlayerRef

PlayerRefは、Quantum ECSのplayer indexのラッパーです。PlayerRefは1から、player indexは0から始まります。これはdefault(PlayerRef)が便宜上、null/無効なPlayerRef構造体を返せるようにするためです。

  • default(PlayerRef)は内部的に0で、「誰でもない」という意味です
  • PlayerRefが内部的に1なら、プレイヤーインデックス0と同じです
  • PlayerRefが内部的に2なら、プレイヤーインデックス1と同じです

IntegerPlayerRefは、暗黙的キャストで相互に変換可能です。

C#

// DeterministicCommand GetPlayerCommand(PlayerRef player);
for (int p = 0; p < f.PlayerCount; p++) {
  var command = f.GetPlayerCommand(p);
}

プレイヤースロット

Quantumは1つのゲームクライアントで、複数のローカルプレイヤーを実行することができます。プレイヤースロットの0は、すべてのローカルプレイヤーを指します。

いくつかのAPIは、明示的にプレイヤースロットを選択する必要があります。ローカルプレイヤーが1人のみの場合は0を指定します。

Is Local Player

Quantumは、ビュー内でローカルプレイヤーかどうかを確認するAPIを提供しています。

  • QuantumRunner.Default.Game.Session.IsLocalPlayer(int player)
  • QuantumRunner.Default.Game.PlayerIsLocal(PlayerRef playerRef)

Get Local Players

QuantumRunner.Default.Game.GetLocalPlayers()は、各クライアントごとにユニークな配列を返します。配列の要素は、ローカルクライアントがQuantumシミュレーション内で制御しているグローバルプレイヤーインデックスを表します。

QuantumRunner.Default.Game.GetLocalPlayerSlots()は、各クライアントごとにユニークな配列を返します。配列の要素は、ローカルクライアントがQuantumシミュレーション内で制御しているプレイヤースロットを表します。

  • このメソッドは、ローカルプレイヤーが1人のみの場合、1つのインデックスを返します。同じローカル端末に複数のプレイヤーが存在する場合、配列長はローカルプレイヤー数になります。
  • ゲームへ再参加する際、同じQuantumのSessionRunner.Arguments.ClientIdでセッションを開始すると、クライアントに同じプレイヤーインデックスが割り当てられます。

Photon Id

FrameAPIから、プレイヤーに対応するPhoton Idを識別できます。

  • Frame.PlayerToActorId(PlayerRef player)は、QuantumのPlayerRefActorId(Photon Client Id)に変換します。
  • Frame.ActorIdToAllPlayers(Int32 actorId)は、上記メソッドのプロセスの逆です。

重要: Photon Idは、Quantumシミュレーションには一切関係しません。

ゲームの開始

SessionRunner.Start()を呼び出すと、以下の開始プロトコルシーケンスが実行されます。

  1. SessionConfigRuntimeConfigを含むStartRequestがサーバーに送信されます。
  2. サーバーはリクエストを受信・検証し、選択した設定を含むSimulationStartの確認をクライアントに返送します。リクエストの情報が無効だった場合、リクエストはPluginDisconnectメッセージで拒否されます。
  3. クライアントはSimulationStartメッセージを受信します。ここでクライアントはまだプレイヤーを持ちません。すべてのクライアントは観客としてのみ開始し、入力やコマンドを送信することはできません。
  4. (オプション) 途中参加する場合、クライアントは同期用のスナップショットを受信します。
  5. QuantumGame.AddPlayer()を使用してQuantumプレイヤーを登録し、個別にRuntimePlayer設定を送信します。
  6. サーバーは参加成功したプレイヤーを入力にエンコードし、すべてのクライアントで同じティックにISignalOnPlayerAddedが呼び出されます。プレイヤー登録に失敗した場合は、OnLocalPlayerAddFailedコールバックが呼び出されます。
Config Sequence Diagram
シーケンス図

関連する設定ファイルの詳細については、設定ファイルマニュアルをご覧ください。

プレイヤーの追加/削除

クライアントは、プレイヤーを追加する前に、ゲームが開始されるのを待つ必要があります。これには2つの方法があります。

A) 非同期版のSessionRunner.StartAsyncを使用して完了を待つ

C#

// 接続ロジックが完了する(例:必要に応じて、スナップショットを受信する)と戻る
var runner = (QuantumRunner)await SessionRunner.StartAsync(sessionRunnerArguments);

// オンラインシミュレーションにプレイヤーを追加する
var runtimePlayer = new RuntimePlayer { PlayerNickname = "whiskeyjack29" };
runner.Game.AddPlayer(runtimePlayer);

B) QuantumAddRuntimePlayers.cs/QuantumRunnerLocalDebug.csスクリプトのように、CallbackGameStartedで登録する

C#

public class QuantumAddRuntimePlayers : QuantumMonoBehaviour {
    public RuntimePlayer[] Players;

    public void Awake() {
        QuantumCallback.Subscribe(this, (CallbackGameStarted c) => OnGameStarted(c.Game, c.IsResync), game => game == QuantumRunner.Default.Game);
    }

    public void OnGameStarted(QuantumGame game, bool isResync) {
        for (int i = 0; i < Players.Length; i++) {
            game.AddPlayer(i, Players[i]);
        }
    }
}

AddPlayer()は、スロットごとに一度だけ呼び出すことができます。何らかのエラーが発生した場合、サーバーはAddPlayerFailedプロトコルイベント(後述のコールバックを参照)を送信します。

Webリクエストが発生する命令は、サードパーティーのバックエンドを保護するために、送信レート制限があります。

クライアントがリクエストできる最大プレイヤー数は、Webhookや、PhotonダッシュボードのパラメーターMaxPlayerSlotsで制限できます。

プレイヤー関連の命令を送信するAPIは以下の通りです。

C#

class QuantumGame {
  // プレイヤーデータをサーバーへ送信し、プレイヤー追加(プレイヤースロット0)をリクエストする
  void AddPlayer(RuntimePlayer data);
  // 特定プレイヤースロットのプレイヤーデータをサーバーへ送信する
  void AddPlayer(int playerSlot, RuntimePlayer data);
  // スロット0のプレイヤー削除をリクエストする
  void RemovePlayer();
  // 特定スロットのプレイヤーを削除する
  void RemovePlayer(int playerSlot);
  // このクライアントに属するすべてのプレイヤーを削除する
  void RemoveAllPlayers();
}

以下のQuantumコールバックがビューで使用できます。

C#

CallbackLocalPlayerAddConfirmed {
  public Frame Frame;
  public int PlayerSlot;
  public PlayerRef Player;
}

CallbackLocalPlayerAddFailed {
  public int PlayerSlot;
  public string Message;
}

CallbackLocalPlayerRemoveConfirmed {
  public Frame Frame;
  public int PlayerSlot;
  public PlayerRef Player;
}

CallbackLocalPlayerRemoveFailed {
  public int PlayerSlot;
  public string Message;
}

// 例
QuantumCallback.Subscribe(this, (CallbackLocalPlayerAddConfirmed c) => OnLocalPlayerAddConfirmed(c));
private void OnLocalPlayerAddConfirmed(CallbackLocalPlayerAddConfirmed c) { }

以下のQuantumシグナルがシミュレーションで使用できます。プレイヤースロット情報はローカルプレイヤーのみ利用可能であるため、ここには含まれません。

C#

// PlayerRefに初めてプレイヤーが割り当てられた時にfirstTimeになります。
// firstTimeがfalseの場合、PlayerRefが別のプレイヤーで再利用されています。
ISignalOnPlayerAdded(Frame frame, PlayerRef player, bool firstTime)
ISignalOnPlayerRemoved(Frame frame, PlayerRef player)

PlayerConnectedSystem

PlayerConnectedSystemは、プレイヤーのオンライン接続状態を追跡するもう一つの方法で、入力と接続フラグを使用します。これはSystenmsConfigに追加することで使用できます。

接続/切断のQuantumシステムコールバックを受け取るには、ISignalOnPlayerConnected/ISignalOnPlayerDisconnectedを実装する必要があります。

RuntimePlayer

RuntimePlayerクラスは、プレイヤー固有の情報(例:選択したキャラクター)を保持します。

このクラスは、サーバーへ送受信する際にJSONでシリアライズされます。

RuntimePlayerは、quantum.codeプロジェクトの部分クラスで、RuntimePlayer.User.csから独自実装を行うことができます。データを追加する際は、JSONシリアライズの互換性を考慮してください。

実行時のアクセス

任意のプレイヤーに関連付けられたRuntimePlayerアセットは、Frame.GetPlayerData()PlayerRefをクエリすることで取得できます。

C#

public void OnPlayerDataSet(Frame f, PlayerRef player){
  var data = f.GetPlayerData(player);
}

プレイヤーエンティティの初期化

プレイヤーが制御されるエンティティは、シミュレーションの任意のタイミングで初期化できます。一般的なアプローチは、プレイヤーの接続(ISignalOnPlayerConnected)/プレイヤーデータの受信(ISignalOnPlayerAdded)時に初期化することです。

  • ISignalOnPlayerConnected:シミュレーションやアセットデータベースで既に利用可能になっている情報から、プレイヤーエンティティを初期化できます。
  • ISignalOnPlayerAdded:プレイヤーのRuntimePlayerに関連付けられた固有情報から、プレイヤーエンティティを初期化できます。選択されたキャラクターモデルや所持品・装備品などの反映に便利です。

シミュレーション vs ビュー

まず、明確に説明をしておきます。

  1. シミュレーション観点(Quantum)では、プレイヤーが制御するエンティティは、プレイヤー入力を受けるエンティティです。ローカル/リモートプレイヤーの概念には関知しません。
  2. ビュー観点(Unity)では、ローカルクライアント上のプレイヤーから、入力をポーリングします。

要約すると、シミュレーションでは「ローカル」「リモート」プレイヤーの概念は存在せず、ビューではプレイヤーが「ローカル」であるかどうかを区別します。

C#

Photon.Deterministic.DeterministicGameMode.Local
Photon.Deterministic.DeterministicGameMode.Multiplayer
Photon.Deterministic.DeterministicGameMode.Replay

最大プレイヤー数

フレームごとのメモリチャンク内にどの程度のスペースを確保する必要があるのかを定義するためには、最大プレイヤー数を事前に知ることが重要になります。デフォルトの最大プレイヤー数は6です。

これを変更するには、任意のqtnファイルに次の行を追加します。

#define PLAYER_COUNT 8
#pragma max_players PLAYER_COUNT
  • defineは定義として機能し、DSL内で使用できます(例:プレイヤー数から配列を割り当てる)。
  • pragmaは、実際にシミュレーションが処理できるプレイヤーの数を定義します。
Back to top