Network Object
簡介
NetworkObject
為GameObject
分配了一個網路身份,所以它可以在網路上共享。任何被所有玩家看到並且是場景的動態部分的GameObject
都應該帶有一個NetworkObject
腳本。
Fusion提供了兩個NetworkObject
基礎類別,用於派生自定義網路行為:
SimulationBehaviour
仿真行為用於仿真相關的行為;以及,NetworkBehaviour
用於保持或追蹤[Networked]
狀態的行為。
網路對象
在一個GameObject
的根節點上有一個單一的NetworkObject
腳本,足以控制整個層次結構。在執行時,NetworkObject
會找到其層次結構中的所有SimulationBehaviour
和NetworkBehaviour
。
如果需要,NetworkObject
可以被嵌套。在這種情況下,NetworkObject
不會搜索到嵌套的子對象,而是讓子NetworkObject
追蹤它下面的所有SimulationBehaviour
和NetworkBehaviour
。一個典型的用例是一個由底盤和炮塔組成的雙人控制的坦克,駕駛員控制底盤的方向(移動),炮手控制炮塔(射擊)。
預制件/對象表
為了使Prefab
參數對Fusion有意義(並確保所有客戶都同意到底是哪個預制件),預制件本身必須在Fusion註冊。在正常情況下,Fusion工具集會自動檢測這些預制件並註冊,但您也可以通過選擇NetworkProjectConfig
並在Unity Inspector中按Rebuild Object Table
來手動觸發這種檢測。
事件化/生成
NetworkObject
預制件的事件化需要兩個步驟:
- 在編輯時:確保客戶必須有相同的
NetworkProjectConfig
資產,這與NetworkObject
預制件的身份一致。這是通過在NetworkProjectConfig
資產中執行Rebuild Object Table
來執行的;以及, - 在執行時:在
NetworkRunner
上呼叫Spawn()
,將NetworkObject
預制件作為參數。
IMPORTANT: 呼叫通用的UnityInstantiate()
方法只會創建一個有問題的本地事件!
銷毀/解體
要銷毀一個NetworkObject
,請呼叫Runner.Despawn()
並將該對象作為參數傳遞。這將銷毀該對象以及所有的嵌套對象;要想只銷毀頂層對象,需要先手動去掉嵌套對象的父級。
授權
一個NetworkObject
有兩種類型的權限(又稱所有權):
- 輸入權限;以及,
- 狀態權限。
輸入與狀態
輸入權限允許收集和發送該對象的用戶輸入。它也將使客戶端能夠在FixedUpdateNetwork()
中獲得相同的輸入,以模擬和本地預測該對象的未來狀態。State Authority能夠根據輸入來讀取和模擬狀態,從而允許預測和真實狀態被同一代碼所突變。
換句話說,State Authority給出了對對象狀態的最終控制權,並直接決定了什麼會進入發送到所有其他客戶端的同步的delta快照。最終,擁有狀態權限的客戶端所設置的狀態將取代擁有輸入權限的客戶端所預測的狀態。
指派權限
遊戲代碼可以通過HasInputAuthority
和HasStateAuthority
屬性來檢查客戶端對一個給定的NetworkObject
的權限類型。
當一個NetworkObject
被Runner.Spawn()
生成時,呼叫的客戶端可以將輸入權限分配給自己或任何其他客戶端。
- 如果網路模式被設置為客戶端權限,客戶端也可以對該對象承擔狀態權限。
- 如果網路模式被設置為專用伺服器或客戶主機,狀態權限將總是被分配在那裡。
模擬行為
所有的SimulationBehaviour
都可以通過Object
屬性訪問它們相關的NetworkObject
。
Simulation是Fusion更新網路狀態的方式。任何屬於或影響模擬狀態的行為都必須派生自SimulationBehaviour
而不是MonoBehaviour
。
例如,坦克樣本中的LevelManager
會產生動力裝置。雖然動力裝置是NetworkObject
,但LevelManager
只需要影響和了解模擬狀態來執行其行為。因此,繼承自SimulationBehaviour
是正確的方法。
public class LevelManager : SimulationBehaviour
{
[SerializeField] private float _powerupDelay;
[SerializeField] private NetworkObject _powerupPrefab;
private TickTimer _powerupTimer;
public override void FixedUpdateNetwork()
{
// 所有關卡管理邏輯都發生在服務器端,所以如果我們不是伺服器,請您放心。
if (!Object.HasStateAuthority) return;
// LevelManager 的唯一職責是在計時器到期時生成電源
if (_powerupTimer.ExpiredOrNotRunning(Runner)) {
// 重置計時器,並檢查是否有空點來生成新通電
_powerupTimer = TickTimer.CreateFromSeconds(Runner,_powerupDelay);
SpawnPoint spawnPoint = GetSpawnPoint(SpawnPoint.Type.Powerup);
if (spawnPoint != null) {
Debug.Log("Spawning Powerup");
NetworkObject powerupobj = Runner.Spawn(_powerupPrefab, spawnPoint.transform.position, spawnPoint.transform.rotation, PlayerRef.None);
powerupobj.GetComponent<Powerup>().Init();
} else {
Debug.Log("Not Spawning Powerup - no free spot");
}
}
}
}
如果該行為需要訪問[Networked]
屬性,它必須派生自NetworkBehaviour
。
網路行為
一個NetworkBehaviour
需要在同一個節點或一個父節點上有一個NetworkObject
。
NetworkBehaviour
是一個可以攜帶同步狀態的SimulationBehaviour
;這就是為什麼它必須要有一個相關的NetworkObject
的原因。
網路狀態
在內部,Fusion保留了一個單一的內存緩沖區,代表了當前本地tick的整個網路狀態。該狀態通過標記[Networked]
的屬性暴露給遊戲代碼。
要定義和序列化一個網路狀態的值,在一個屬性前面添加[Networked]
屬性。Fusion將自動用它自己的高性能數據緩沖器和delta壓縮來處理標記的屬性。 屬性設置器和獲取器在編譯時被替換為自定義代碼,以消除內存分配消耗並提供最佳性能。
public class PlayerBehaviour : NetworkedBehaviour {
[Networked] public float Health { get; set; }
}
在編譯時,Fusion將用訪問實際網路狀態數據的代碼取代空的get/set stubs。直接訪問消除了內存分配的消耗,提供了最佳的性能。不要手動執行它們。
對緩沖區的直接訪問也意味著每當有變化發生時,屬性會立即反映出該變化。
要編寫影響網路狀態的邏輯,請覆蓋並執行FixedUpdateNetwork()
。
public override void FixedUpdateNetwork() {
Health += Runner.DeltaTime * HealthRegen;
}
限制條件
[Networked]
屬性必須遵守這些約束條件:
- 該屬性必須有一個空的get/set對;
- 它只能包含以下類型:
- 原始類型(完整列表見下文)
- 統一類型(完整列表見下文)
- 執行
INetworkStruct
並在頂層定義的結構(即不能嵌套在類中) - 指向網路結構的指針
- 使用
[Capacity]
屬性設置最大長度的字符串(默認為16) 網路對象
NetworkBehaviour
子類NetworkArray<T>
,最大長度使用[Capacity]
屬性設置(默認為1)
- 對於布林值,使用
NetworkBool
而不是bool
-C#在不同的平台上對布林值不執行一致的大小,所以使用NetworkBool
來正確地將其序列化為一個位元。
可用的原始類型有:
- byte, sbyte
- Int16, Int32, Int64
- UInt16, UInt32, UInt64
- float
- double
可用的Unity類型是:
- Vector2, Vector3, Vector4
- Quaternion
- Matrix4x4
- BoundingSphere
- Bounds
- Rect
- Color
- Vector2Int, Vector3Int
- BoundsInt
- RectInt
- Color32
屬性
通過使用[Accuracy]
屬性,可以控制單個數字類型屬性的準確性。
public class PlayerBehaviour : NetworkedBehaviour {
[Networked, Accuracy(0.001)]
public float Health { get; set; }
}
[Capacity]
用於定義NetworkArray
和字符串的最大長度;設置它們的最大長度是Fusion能夠對這些類型進行聯網的必要條件。
[Capacity(14)] string MyString { get; set; }
[Capacity(8)] NetworkArray<byte> MyArray { get; }
對OnChanged的反應
當網路屬性被Fusion改變時,遊戲代碼可以被觸發。要寫反應式代碼,使用[Networked]
屬性中的(OnChanged)
參數。
public class Powerup : NetworkBehaviour{
[Networked(OnChanged = nameof(OnTypeChanged))] public Type type { get; set; }
// 必須是 public static void
public static void OnTypeChanged(Changed<Powerup> changed){
changed.Behaviour.OnTypeChanged();
}
private void OnTypeChanged(){
// 對“類型“的值變化做出反應的一些邏輯
}
}
除了指定呼叫返回名稱外,還可以控制呼叫返回在哪些機器上執行:
OnChangedLocal
(默認為false):設置為true,也可以在改變屬性的機器上呼叫事件鉤。OnChangedRemote
(默認為true):設置為false,只在改變屬性的機器上呼叫事件鉤。
覆寫方法
一個NetworkObject
有許多額外的生命周期方法,所有這些方法都可以在執行SimulationBehaviour
和NetworkBehaviour
時被覆寫。
功能 | 說明 |
---|---|
FixedUpdateNetwork() | Fusion的固定時間步長呼叫返回。用於遊戲邏輯核心。 |
Spawned() | 生成後呼叫返回。 |
Despawned(bool hasState) | 在網絡對象消失之前呼叫。
--- bool hasState:如果行為的狀態仍然可以訪問。 |
Render() | 後模擬幀渲染呼叫返回。在所有模擬完成後執行。在 Fusion處理物理時代替Unity的更新。 |
FixedUpdateNetwork
FixedUpdateNetwork()
- 簡稱FUN()
- 在模擬新的狀態時,從一個tick到下一個tick被呼叫。FixedUpdateNetwork()
以固定的時間間隔被呼叫;這些間隔的時間步長在NetworkProjectConfig
資產中的Simulation > Tick Rate
中定義。時間步長可以通過Runner.DeltaTime
屬性從任何SimulationBehaviour
和NetworkBehaviour
中獲取。
FixedUpdateNetwork()
可以為同一個狀態轉換多次呼叫,以根據從伺服器收到的更新(基本事實)重新模擬當前預測的狀態。對於[Networked]
屬性來說,重新模擬是透明的,因為Fusion會在呼叫FixedUpdateNetwork()
之前重置網路狀態,以達到重新模擬的目的。
IMPORTANT: 常規的本地非網路狀態變數(例如類別成員)將不會被重置,只是將其視為一個額外的前向狀態進展。