Commands
概述
Quantum Commands是除了使用輸入之外,另一種將資料傳送到Quantum模擬的方式。指令與Quantum輸入類似,但 不 需要每一刷新都發送,可以在特定情況下觸發。
量子指令是完全可靠的。預設情況下,伺服器總是會接受並確認指令,無論指令何時發送。指令會在本機預測幀(predicted frames)中包含,並在本機機器上快速執行。但對於遠端客戶端(remote clients)來說有一個權衡:遠端客戶端無法預測指令被模擬接收的幀,因此指令會有延遲才會被接收。
指令是以繼承自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) {
      // 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>());
    }
  }
}
從視圖發送指令
指令可以從 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()有兩個多載。
C#
void SendCommand(DeterministicCommand command);
void SendCommand(Int32 player, DeterministicCommand command);
如果同一台機器控制多個玩家,可以指定玩家索引(PlayerRef)。只有一個本地玩家的遊戲可以忽略玩家索引欄位。
從模擬中輪詢指令
要在模擬中接收並處理指令,可以從幀中輪詢特定玩家的指令:
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;
            }
        }
    }
}
複合指令範例
每個幀(tick)只能將一個指令附加到輸入流中。即使客戶端可以在每個幀發送多個確定性指令(Deterministic Commands),這些指令也不會在同一幀到達模擬,而是會在連續的幀中分別到達。為了解決這個限制,可以將多個確定性指令打包成一個CompoundCommand,這是 SDK 提供的功能。
從視圖實例化並發送複合指令:
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 frame) {
  for (var i = 0; i < frame.PlayerCount; i++) {
      var compoundCommand = frame.GetPlayerCommand(i) as CompoundCommand;
      if (compoundCommand != null) {
        foreach (var cmd in compoundCommand.Commands) {
          // execute individual commands logic
        }
      }
  }
}