コマンド
はじめに
Quantumコマンドは、入力の代替としてQuantumシミュレーションにデータを送信する方法です。コマンドは入力に似ていますが、毎ティックの送信が不要で、特定の状況でトリガーされるものです。
Quantumコマンドは、完全に信頼性があります。デフォルトでは、送信された時間にかかわらず、サーバーは常にコマンドを受け入れます。コマンドはローカルの予測フレームにも含まれ、ローカル端末で迅速に実行されます。しかし、リモートクライアントではトレードオフがあり、シミュレーションからコマンドを受信するティックは予測できないため、コマンドを受信するまで遅延が発生します。
コマンドは、Photon.Deterministic.DeterministicCommand
を継承した通常のC#クラスとして実装されます。そこで、任意のシリアライズ可能なデータを含むことができます。
C#
namespace Quantum
{
using Photon.Deterministic;
public class CommandSpawnEnemy : DeterministicCommand
{
public AssetRefEntityPrototype EnemyPrototype;
public override void Serialize(BitStream stream)
{
stream.Serialize(ref EnemyPrototype);
}
public void Execute(Frame frame)
{
frame.Create(EnemyPrototype);
}
}
}
シミュレーション中のコマンド設定
コマンドクラスを定義したら、DeterministicCommandSetup
のファクトリーに登録が必要です。Assets/QuantumUser/Simulation
に移動し、スクリプトCommandSetup.User.cs
を開いてください。そして次のように、ファクトリーにコマンドを追加します。
C#
// CommandSetup.User.cs
namespace Quantum {
using System.Collections.Generic;
using Photon.Deterministic;
public static partial class DeterministicCommandSetup {
static partial void AddCommandFactoriesUser(ICollection<IDeterministicCommandFactory> factories, RuntimeConfig gameConfig, SimulationConfig simulationConfig) {
// ユーザー定義コマンドはここに追加する
// FooCommandを受信(デシリアライズ)すると、新しいインスタンスが作成される
factories.Add(new FooCommand());
// BazCommandインスタンスは、自動的にプールから取得/返却される
factories.Add(new DeterministicCommandPool<BazCommand>());
}
}
}
ビューからのコマンド送信
コマンドはUnity内のどこからでも送信できます。
C#
namespace Quantum
{
using UnityEngine;
public class EnemySpawnerUI : MonoBehaviour
{
[SerializeField] private AssetRefEntityPrototype _enemyPrototype;
public void SpawnEnemy()
{
CommandSpawnEnemy command = new CommandSpawnEnemy()
{
EnemyPrototype = _enemyPrototype,
};
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 frame)
{
for (int i = 0; i < f.PlayerCount; i++)
{
var command = frame.GetPlayerCommand(i) as CommandSpawnEnemy;
command?.Execute(frame);
}
}
}
}
注意
コマンドに対してAPIは、特定のコールバックメカニズムやデザインパターンの実装を強制しません。コマンドをどのように消費・解釈・実行するかは開発者の判断に委ねられます。例えば、Chain of Responsibilityパターンを使用して、コマンドをシグナルにエンコードしたり、コマンドを実行するメソッドを実装したりすることができます。
コレクションの例
リスト
C#
namespace Quantum
{
using System.Collections.Generic;
using Photon.Deterministic;
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#
namespace Quantum
{
using Photon.Deterministic;
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;
}
}
}
}
複合コマンドの例
入力ストリームには、1ティックあたり1コマンドしかアタッチできません。クライアントは1ティックで複数の決定論的コマンドを送信できますが、コマンドは同じティックのシミュレーションで到達するのではなく、連続するティックで別々に到着します。この制限を回避するために、複数の決定論的コマンドを1つのCompoundCommand
にパッキングすることが可能です。
複合コマンドをビューから生成/送信する方法は次の通りです。
C#
var compound = new Quantum.Core.CompoundCommand();
compound.Commands.Add(new FooCommand());
compound.Commands.Add(new BazCommand());
QuantumRunner.Default.Game.SendCommand(compound);
複合コマンドの処理は次の通りです。
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