Pre-built Components
簡介
Fusion提供了各種預制的NetworkBehaviour
,使您能夠快速啟動和執行。
NetworkRunner
NetworkRunner
是Fusion的核心。NetworkRunner管理與網路的連接,並控制模擬-從收集輸入到合並快照,再到呼叫SimulationBehaviour
的呼叫返回和生命周期方法。 在每個客戶端和伺服器的場景中,只有一個NetworkRunner
。
為了執行這些任務,NetworkRunner
將追蹤所有的NetworkObject
、NetworkBehaviour
和SimulationBehaviour
。
屬性
所有的NetworkBehaviour
可以通過Runner
屬性訪問當前的NetworkRunner
,Runner本身也暴露了一些重要的系統屬性。
IsServer
:如果這個執行器代表一個授權的伺服器,則為真。Stage
:模擬的當前階段。可以是Forward
,如果模擬目前正在預測下一個狀態,或者是Resimulate
,如果模擬正在重新模擬一個舊的狀態以協調伺服器的更新。注意,Resimulate
永遠不會發生在伺服器上,或者在客戶-授權模式下,模擬總是Forward
。
呼叫返回
NetworkRunner
允許應用程式通過執行INetworkRunnerCallbacks
接口,並通過呼叫NetworkRunner.AddCallbacks()
將腳本與runner註冊在一起,從而鉤住重要的網路相關事件。
Note: Fusion也會自動註冊任何執行INetworkRunnerCallbacks
接口的SimulationBehaviour
,與NetworkRunner
位於同一對象上,成為呼叫返回目標。
最重要的呼叫返回是OnServerConnected
和OnPlayerJoined
,這是遊戲生成初始玩家對象的地方:
C#
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
_players[player] = runner.Spawn(_playerPrefab, Vector3.zero, Quaternion.identity, player);
}
關於所有可用的呼叫返回的詳細解釋,請查閱API參考。
遊戲模式
GameMode
定義了本地同伴的行為方式。GameMode
作為參數傳遞給NetworkRunner.StartGame()
方法。
client
:作為客戶端執行,創建一個本地玩家,連接到客戶端主機( Cloud共享)或專用(基於伺服器)。host
:作為伺服器執行,創建一個本地玩家(伺服器+客戶端)。server
:作為專用伺服器執行,沒有玩家shared
:作為客戶端執行,創建一個本地玩家,連接到Fusion插件伺服器。single
:作為"伺服器"執行,創建一個本地玩家,不允許連接 (單人遊戲)
NetworkTransform
NetworkTransform
通過在對象的實際本地Transform
和其模擬狀態之間進行插值來管理一個簡單的運動學對象。
NetworkRigidbody
對於剛體,NetworkRigidbody
比簡單地在舊狀態之間插值做得更好,它利用物理對象的可預測性來推斷更準確的局部位置。
NetworkCharacterController
最不穩定的對象是那些由玩家直接控制的對象,通常被稱為角色控制器,Fusion也有一個NetworkCharacterController
用於這種特殊的使用情況。
預建的NetworkCharacterController
允許快速建立原型,因為它包括了通常需要的行為。然而,沒有一個放之四海而皆準的角色控制器,因為角色控制器的執行是非常具體的遊戲。
因此,建議通讀NetworkCharacterController
使用的兩個核心方法Move()
和ComputeRawMovement()
的源代碼,以便為制作遊戲創建一個替換的自定義角色控制器而汲取靈感。
ComputeRawSteer()
ComputeRawSteer()
是一個內部方法,根據角色當前執行的運動類型進行大部分的運動計算。在預建的NetworkCharacterController
中,Move()
通過傳遞對要填寫的結構的引用,從ComputeRawMovement()
請求movementPack
值。
C#
void ComputeRawSteer(ref Movement movementPack, float dt) {
Grounded = movementPack.Grounded;
float minYSpeed = -100;
float maxYSpeed = 100;
var current = Velocity;
switch (movementPack.Type) {
case MovementType.FreeFall:
current.y -= Config._gravityStrength * dt;
if (!Config.AirControl || movementPack.Tangent == default(Vector3)) {
current.x = Mathf.Lerp(current.x, 0, dt * Config.Braking);
current.z = Mathf.Lerp(current.z, 0, dt * Config.Braking);
} else {
current += movementPack.Tangent * Config.Acceleration * dt;
}
break;
case MovementType.Horizontal:
// 應用切線速度
current += movementPack.Tangent * Config.Acceleration * dt;
var tangentSpeed = Vector3.Dot(current, movementPack.Tangent);
// 將當前速度轉為切線速度
var tangentVel = tangentSpeed * movementPack.Tangent;
var lerp = Config.Braking * dt;
current.x = Mathf.Lerp(current.x, tangentVel.x, lerp);
current.z = Mathf.Lerp(current.z, tangentVel.z, lerp);
// 如果角色不是在這個確切的幀中跳躍,我們只計算垂直速度,
// 否則,它將以較低的衝力跳躍
if (Jumped == false) {
current.y = Mathf.Lerp(current.y, tangentVel.y, lerp);
}
// 用最大速度貼齊切線速度
if (tangentSpeed > MaxSpeed) {
current -= movementPack.Tangent * (tangentSpeed - MaxSpeed);
}
break;
case MovementType.SlopeFall:
current += movementPack.SlopeTangent * Config.Acceleration * dt;
minYSpeed = -Config.MaxSlopeSpeed;
break;
case MovementType.None:
var lerpFactor = dt * Config.Braking;
if (current.x != 0) {
current.x = Mathf.Lerp(current.x, default, lerpFactor);
if (Mathf.Abs(current.x) < float.Epsilon) {
current.x = 0;
}
}
if (current.z != 0) {
current.z = Mathf.Lerp(current.z, default, lerpFactor);
if (Mathf.Abs(current.z) < float.Epsilon) {
current.z = 0;
}
}
// 如果角色在這一幀中沒有跳,我們就把垂直速度拉回到0,
// 否則,它將以較低的衝力跳躍
if (current.y != 0 && Jumped == false) {
current.y = Mathf.Lerp(current.y, default, lerpFactor);
if (Mathf.Abs(current.y) < float.Epsilon) {
current.y = 0;
}
}
minYSpeed = 0;
break;
}
// 水平方向在其他地方被貼齊
if (movementPack.Type != MovementType.Horizontal) {
Vector2 h = new Vector2(current.x, current.z);
if (h.sqrMagnitude > MaxSpeed * MaxSpeed) {
h = h.normalized * MaxSpeed;
}
current.x = h.x;
current.y = Mathf.Clamp(current.y, minYSpeed, maxYSpeed);
current.z = h.y;
}
Velocity = current;
// 設置跳躍狀態
Jumped = false;
}
Move()
這是一個完整的Move()
函數的基本執行。執行一個運動查詢,使用其結果來計算新的速度,然後將穿透力修正+速度整合到變換位置。它不改變旋轉。
direction
:預期的運動方向,以運動查詢+加速度為準。callback
:可選的自定義呼叫返回對象。layerMask
:可選遮色片圖層。如果不通過,將使用配置中的默認值。
C#
public void Move(Vector3 direction, ICallbacks callback = null, LayerMask? layerMask = null) {
var dt = Runner.DeltaTime;
var movementPack = ComputeRawMovement(direction, callback, layerMask);
ComputeRawSteer(ref movementPack, dt);
var movement = Velocity * dt;
if (movementPack.Penetration > float.Epsilon) {
if (movementPack.Penetration > Config.AllowedPenetration) {
movement += movementPack.Correction;
} else {
movement += movementPack.Correction * Config.PenetrationCorrection;
}
}
_transform.position += movement;
#if DEBUG
LastMovement = movementPack;
#endif
}
NetworkMechanimAnimator
NetworkMechanimAnimator
同步了相關的Unity mechanimAnimator
所持有的參數值。
N.B.: 它並_NOT_同步動畫本身!
NetworkObjectPool
為了減少執行時的內存分配,更重要的是減少垃圾回收,每個Unity遊戲都需要使用某種形式的對象池,這樣舊的遊戲對象就可以被重新使用而不是銷毀。網路對象也不例外。
因為每個遊戲都是不同的,有不同的需求,Fusion(像Unity)沒有任何內置的對象池,而是提供了兩個鉤子-一個用於從池中獲取一個對象,另一個用於將使用過的對象返回到池中。
NetworkSceneManager
NetworkEvents
NetworkEvents
可以用來代替NetworkRunner.AddCallbacks()
在Unity檢查器中直接連接事件處理程序。只需將組件添加到NetworkObject
中,並使用拖放來註冊各個事件處理程序。
沒有必要使用NetworkEvents
,大多數遊戲也不需要它,它是為了方便在少數情況下,網路事件需要從Unity場景中配置。