Overview
簡介
Quantum與玩家實體的概念無關。在模擬中,所有實體都是一樣的。因此,在本文檔中當我們提到 “玩家” 時,指的是玩家控制的實體。
玩家識別
玩家可以通過兩種方式被識別:
- 他們的玩家索引
- 他們的玩家引用(PlayerRef)
玩家索引分配
Quantum 的player index由服務器根據QuantumGame.AddPlayer()操作到達的順序分配。這不要與基於玩家加入 Photon房間 順序的 Photon/Actor Id 混淆。
無法設置 “期望的 Quantum ID”。
注意: 在斷開連接的情況下,我們保證 如果 客戶端使用相同的 ClientId 重新連接,它將獲得相同的玩家索引;無論它們的 Photon ID 是什麼 - public static QuantumRunner StartGame(String clientId, Int32 playerCount, StartParameters param).
玩家索引與玩家引用
PlayerRef是 Quantum ECS 中player index的包裝器。PlayerRef是從 1 開始的,而player index從 0 開始。原因是default(PlayerRef)會方便地返回一個 “空 / 無效” 的玩家引用結構。
- default(PlayerRef),內部為 0,表示無人
- PlayerRef,內部為 1,對應玩家索引 0
- PlayerRef,內部為 2,對應玩家索引 1
自動轉換操作符可以將Integer轉換為PlayerRef,反之亦然。
C#
// DeterministicCommand GetPlayerCommand(PlayerRef player);
for (int p = 0; p < f.PlayerCount; p++) {
var command = f.GetPlayerCommand(p);
}
玩家插槽(PlayerSlot)
Quantum 支持在一個游戲客戶端中運行多個本地玩家。從 0 開始的player slot 指代所有本地玩家。
一些 API 明確要求選擇一個玩家插槽,當只有一個本地玩家時,該插槽為 0。
更多內容請閱讀下一章Multiple Local Players。
是否為本地玩家
Quantum在視圖中提供了兩個 API 來檢查一個玩家是否為本地玩家:
QuantumRunner.Default.Game.Session.IsLocalPlayer(int player)QuantumRunner.Default.Game.PlayerIsLocal(PlayerRef playerRef)
獲取本地玩家
QuantumRunner.Default.Game.GetLocalPlayers()返回一個在每個客戶端上都是唯一的數組,該數組表示本地客戶端在 Quantum 模擬中控制的全局玩家索引。
QuantumRunner.Default.Game.GetLocalPlayerSlots()返回一個在每個客戶端上都是唯一的數組,該數組表示本地客戶端在 Quantum 模擬中控制的玩家插槽。
- 如果只有一個本地玩家,這些方法返回一個索引。如果在同一台本地機器上有多個玩家,那麼這些數組的長度將等於本地玩家的數量。
- 當重新加入游戲時,使用相同的 Quantum
SessionRunner.Arguments.ClientId啟動會話時,相同的玩家索引將分配給客戶端。
Photon ID
你可以通過Frame API 識別玩家對應的 Photon ID:
Frame.PlayerToActorId(PlayerRef player)將 Quantum PlayerRef 轉換為 ActorId(Photon 客戶端 ID);或者Frame.ActorIdToAllPlayers(Int32 actorId)上述方法的反向操作。
重要: Photon ID 與 Quantum 模擬無關。
啟動游戲
調用SessionRunner.Start()後,將執行以下啟動協議序列:
- 發送一個 StartRequest 到服務器,並附加 SessionConfig 和 RuntimeConfig
- 服務器接收請求,進行驗證,然後發送一個
SimulationStart確認信息回客戶端,其中包含選擇的配置。如果請求附加的信息無效,請求將被PluginDisconnect消息拒絕。 - 客戶端接收
SimulationStart游戲消息。此時客戶端還沒有玩家。它們都將以僅旁觀者身份啟動,不能發送輸入或命令。 - (可選) - 在延遲加入的情況下,客戶端可能會收到快照重新同步。
- 使用
QuantumGame.AddPlayer()注冊一個 Quantum 玩家,並發送單獨的 RuntimePlayer 配置。 - 服務器將成功加入的玩家編碼到輸入中,所有客戶端將在同一個 tick 處理該輸入,並調用
ISignalOnPlayerAdded信號。失敗的嘗試將通過OnLocalPlayerAddFailed回調來回應。
有關所涉及的配置文件的更多信息,請參閱配置文件手冊。
添加和移除玩家
在客戶端可以添加玩家之前,它必須等待游戲啟動完成。這可以通過兩種方式實現:
A) 使用SessionRunner.StartAsync的異步版本並等待返回。
C#
// this will return once the connection logic is complete (e.g. received snapshot if needed)
var runner = (QuantumRunner)await SessionRunner.StartAsync(sessionRunnerArguments);
// adding player to the online simulation
var runtimePlayer = new RuntimePlayer { PlayerNickname = "whiskeyjack29" };
runner.Game.AddPlayer(runtimePlayer);
B) 像QuantumAddRuntimePlayers.cs或QuantumRunnerLocalDebug.cs腳本中所示的那樣注冊CallbackGameStarted。
C#
public class QuantumAddRuntimePlayers : QuantumMonoBehaviour {
public RuntimePlayer[] Players;
public void Awake() {
QuantumCallback.Subscribe(this, (CallbackGameStarted c) => OnGameStarted(c.Game, c.IsResync), game => game == QuantumRunner.Default.Game);
}
public void OnGameStarted(QuantumGame game, bool isResync) {
for (int i = 0; i < Players.Length; i++) {
game.AddPlayer(i, Players[i]);
}
}
}
每個插槽只能調用一次 AddPlayer ()。服務器在出現任何錯誤時將發送AddPlayerFailed協議事件(見下面的回調)。
由於該操作可能會調用網絡請求,因此存在速率限制以保護第三方後端。
客戶端可以請求的玩家總數可能會受到網絡鉤子或 Photon 儀表板參數MaxPlayerSlots的限制。
以下 API 發送與玩家相關的操作:
C#
class QuantumGame {
// Sends player data to the server and request adding a player (using player slot 0)
void AddPlayer(RuntimePlayer data);
// Sends player data a certain for player slot to the server
void AddPlayer(int playerSlot, RuntimePlayer data);
// Request removing player slot 0
void RemovePlayer();
// Removing certain player float
void RemovePlayer(int playerSlot);
// Remove all players that belong to this client
void RemoveAllPlayers();
}
可以監聽以下 Quantum 回調並在視圖中使用:
C#
CallbackLocalPlayerAddConfirmed {
public Frame Frame;
public int PlayerSlot;
public PlayerRef Player;
}
CallbackLocalPlayerAddFailed {
public int PlayerSlot;
public string Message;
}
CallbackLocalPlayerRemoveConfirmed {
public Frame Frame;
public int PlayerSlot;
public PlayerRef Player;
}
CallbackLocalPlayerRemoveFailed {
public int PlayerSlot;
public string Message;
}
// for example
QuantumCallback.Subscribe(this, (CallbackLocalPlayerAddConfirmed c) => OnLocalPlayerAddConfirmed(c));
private void OnLocalPlayerAddConfirmed(CallbackLocalPlayerAddConfirmed c) { }
可以監聽以下 Quantum 信號並在模擬中使用。這些信息不包含玩家插槽,因為這只是本地玩家可用的信息。
C#
// The first time that this player ref was assigned to a player at all.
// When firstTime is false the player ref is being reused by a different player.
ISignalOnPlayerAdded(Frame frame, PlayerRef player, bool firstTime)
ISignalOnPlayerRemoved
(Frame frame, PlayerRef player)
PlayerConnectedSystem
PlayerConnectedSystem是另一種使用輸入和連接標誌跟蹤玩家在線連接狀態的方式。將其添加到SystemsConfig中即可使用。
要接收連接和斷開連接的 Quantum 系統回調,必須實現ISignalOnPlayerConnected 和ISignalOnPlayerDisconnected。
RuntimePlayer
RuntimePlayer類包含玩家特定的信息,例如所選擇的角色。
該類在發送到服務器和從服務器接收時會進行 Json 序列化。
RuntimePlayer是quantum.code項目中的一個部分類,自定義實現應在RuntimePlayer.User.cs中完成。添加數據時請考慮 Json 序列化的兼容性。
在運行時訪問
與任何玩家關聯的RuntimePlayer資產可以通過向Frame.GetPlayerData()傳遞他們的PlayerRef來檢索。
C#
public void OnPlayerDataSet(Frame frame, PlayerRef player){
var data = frame.GetPlayerData(player);
}
初始化玩家實體
玩家控制的實體可以在模擬期間的任何時候初始化。一種常見的方法是在玩家連接時(ISignalOnPlayerConnected)和 / 或收到玩家數據時(ISignalOnPlayerAdded)初始化它。
ISignalOnPlayerConnected:可以使用模擬中已有的任何信息或資產數據庫中的信息來初始化玩家實體。ISignalOnPlayerAdded:可以使用與玩家的RuntimePlayer特定信息相關聯的信息來初始化玩家實體。這便於處理諸如所選擇的角色模型或庫存裝備之類的事情。
模擬與視圖
首先,做一些說明:
- 從模擬的角度(Quantum)來看,玩家控制的實體是具有玩家輸入的實體。它不知道本地或遠程玩家。
- 從視圖的角度(Unity)來看,我們從本地客戶端的玩家那裡輪詢輸入。
總結一下,在模擬中沒有 “本地” 或 “遠程” 玩家之說;然而,在視圖中,玩家要麼是 “本地的”,要麼不是。
C#
Photon.Deterministic.DeterministicGameMode.Local
Photon.Deterministic.DeterministicGameMode.Multiplayer
Photon.Deterministic.DeterministicGameMode.Replay
最大玩家數
最大玩家數事先知道是很重要的,因為它決定了每個幀的內存塊中需要分配多少空間。默認的最大玩家數是6。
要更改它,請在你的任何qtn文件中添加以下行:
#define PLAYER_COUNT 8
#pragma max_players PLAYER_COUNT
define就像一個定義,可以在 DSL 中使用(例如,用於分配具有玩家數量的數組)。pragma實際上定義了模擬可以處理的玩家數量。