概要
はじめに
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と同じです
Integer
とPlayerRef
は、暗黙的キャストで相互に変換可能です。
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
Frame
APIから、プレイヤーに対応するPhoton Idを識別できます。
Frame.PlayerToActorId(PlayerRef player)
は、QuantumのPlayerRef
をActorId
(Photon Client Id)に変換します。Frame.ActorIdToAllPlayers(Int32 actorId)
は、上記メソッドのプロセスの逆です。
重要: Photon Idは、Quantumシミュレーションには一切関係しません。
ゲームの開始
SessionRunner.Start()
を呼び出すと、以下の開始プロトコルシーケンスが実行されます。
SessionConfig
とRuntimeConfig
を含むStartRequest
がサーバーに送信されます。- サーバーはリクエストを受信・検証し、選択した設定を含む
SimulationStart
の確認をクライアントに返送します。リクエストの情報が無効だった場合、リクエストはPluginDisconnect
メッセージで拒否されます。 - クライアントは
SimulationStart
メッセージを受信します。ここでクライアントはまだプレイヤーを持ちません。すべてのクライアントは観客としてのみ開始し、入力やコマンドを送信することはできません。 - (オプション) 途中参加する場合、クライアントは同期用のスナップショットを受信します。
QuantumGame.AddPlayer()
を使用してQuantumプレイヤーを登録し、個別にRuntimePlayer
設定を送信します。- サーバーは参加成功したプレイヤーを入力にエンコードし、すべてのクライアントで同じティックに
ISignalOnPlayerAdded
が呼び出されます。プレイヤー登録に失敗した場合は、OnLocalPlayerAddFailed
コールバックが呼び出されます。

関連する設定ファイルの詳細については、設定ファイルマニュアルをご覧ください。
プレイヤーの追加/削除
クライアントは、プレイヤーを追加する前に、ゲームが開始されるのを待つ必要があります。これには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 ビュー
まず、明確に説明をしておきます。
- シミュレーション観点(Quantum)では、プレイヤーが制御するエンティティは、プレイヤー入力を受けるエンティティです。ローカル/リモートプレイヤーの概念には関知しません。
- ビュー観点(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
は、実際にシミュレーションが処理できるプレイヤーの数を定義します。