This document is about: QUANTUM 3
SWITCH TO

コマンド

はじめに

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