Network Input
簡介
輸入的定義、輪詢和消耗是Fusion的核心。
輸入結構的定義
輸入結構可以根據需要存儲簡單或復雜的數據。Fusion將只傳輸實際變化的數據。因此,盡管保持數據的緊湊性是有利的(例如,為按鈕使用標誌),但包括很少使用的字段也是可以的。一個例子是收集幾個本地玩家的輸入,即使大部分只有一個玩家-如果這些其他玩家沒有實際提供輸入,他們不會影響輸入結構的頻寬使用,只會影響本地內存。
輸入結構有以下限制:
- 它必須繼承自
INetworkInput
; - 它只能包含原始類型和結構;
- 輸入結構和它所持有的任何結構必須是頂級結構(即不能嵌套在一個類別中);並且,
- 對於布林值,使用
NetworkBool
而不是bool
-C#在不同平台上對布林的大小並不一致,所以使用NetworkBool
來正確地將其序列化為一個位元。
Fusion將智能地映射結構的類型;這允許為不同的遊戲模式或遊戲的不同部分使用不同的結構。當對輸入進行解包時,Fusion將只返回正確類型的可用輸入。
C#
public struct MyInput : INetworkedInput {
public Vector3 aimDirection;
}
按鈕
有一個特殊的NetworkButtons
類型,為在INetworkInput
結構中保存按鈕提供了一個方便的封裝。
要在輸入結構中添加按鈕,只需:
- 為按鈕創建一個枚舉( Important: 必須明確定義並從0開始);並且,
- 在
INetworkedInput
中添加一個NetworkButtons
變數。
C#
enum MyButtons {
Forward = 0,
Backward = 1,
Left = 2,
Right = 3,
}
public struct MyInput : INetworkInput {
public NetworkButtons buttons;
public Vector3 aimDirection;
}
可用於直接從NetworkButtons
變數中賦值和讀值的API是:
void Set(int button, bool state)
:獲取按鈕的枚舉值和它的狀態(已點擊=真,未點擊=假)。bool IsSet(int button)
:獲取按鈕的枚舉值並返回其布林狀態。
NetworkButtons
類型是無狀態的,因此不包含有關按鈕先前狀態的任何位元數據。為了能夠使用 NetworkButtons
提供的下一組方法,有必要追蹤按鈕的先前狀態;這很容易通過為每個玩家創建一個先前狀態的[Networked]
版本來完成。
C#
public class PlayerInputExample : NetworkBehaviour {
[Networked] public NetworkButtons ButtonsPrevious { get; set; }
...
}
有了這個,就可以將按鈕的當前狀態與它們之前的狀態進行比較,以評估按鈕是剛剛被按下還是被釋放。
NetworkButtons GetPressed(NetworkButtons previous)
:為所有剛剛按下的按鈕返回一組值。NetworkButtons GetReleased(NetworkButtons previous)
:為所有剛剛釋放的按鈕返回一組值。(NetworkButtons, NetworkButtons) GetPressedOrReleased(NetworkButtons previous)
:返回剛剛按下和釋放的按鈕的值元組。
IMPORTANT: 只能使用Input.GetKey()
來指定按鈕的值。不要使用Input.GetKeyDown()
或Input.GetKeyUp()
,因為它們不與Fusion ticks同步,因此可能被錯過。
輪詢輸入
Fusion通過輪詢本地客戶端和填充先前定義的輸入結構來收集輸入。Fusion Runner只追蹤一個輸入結構,因此強烈建議在一個地方執行輸入輪詢,以避免任何意外行為。
Fusion Runner通過呼叫INetworkRunnerCallbacks.OnInput()
方法來輪詢輸入。OnInput()
的執行可以用選擇的數據填充任何繼承自INetworkInput
的結構。通過在提供的NetworkInput
上呼叫Set()
,將填充的結構傳回給Fusion。
IMPORTANT: 如果有多個輪詢點,除了最後一個輸入結構的版本,其他的都會被覆蓋。
模擬行為
Fusion automatically 對所有執行了INetworkRunnerCallbacks
接口的SimulationBehaviour
和NetworkBehaviour
組件呼叫OnInput()
,並且客戶端對其有Input Authority
- 因此得名。
C#
public class InputProvider : SimulationBehaviour, INetworkRunnerCallbacks {
public void OnInput(NetworkRunner runner, NetworkInput input) {
var myInput = new MyInput();
myInput.Buttons.Set(MyButtons.Forward, Input.GetKey(KeyCode.W));
myInput.Buttons.Set(MyButtons.Backward, Input.GetKey(KeyCode.S));
myInput.Buttons.Set(MyButtons.Left, Input.GetKey(KeyCode.A));
myInput.Buttons.Set(MyButtons.Right, Input.GetKey(KeyCode.D));
myInput.Buttons.Set(MyButtons.Jump, Input.GetKey(KeyCode.Space));
input.Set(myInput);
}
}
MonoBehaviour and Pure CSharp
要從一個普通的CSharp腳本或MonoBehaviour
中輪詢輸入,請遵循以下步驟:
- 執行
INetworkRunnerCallbacks
和OnInput()
;以及, - 通過呼叫
AddCallbacks()
將腳本註冊到NetworkRunner
中。
C#
public class InputProvider : Monobehaviour, INetworkRunnerCallbacks {
public void OnEnable(){
var myNetworkRunner = FindObjectOfType<NetworkRunner>();
myNetworkRunner.AddCallbacks( this );
}
public void OnInput(NetworkRunner runner, NetworkInput input) {
// 與SimulationBehaviour和NetworkBehaviour的片段相同。
}
public void OnDisable(){
var myNetworkRunner = FindObjectOfType<NetworkRunner>();
myNetworkRunner.RemoveCallbacks( this );
}
}
讀取輸入
仿真可以讀取輸入,根據之前輪詢的輸入,將現有的網路化狀態從當前狀態修改為新的狀態。Fusion在網路上同步輸入結構,並使其在擁有輸入權限的客戶端和擁有狀態權限的客戶端(主機)的仿真過程中可用。
與輪詢輸入相反,讀取輸入可以根據需要在許多不同的地方進行。
N.B.: 玩家的輸入只對有輸入權限和狀態權限的客戶端有效。在HostMode
和ServerMode
中,這意味著玩家客戶端和主機/伺服器,而在SharedMode
中,這是一個相同的客戶端。
不可能讀取一個客戶端對另一個客戶端的輸入。因此,任何依賴輸入的變化都需要被保存為[Networked]
狀態,以便在其他客戶端上複製。
GetInput()
要獲得輸入結構,請在任何對相關對象具有輸入權限的NetworkBehaviour
(例如,控制玩家運動的組件)的FixedUpdateNetwork()
中呼叫GetInput(out T input)
。對GetInput()
的呼叫提供了先前在OnInput()
中填充的相同輸入結構。
對GetInput()
的呼叫將在以下情況下返回錯誤:
- 客戶端沒有狀態權限或輸入權限
- 要求的輸入類型在模擬中不存在
C#
using Fusion;
using UnityEngine;
public class PlayerInputExample : NetworkBehaviour {
[Networked] public NetworkButtons ButtonsPrevious { get; set; }
public override void FixedUpdateNetwork() {
NetworkButtons buttons = default;
if (GetInput<MyInput>(out var input) == false) return;
buttons = input.Buttons;
// 計算按下/釋放的狀態
var pressed = buttons.GetPressed(ButtonsPrevious);
var released = buttons.GetReleased(ButtonsPrevious);
// 將最新的輸入作為我們的’previous’狀態來存儲
ButtonsPrevious = buttons;
// 移動(檢查是否向下)
var vector = default(Vector3);
if (buttons.IsSet(MyButtons.Forward)) { vector.z += 1; }
if (buttons.IsSet(MyButtons.Backward)) { vector.z -= 1; }
if (buttons.IsSet(MyButtons.Left)) { vector.x -= 1; }
if (buttons.IsSet(MyButtons.Right)) { vector.x += 1; }
DoMove(vector);
// 跳躍(檢查是否被按下)
if (pressed.IsSet(MyButtons.Jump)) {
DoJump();
}
}
void DoMove(Vector3 vector) {
// 沒有邏輯的防傻方法
}
void DoJump() {
// 沒有邏輯的防傻方法
}
}
Runner.TryGetInputForPlayer()
通過呼叫NetworkRunner.TryGetInputForPlayer<T>(PlayerRef playerRef, out var input)
可以從NetworkBehaviour
外部讀取輸入。除了INetworkInput
類型外,它還需要指定要檢索輸入的玩家。 N.B.: 適用於GetInput()
的限制;即在有輸入權限的客戶端或伺服器/主機上可以獲得指定玩家的輸入。
C#
var myNetworkRunner = FindObjectOfType<NetworkRunner>();
// 如果腳本只在客戶端執行,則以本地玩家為例
if(myNetworkRunner.TryGetInputForPlayer<MyInput>(myNetworkRunner.LocalPlayer, out var input)){
// 做邏輯
}
授權說明
為了保証完全的模擬權限,關鍵是在填充輸入結構時只在OnInput()
中收集輸入值。基於輸入要執行的邏輯應該完全在GetInput()
中完成。
例如,下面的拆分將被用於發射一顆子彈:
OnInput()
:為玩家保存發射按鈕的值。GetInput()
:檢查射擊按鈕是否被按下,如果被按下就發射子彈。