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