This page is a work in progress and could be pending updates.

Network Input

簡介

輸入的定義、輪詢和消耗是Fusion的核心。

Back To Top

輸入結構的定義

輸入結構可以根據需要存儲簡單或復雜的數據。Fusion將只傳輸實際變化的數據。因此,盡管保持數據的緊湊性是有利的(例如,為按鈕使用標誌),但包括很少使用的字段也是可以的。一個例子是收集幾個本地玩家的輸入,即使大部分只有一個玩家-如果這些其他玩家沒有實際提供輸入,他們不會影響輸入結構的頻寬使用,只會影響本地內存。

輸入結構有以下限制:

  • 它必須繼承自INetworkInput
  • 它只能包含原始類型和結構;
  • 輸入結構和它所持有的任何結構必須是頂級結構(即不能嵌套在一個類別中);並且,
  • 對於布林值,使用NetworkBool而不是bool-C#在不同平台上對布林的大小並不一致,所以使用NetworkBool來正確地將其序列化為一個位元。

Fusion將智能地映射結構的類型;這允許為不同的遊戲模式或遊戲的不同部分使用不同的結構。當對輸入進行解包時,Fusion將只返回正確類型的可用輸入。

public struct MyInput : INetworkedInput {
    public Vector3 aimDirection;
}

Back To Top

按鈕

有一個特殊的NetworkButtons類型,為在INetworkInput結構中保存按鈕提供了一個方便的封裝。

要在輸入結構中添加按鈕,只需:

  1. 為按鈕創建一個枚舉( Important: 必須明確定義並從0開始);並且,
  2. INetworkedInput中添加一個NetworkButtons變數。
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]版本來完成。

public class PlayerInputExample : NetworkBehaviour {
    [Networked] public NetworkButtons ButtonsPrevious { get; set; }

    ...
}
對於完整的代碼片段,請參閱下面的 __GetInput__ 部分。

有了這個,就可以將按鈕的當前狀態與它們之前的狀態進行比較,以評估按鈕是剛剛被按下還是被釋放。

  • NetworkButtons GetPressed(NetworkButtons previous):為所有剛剛按下的按鈕返回一組值。
  • NetworkButtons GetReleased(NetworkButtons previous):為所有剛剛釋放的按鈕返回一組值。
  • (NetworkButtons, NetworkButtons) GetPressedOrReleased(NetworkButtons previous):返回剛剛按下和釋放的按鈕的值元組。

IMPORTANT: 只能使用Input.GetKey()來指定按鈕的值。不要使用Input.GetKeyDown()Input.GetKeyUp(),因為它們不與Fusion ticks同步,因此可能被錯過。

Back To Top

輪詢輸入

Fusion通過輪詢本地客戶端和填充先前定義的輸入結構來收集輸入。Fusion Runner只追蹤一個輸入結構,因此強烈建議在一個地方執行輸入輪詢,以避免任何意外行為。

Fusion Runner通過呼叫INetworkRunnerCallbacks.OnInput()方法來輪詢輸入。OnInput()的執行可以用選擇的數據填充任何繼承自INetworkInput的結構。通過在提供的NetworkInput上呼叫Set(),將填充的結構傳回給Fusion。

IMPORTANT: 如果有多個輪詢點,除了最後一個輸入結構的版本,其他的都會被覆蓋。

Back To Top

模擬行為

Fusion automatically 對所有執行了INetworkRunnerCallbacks接口的SimulationBehaviourNetworkBehaviour組件呼叫OnInput(),並且客戶端對其有Input Authority- 因此得名。

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);
  }
}

Back To Top

MonoBehaviour And Pure CSharp

要從一個普通的CSharp腳本或MonoBehaviour中輪詢輸入,請遵循以下步驟:

  1. 執行INetworkRunnerCallbacksOnInput();以及,
  2. 通過呼叫AddCallbacks()將腳本註冊到NetworkRunner中。

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 );
  }
}

Back To Top

讀取輸入

仿真可以讀取輸入,根據之前輪詢的輸入,將現有的網路化狀態從當前狀態修改為新的狀態。Fusion在網路上同步輸入結構,並使其在擁有輸入權限的客戶端和擁有狀態權限的客戶端(主機)的仿真過程中可用。

與輪詢輸入相反,讀取輸入可以根據需要在許多不同的地方進行。

N.B.: 玩家的輸入只對有輸入權限和狀態權限的客戶端有效。在HostModeServerMode中,這意味著玩家客戶端和主機/伺服器,而在SharedMode中,這是一個相同的客戶端。

不可能讀取一個客戶端對另一個客戶端的輸入。因此,任何依賴輸入的變化都需要被保存為[Networked]狀態,以便在其他客戶端上複製。

Back To Top

GetInput()

要獲得輸入結構,請在任何對相關對象具有輸入權限的NetworkBehaviour(例如,控制玩家運動的組件)的FixedUpdateNetwork()中呼叫GetInput(out T input)。對GetInput()的呼叫提供了先前在OnInput()中填充的相同輸入結構。

GetInput()的呼叫將在以下情況下返回錯誤:

  • 客戶端沒有狀態權限或輸入權限
  • 要求的輸入類型在模擬中不存在
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() {
    // 沒有邏輯的防傻方法
  }
}

Back To Top

Runner.TryGetInputForPlayer()

通過呼叫NetworkRunner.TryGetInputForPlayer<T>(PlayerRef playerRef, out var input)可以從NetworkBehaviour外部讀取輸入。除了INetworkInput類型外,它還需要指定要檢索輸入的玩家。 N.B.: 適用於GetInput()的限制;即在有輸入權限的客戶端或伺服器/主機上可以獲得指定玩家的輸入。

var myNetworkRunner = FindObjectOfType<NetworkRunner>();

// 如果腳本只在客戶端執行,則以本地玩家為例
if(myNetworkRunner.TryGetInputForPlayer<MyInput>(myNetworkRunner.LocalPlayer, out var input)){
    // 做邏輯
}

Back To Top

授權說明

為了保証完全的模擬權限,關鍵是在填充輸入結構時只在OnInput()中收集輸入值。基於輸入要執行的邏輯應該完全在GetInput()中完成。

例如,下面的拆分將被用於發射一顆子彈:

  • OnInput():為玩家保存發射按鈕的值。
  • GetInput():檢查射擊按鈕是否被按下,如果被按下就發射子彈。

To Document Top