コマンド
イントロダクション
QUANTUMコマンドとは、Quantumの標準APIへの入力データパスです。QUANTUMインプットと似ていますが、毎ティックごとに送信される必要は ありません。
Quantumコマンドには、完全な信頼性があります。サーバーは 常に、コマンドが送信された時間に関わらずこれを受け入れ確認します。ただし、ローカルのクライアントが(コマンドを生成したそのクライアントでさえも)シミュレーションの一部となるチックを予測できないトレードオフを伴います。開発者は必要であればビジュアル予測を表示できますが、シミュレーションがコマンドを受信するのはサーバーがコマンドをチックの一部だと確認してからになります。
コマンドはPhoton.Deterministic.DeterministicCommand
から継承された通常のC#クラスとして実装されています。コマンドには、シリアル化できるデータ全てが含まれています。
C#
using Photon.Deterministic;
namespace Quantum
{
public class CommandSpawnEnemy : DeterministicCommand
{
public long enemyPrototypeGUID;
public override void Serialize(BitStream stream)
{
stream.Serialize(ref enemyPrototypeGUID);
}
public void Execute(Frame f)
{
var enemyPrototype = f.FindAsset<EntityPrototype>(enemyPrototypeGUID);
enemyPrototype.Container.CreateEntity(f);
}
}
}
Quantumシステムと同様に、ランタイム時に使用可能にするためにはコマンドもCommandSetup.cs
内に含まれている必要があります。以下を参照してください:
C#
using Photon.Deterministic;
namespace Quantum {
public static class CommandSetup {
public static DeterministicCommand[] CreateCommands(RuntimeConfig gameConfig, SimulationConfig simulationConfig) {
return new DeterministicCommand[] {
// user commands go here
new CommandSpawnEnemy(),
};
}
}
}
シミュレーションにおけるコマンドシステムのセットアップ
Quantumからコマンドを送信するためには、そのためのシステムを作成し、利用可能なコマンドを認識させる必要があります。
- Quantum 2.1 以降では、Command は
CommandSetup.User.cs
に登録する必要があります。 - Quantum 2.0 では、Command は
CommandSetup.cs
に登録する必要があります。
DeterministicCommandSetup
コマンドは CommandSetup.User.cs
にあるコマンドファクトリに追加して、実行時に利用できるようにする必要があります。
N.B.: CommandSetup.Legacy.cs
はこのセットアップでは直接使用されません。しかし、互換性の理由から 2.1 でも残しておく必要があります。
C#
// CommandSetup.User.cs
namespace Quantum {
public static partial class DeterministicCommandSetup {
static partial void AddCommandFactoriesUser(ICollection<IDeterministicCommandFactory> factories, RuntimeConfig gameConfig, SimulationConfig simulationConfig) {
// user commands go here
// new instances will be created when a FooCommand is received (de-serialized)
factories.Add(new FooCommand());
// BazCommand instances will be acquired from/disposed back to a pool automatically
factories.Add(new DeterministicCommandPool<BazCommand>());
}
}
}
----------
// CommandSetup.Legacy.cs
using Photon.Deterministic;
namespace Quantum {
public static class CommandSetup {
public static DeterministicCommand[] CreateCommands(RuntimeConfig gameConfig, SimulationConfig simulationConfig) {
return new null;
}
}
}
CommandSetup (2.0のみ!)
Quantum 2.0 では、コマンドは CommandSetup.cs
で登録しないと、実行時に利用できません。
N.B.: このシステムはQuantumの新しいバージョンでは廃止されています。詳しくは DeterministicCommandSetup
を参照してください。
C#
using Photon.Deterministic;
namespace Quantum {
public static class CommandSetup {
public static DeterministicCommand[] CreateCommands(RuntimeConfig gameConfig, SimulationConfig simulationConfig) {
return new DeterministicCommand[] {
// user commands go here
new CommandSpawnEnemy(),
};
}
}
}
コマンドをサーバーに送信する
コマンドはUnity内のどこからでも送信可能です:
C#
using Quantum;
using UnityEngine;
public class UISpawnEnemy : MonoBehaviour
{
[SerializeField] private EntityPrototypeAsset enemyPrototype = null;
private PlayerRef _playerRef;
public void Initialize(PlayerRef playerRef) {
_playerRef = playerRef;
}
public void SpawnEnemy() {
CommandSpawnEnemy command = new CommandSpawnEnemy()
{
enemyPrototypeGUID = enemyPrototype.Settings.Guid.Value,
};
QuantumRunner.Default.Game.SendCommand(command);
}
}
オーバーロード
SendCommand()
には2つのオーバーロードがあります。
C#
void SendCommand(DeterministicCommand command);
void SendCommand(Int32 player, DeterministicCommand command);
同じマシンで管理するマルチプレイヤーの場合、プレイヤーインデックス(PlayerRef)を指定します。1人のローカルプレイヤーのみで行うゲームの場合、プレイヤーインデックスのフィールドは空欄で構いません。
シミュレーションからコマンドをポーリングする
シミュレーションポール内でコマンドの受信・処理を行うには、対象のプレイヤーのフレームを以下のようにします:
C#
using Photon.Deterministic;
namespace Quantum
{
public class PlayerCommandsSystem : SystemMainThread
{
public override void Update(Frame f)
{
for (int i = 0; i < f.PlayerCount; i++)
{
var command = f.GetPlayerCommand(i) as CommandSpawnEnemy;
command?.Execute(f);
}
}
}
}
他のシステムと同様に、システムがコマンドポーリングを処理します。コンサンプションはSystemSetup.cs
に含まれる必要があります。
C#
namespace Quantum {
public static class SystemSetup {
public static SystemBase[] CreateSystems(RuntimeConfig gameConfig, SimulationConfig simulationConfig) {
return new SystemBase[] {
// pre-defined core systems
[...]
// user systems go here
new PlayerCommandsSystem(),
};
}
}
}
備考
API はコマンドの特定のコールバック機構やデザインパターンを強制したり、実装したりするものではありません。例えば、コマンドをシグナルにエンコードしたり、Chain of Responsibilityを使用したり、コマンドの実行をメソッドとして実装したりするなど、コマンドをどのように消費、解釈、実行するかは開発者の選択に委ねられています。
コレクションの例
リスト
C#
using System.Collections.Generic;
using Photon.Deterministic;
namespace Quantum
{
public class ExampleCommand : DeterministicCommand
{
public List<EntityRef> Entities = new List<EntityRef>();
public override void Serialize(BitStream stream)
{
var count = Entities.Count;
stream.Serialize(ref count);
if (stream.Writing)
{
foreach (var e in Entities)
{
var copy = e;
stream.Serialize(ref copy.Index);
stream.Serialize(ref copy.Version);
}
}
else
{
for (int i = 0; i < count; i++)
{
EntityRef readEntity = default;
stream.Serialize(ref readEntity.Index);
stream.Serialize(ref readEntity.Version);
Entities.Add(readEntity);
}
}
}
}
}
配列
C#
using Photon.Deterministic;
namespace Quantum
{
public class ExampleCommand : DeterministicCommand
{
public EntityRef[] Entities;
public override void Serialize(BitStream stream)
{
stream.SerializeArrayLength(ref Entities);
for (int i = 0; i < Cars.Length; i++)
{
EntityRef e = Entities[i];
stream.Serialize(ref e.Index);
stream.Serialize(ref e.Version);
Entities[i] = e;
}
}
}
}
複合コマンド(Compound Command)の例
ティックごとに1つのコマンドのみ入力ストリームにアタッチできます。クライアントがティックごとに複数の決定性コマンドを送信することができたとしても、コマンドは同じティックのシミュレーションに到達せず、引き続くティックに別々に到達します。この制限を回避するには、複数の決定性コマンドを単一の 複合コマンド(Compound Command) にまとめます。
Quantum 2.1では、すでにこのクラスを提供しています。また、以前のバージョンでは以下のように追加可能です:
C#
public class CompoundCommand : DeterministicCommand {
public static DeterministicCommandSerializer CommandSerializer;
public List<DeterministicCommand> Commands = new List<DeterministicCommand>();
public override void Serialize(BitStream stream) {
if (CommandSerializer == null) {
CommandSerializer = new DeterministicCommandSerializer();
CommandSerializer.RegisterPrototypes(CommandSetup.CreateCommands(null, null));
}
var count = Commands.Count;
stream.Serialize(ref count);
if (stream.Reading) {
Commands.Clear();
}
for (var i = 0; i < count; i++) {
if (stream.Reading) {
CommandSerializer.ReadNext(stream, out var cmd);
Commands.Add(cmd);
} else {
CommandSerializer.PackNext(stream, Commands[i]);
}
}
}
}
Compound Commandsを送信するには:
C#
public override void Update(Frame f) {
for (var i = 0; i < f.PlayerCount; i++) {
var compoundCommand = f.GetPlayerCommand(i) as CompoundCommand;
if (compoundCommand != null) {
foreach (var cmd in compoundCommand.Commands) {
}
}
}
}
Back to top