This document is about: FUSION 1
SWITCH TO

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

Pre-built Components

簡介

Fusion提供了各種預制的NetworkBehaviour,使您能夠快速啟動和執行。

NetworkRunner

NetworkRunner是Fusion的核心。NetworkRunner管理與網路的連接,並控制模擬-從收集輸入到合並快照,再到呼叫SimulationBehaviour的呼叫返回和生命周期方法。 在每個客戶端和伺服器的場景中,只有一個NetworkRunner

為了執行這些任務,NetworkRunner將追蹤所有的NetworkObjectNetworkBehaviourSimulationBehaviour

屬性

所有的NetworkBehaviour可以通過Runner屬性訪問當前的NetworkRunner,Runner本身也暴露了一些重要的系統屬性。

  • IsServer:如果這個執行器代表一個授權的伺服器,則為真。
  • Stage:模擬的當前階段。可以是Forward,如果模擬目前正在預測下一個狀態,或者是Resimulate,如果模擬正在重新模擬一個舊的狀態以協調伺服器的更新。注意,Resimulate永遠不會發生在伺服器上,或者在客戶-授權模式下,模擬總是Forward

呼叫返回

NetworkRunner允許應用程式通過執行INetworkRunnerCallbacks接口,並通過呼叫NetworkRunner.AddCallbacks()將腳本與runner註冊在一起,從而鉤住重要的網路相關事件。

Note: Fusion也會自動註冊任何執行INetworkRunnerCallbacks接口的SimulationBehaviour,與NetworkRunner位於同一對象上,成為呼叫返回目標。

最重要的呼叫返回是OnServerConnectedOnPlayerJoined,這是遊戲生成初始玩家對象的地方:

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場景中配置。

Back to top