プレイヤー
イントロ
Quantumは、プレイヤーエンティティの概念にとらわれません。シミュレーションでは、すべてのエンティティは同じです。したがって、このドキュメントでの "プレイヤー "は、プレイヤーがコントロールするエンティティ を意味します。
プレイヤーの識別
プレイヤーは2つの方法で識別することができます。
- プレイヤーインデックス
- PlayerRef
プレイヤーインデックスの割り当て
Quantumの player index
は Session.Join()
メッセージの到着順に基づいてサーバによって割り当てられる。Photon room に参加したプレイヤーの順番に基づいているPhoton IDと混同しないようにしてください。
Photonプレイヤーに「希望するQuantum Id」を設定することはできません。
注:切断された場合、クライアントが同じ ClientId で再接続した場合、Photon ID に関係なく同じプレイヤーインデックスを取得することを保証します(public static QuantumRunner StartGame(String clientId, Int32 playerCount, StartParameters param)
)
Player Index vs PlayerRef
PlayerRef
はQuantum ECSのplayer index
のラッパーである。PlayerRef
が1ベースであるのに対し、player index
は0から始まります。利便性のため、default(PlayerRef)
が"null/invalid "のplayer ref構造体を返すためです。
intをPlayerRefにキャストできる自動キャスト演算子や、その逆ももあります。
- default(PlayerRef)、内部的には0、NOBODYを意味します。
- PlayerRef、内部的には1、プレイヤーインデックス0と同じ。
- PlayerRef、内部的には2、プレイヤーインデックス1と同じ。
Photon Id
Frame
APIを使って、プレイヤーの対応するPhoton IDを特定することができます。
Frame.PlayerToActorId(PlayerRef player)
はQuantum PlayerRefをアクターID(PhotonクライアントID)に変換します。または、Frame.ActorIdToAllPlayers(Int32 actorId)
で前のメソッドの逆の処理を行います。
例えば、PhotonPlayer.Nickname
でプレイヤー名を表示する場合に使用します。
重要: Photon IDはQuantumシミュレーションとは無関係です。
ゲームに参加する
ゲームが始まると、次のようなことが順番に起こります。
QuantumRunner.Session.Join()
は、希望するプレイヤー数の参加要求をサーバに送信します。- リクエストはサーバによって受信され、それが検証され、確認がユーザに送られます。リクエストに添付された情報が有効でない場合、リクエストは拒否されます。
- クライアントがゲーム開始メッセージを受信します。
- プレイヤーが入力を送受信できるようになりました。
- (任意) - 遅い参加の場合、クライアントはスナップショット再同期を受け取るかもしれません。この場合、ステップ4ではスナップショットを待っている間は入力を送信しません。
- (任意) -
SendPlayerData
が使えるようになった。これはゲームセッション中(ゲーム開始時やセッション中)に何度でも使用することができます。SendPlayerData
が呼ばれるたびに、RuntimePlayer
のシリアル化されたバージョンをサーバに送信し、サーバはテック入力セットの確認に添付することで、決定論的にシグナルをトリガーします。
関係する設定ファイルの詳細については、マニュアルの Configuration Files ドキュメントを参照してください。
SendPlayerData
QuantumGame.SendPlayerData(RuntimePlayer features)
は、特殊な種類のデータ(RuntimePlayer)を決定論的に入力ストリームに注入するためのデータパスです。SendPlayerData
はゲーム開始時に呼び出されて全てのプレイヤーを設定するのが一般的であるが、ゲームセッション中にデータを更新する必要がある場合に呼び出すことも可能です。
Quantumゲームを開始した後、参加すると CallbackGameStarted
コールバックが発生します。この時、各プレイヤーは SendPlayerData
メソッドを呼び出して、他のプレイヤーのシミュレーションにプレイヤーとして追加することができます。これを明示的に呼び出すことで、後から参加するプレイヤーの処理を大幅に簡素化することができます。
C#
public class MyCallbacks : MonoBehaviour {
private void OnEnable() {
QuantumCallback.Subscribe<CallbackGameStarted>(this, OnGameStart);
}
private void OnGameStart(CallbackGameStarted callback) {
// paused on Start means waiting for Snapshot
if (callback.Game.Session.IsPaused) return;
// It needs to be sent for each local player.
foreach (var lp in callback.Game.GetLocalPlayers()) {
Debug.Log("CustomCallbacks - sending player: " + lp);
callback.Game.SendPlayerData(lp, new Quantum.RuntimePlayer { });
}
}
}
プレイヤーエンティティはローカルモードではインスタンス化されますが、マルチプレイヤーモードではインスタンス化されません。
各プレイヤーに対して QuantumGame.SendPlayerData()
が実行されていない可能性の方が高いです。デモメニューを使ってゲームを開始する場合は、メニューシーンの任意の場所に CustomCallbacks.cs
スクリプトを追加してください。
PlayerConnectedSystem
Quantumセッションへの接続を管理するために,Input & Connection Flags が使用されます。PlayerConnectedSystem
は、その手続きを自動化し、プレイヤーがセッションに接続した場合、または切断した場合にシミュレーションに通知します。このシステムを利用するためには、SystemSetup
に追加する必要があります。
C#
public static class SystemSetup {
public static SystemBase[] CreateSystems(RuntimeConfig gameConfig, SimulationConfig simulationConfig) {
return new SystemBase[] {
// pre-defined core systems
...
new PlayerConnectedSystem(),
// custom systems
...
}
}
}
接続と切断のコールバックを受け取るためには、ISignalOnPlayerConnected
とISignalOnPlayerDisconnected
をシステムに実装する必要があります。
ISignalOnPlayerDataSet
システムにISignalOnPlayerDataSet
を実装すると、public void OnPlayerDataSet(Frame f, PlayerRef playerRef)
にアクセスできるようになります。 シリアライズ化されたRuntimePlayer
が特定のティックインプットの一部になっている場合、毎回OnPlayerDataSet
が呼び出されます。
RuntimePlayer
クラス RuntimePlayer
は、例えば選択されたキャラクターのようなプレイヤー固有の情報を保持します。RuntimePlayer
をカスタムのニーズに合わせて動作するためには、シリアライズメソッドを実装する必要があります - アセットリンクの場合は、GUIDをシリアライズする必要があります。
RuntimePlayer
は quantum.code
プロジェクトのパーシャルクラスです。SDKのアップグレードや将来の検証を容易にするために、カスタム実装は RuntimePlayer.User.cs
で行います。ここで、各プレイヤーに指定したいパラメータを追加し、SerializeUserData
メソッドでシリアライズを実装することができます。結果は以下のようになります。
C#
namespace Quantum {
partial class RuntimePlayer {
public AssetRefCharacterSpec CharacterSpec;
partial void SerializeUserData(BitStream stream)
{
stream.Serialize(ref CharacterSpec.Guid);
}
}
}
Accessing at Runtime
プレイヤーと関連のあるRuntimePlayer
アセットは、PlayerRef
でFrame.GetPlayerData()
をクエリすることで取得可能です。
C#
public void OnPlayerDataSet(Frame f, PlayerRef player){
var data = f.GetPlayerData(player);
}
シミュレーションとビュー
まず最初にいくつかのことを説明します。
- シミュレーションの視点(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
は実際にシミュレーションが扱えるプレイヤーの数を定義します。
ローカルプレイヤー
Quantumは、プレイヤーがローカルであるかどうかを確認するために以下のビューのAPIを提供しています。
QuantumRunner.Default.Game.Session.IsLocalPlayer(int player)
QuantumRunner.Default.Game.PlayerIsLocal(PlayerRef playerRef)
複数のローカルプレイヤー
QuantumRunner.Default.Game.GetLocalPlayers()
は、クライアントごとに固有の配列を返し、ローカルマシンがQuantumシミュレーションでコントロールするプレイヤーのインデックスを表します。
- ローカルプレイヤーが 1 人しかいない場合は、1 つのインデックスを返します。複数のプレイヤーが同じローカルマシンコントロール上にいる場合、配列はローカルプレイヤー数の長さになります。
- これらのインデックスは、
QuantumInput.Instance.PollInput(int player)
に渡されるものと全く同じです。 - インデックスはサーバーが定義します(ローカルゲームでない限り)。
- インデックスは常に[0, PlayerCount-1]の範囲内です。
PlayerCount
は、マッチに参加しているプレイヤーの総数を表します。これはQuantumRunner.StartGame
に渡されます。 - インデックスの値は任意の値で(このセッションでは0から最大プレイヤー数までの範囲内)、複数のプレイヤーが接続・切断する順番や、メッセージがサーバに到達するタイミングに依存します。
- ローカルマシンに複数のプレイヤーがいる場合、値が連続しているとは限りません。
- ゲームに再参加する際には、
Session.Join()
を同じGUIDで呼び出して、切断してから新しいプレイヤーが入っていなければ、同じプレイヤーインデックスを割り当てることができます。
ランタイムプレイヤーのデータを送信するには、上記の関数からローカルプレイヤーのインデックスを使用します: QuantumGame.SendPlayerData(int player, RuntimePlayer data)
. これを1台のマシン上のすべてのプレイヤーに対して行います。