This document is about: QUANTUM 3
SWITCH TO

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
        }
      }
  }
}
Back to top