揮別 PUN,擁抱 Fusion — (1) 多人遊戲開發Z世代

Steven Hu
Photon Taiwan
Published in
19 min readJun 24, 2024

Photon Fusion 2, 作為 Unity 遊戲開發者的新一代網路開發套件, 標誌著多人遊戲開發進入了一個全新的時代。相較於十年之前的 PUN2 技術, 新的 Fusion 2 在架構、API 設計及性能優化上都達到了前所未有的高度。

從 PUN2 遷移到 Photon Fusion 2 是一次技術躍進, 雖然過程中會遇到挑戰, 但藉由 Fusion 2 提供的強大功能, 開發者將能夠打造更為流暢、穩定且吸引人的多人遊戲體驗。

Fusion2 的優點

Fusion2 在多個層面上超越了 PUN2,其關鍵改進包括:

  • 更高的玩家容量:Fusion2 支援更大的玩家群體,確保更穩定的多人遊戲體驗。一局遊戲可以多達 200 人以上呢 !!
  • 精確的動作複製:提供更為細緻的同步機制,使玩家動作在各客戶端之間更加一致。
  • 強化的權限管理:引入靈活且強大的權限系統,讓遊戲邏輯的控制更為細膩。
  • 預測性網路模型:透過預測與回滾技術,即使在網路條件不佳的情況下也能保持遊戲流暢度。

核心概念比較

Fusion2 在網路架構上帶來了顯著的改變:

  • 權限模型:由 PUN2 的 MasterClient (虛擬 Host)架構轉變為 Fusion2 的共享模式 Shared Mode , 以及真實的 Host/ Server Mode 架構。
  • NetworkRunner:作為管理網路會話(Session) 和遊戲邏輯的核心概念。
  • NetworkObject 和 NetworkBehaviour:取代了 PUN2 中較為難用😫的 PhotonView。

注意: Fusion 2 的 Session 相當於 PUN2 的 Room, 但不完全同等.

開發者轉換的理由

選擇轉換至 Fusion2 的理由如下:

  • 增強的效能與可擴展性:對於需要處理大量玩家或要求精確同步的遊戲而言,Fusion2 的效能與擴展性尤為突出。
  • 現代化的網路架構:能更有效地應對當今多人遊戲的挑戰。
  • 豐富的功能與控制:有助於打造更複雜且高品質的多人遊戲體驗。

準備工作

開始轉換之前,請確保以下工具和資源齊全:

  • Unity 遊戲引擎:確認版本與 Fusion2 相容。
  • Fusion 2 SDKUnity Asset StorePhoton官網都可以取得最新版本。
  • PUN2 專案源代碼:作為轉換的基礎。

有兩種途徑可以將 Fusion 2 SDK 整合至您的 Unity 專案中:

  1. 透過 Unity Package Manager 安裝:在 Unity 的 Package Manager 中搜尋 “Fusion”,並選擇安裝最新版本。
  2. 從 Photon 官網下載 SDK:訪問 Photon 的官方網站下載 Fusion 2 SDK,隨後將其導入至您的 Unity 專案中。

設置 Fusion 應用, 在正式開始開發前, 您需要在 Photon Engine Dashboard 中建立一個新的 Fusion 應用:

  1. 登錄 Photon Dashboard, 選擇 “Create New App” 選項以創建一個新的 Fusion 應用。
  2. 設定應用名稱與相關細節後, 系統將生成一個專屬的 App ID。

接下來, 您需在 Unity 環境中完成以下設定:

  1. 開啟 Fusion Hub:在 Unity 編輯器中, 從頂部菜單選擇 “Fusion > Fusion Hub” 選項, 這將打開 Fusion Hub 的設定面板。
  2. 輸入 App ID:將先前在 Photon Dashboard 中生成的 App ID 貼入 Fusion Hub 設定面板的 “App Id Fusion” 欄位中。

完成上述步驟後, 您的 Unity 專案已經成功整合了 Photon Fusion 2 SDK, 接下來即可開始利用 Fusion 2 的 Shared Mode 進行多人遊戲的開發工作。

程式 API 轉換方式

以下是最重要的類別和方法的對照:

  • PhotonNetworkSimulationBehaviour.RunnerSimulationBehaviour.Object
  • MonoBehaviourPunCallbacksSimulationBehaviourNetworkBehaviour
  • PhotonNetwork.Instantiate()Runner.Spawn()
  • PhotonViewNetworkObject
  • [PunRPC][Rpc]

範例:將 PUN2 的網路物件轉換為 Fusion2:

PUN2 網路物件:

public class PlayerController : MonoBehaviourPunCallbacks
{
private void Start()
{
if (photonView.IsMine)
{
// 本地玩家邏輯
}
}
}

Fusion2 網路物件:

public class PlayerController : NetworkBehaviour
{
public override void Spawned()
{
if (Object.HasStateAuthority)
{
// 本地玩家邏輯
}
}
}

網路物件同步

在 Fusion2 中,NetworkObjectNetworkBehaviour 為網路實體的核心組件。範例如下:

public class PlayerStats : NetworkBehaviour
{
[Networked] public int Health { get; set; }
[Networked] public float Speed { get; set; }

public override void FixedUpdateNetwork()
{
if (Object.HasStateAuthority)
{
// 更新玩家狀態
Health = Mathf.Clamp(Health, 0, 100);
Speed = Mathf.Clamp(Speed, 0, 10);
}
}
}

輸入處理

Fusion2 的輸入系統以 NetworkInput 來處理玩家輸入,有別於 PUN2 不穩定的直接處理方式:

public struct MyInputData : INetworkInput
{
public Vector2 MovementInput;
public NetworkBool JumpButton;
}

public class PlayerController : NetworkBehaviour
{
public override void FixedUpdateNetwork()
{
if (GetInput(out MyInputData input))
{
// 處理輸入
Move(input.MovementInput);
if (input.JumpButton)
Jump();
}
}
}

Player 識別與引用的轉換技巧

在多人遊戲開發中,識別玩家和引用是非常重要的。 PUN2 和 Fusion 2 皆提供了各自的方法來處理這一核心需求。

PUN2 的 Actor Number, 在 PUN2 中,每個玩家都被分配了一個唯一的 ActorNumber,該數字隨著新玩家的加入而遞增。以下是如何在 PUN2 中取得和使用 ActorNumber

// PUN2 中獲取當前玩家的 ActorNumber
int myActorNumber = PhotonNetwork.LocalPlayer.ActorNumber;

// 獲取所有玩家的 ActorNumber
Player[] allPlayers = PhotonNetwork.PlayerList;
foreach (Player player in allPlayers)
{
Debug.Log($"Player ActorNumber: {player.ActorNumber}");
}

Fusion 2 的 PlayerRef

Fusion 2 使用 PlayerRef 來識別玩家。與 PUN2 的 ActorNumber 相比,PlayerRef 是一個結構體,包含一個從 0 到 MaxPlayers - 1 的索引。
值得注意的是,在 Host Mode 架構的遊戲中, Host 是為 MaxPlayers - 1 作為 PlayerRef 值。

以下是 Fusion 2 中使用 PlayerRef 的示例:

using Fusion;
using UnityEngine;

public class PlayerManager : NetworkBehaviour
{
public override void Spawned()
{
// 取得當前玩家的 PlayerRef
PlayerRef myPlayerRef = Object.InputAuthority;
Debug.Log($"My PlayerRef: {myPlayerRef}");

// 檢查是否是本地玩家
if (Object.HasInputAuthority)
{
Debug.Log("This is the local player");
}

// 取出所有玩家的 PlayerRef
for (int i = 0; i < Runner.ActivePlayers.Count; i++)
{
PlayerRef playerRef = Runner.ActivePlayers[i];
Debug.Log($"Player {i} PlayerRef: {playerRef}");
}
}

// 示例: 根據 PlayerRef 執行操作
public void DoSomethingForPlayer(PlayerRef playerRef)
{
if (Runner.TryGetPlayerObject(playerRef, out NetworkObject playerObject))
{
// 對該玩家的 NetworkObject 執行操作
Debug.Log($"Doing something for player {playerRef}");
}
}
}

在這個 Fusion2 的例子中:

  1. 使用 Object.InputAuthority 來取出當前玩家的 PlayerRef。
  2. 可以使用 Object.HasInputAuthority 來檢查是否是本地玩家。
  3. 可以遍歷 Runner.ActivePlayers 來取出所有活躍玩家的 PlayerRef。
  4. 使用 Runner.TryGetPlayerObject 來根據 PlayerRef 取得對應的 NetworkObject。

轉換注意事項

轉換過程中,應留意以下幾點差異:

  1. 範圍差異:PUN2 的 ActorNumber 從 1 開始,而 Fusion 2 的 PlayerRef 從 0 開始。
  2. 主機處理:Fusion 2 中,主機 Host 的 PlayerRef 總是最後一個值 (MaxPlayers - 1)。
  3. 穩定性:Fusion 2 的 PlayerRef 在整個遊戲會話(Game Session)期間保持不變,而 PUN2 的 ActorNumber 可能會因玩家的離開和加入而改變。
  4. 使用方法:Fusion 2 中,通常使用 PlayerRef 來引用玩家,而非直接使用數字索引, 可以避免很多的誤寫及錯誤。

PUN2 至 Fusion 2 的轉換示例

假設在 PUN2 中,你有以下用於向特定玩家發送消息的 Code:

// PUN2
void SendMessageToPlayer(int actorNumber, string message)
{
Player targetPlayer = PhotonNetwork.CurrentRoom.GetPlayer(actorNumber);
if (targetPlayer != null)
{
// 發送消息給特定玩家
}
}

轉換至 Fusion 2 的實現如下:

// Fusion2
public void SendMessageToPlayer(PlayerRef playerRef, string message)
{
if (Runner.TryGetPlayerObject(playerRef, out NetworkObject playerObject))
{
// 獲取玩家的自定義組件
PlayerComponent playerComponent = playerObject.GetComponent<PlayerComponent>();
if (playerComponent != null)
{
// 使用 RPC 或其他方法發送消息給特定玩家
playerComponent.RPC_ReceiveMessage(message);
}
}
}

// 在玩家的自定義組件中
public class PlayerComponent : NetworkBehaviour
{
[Rpc(RpcSources.StateAuthority, RpcTargets.InputAuthority)]
public void RPC_ReceiveMessage(string message)
{
Debug.Log($"Received message: {message}");
}
}

透過這種方式,你可以將 PUN2 中基於 ActorNumber 的邏輯順利轉換為 Fusion 2 中基於 PlayerRef 的系統,同時利用 Fusion 2 更為強大和靈活的網絡架構,保持玩家識別與引用的功能。記住, 在 Fusion2 中, 通常會通過 NetworkObject 和 NetworkBehaviour 來處理玩家相關的邏輯。

網路事件和 RPC

Fusion2 的 RPC 系統更為靈活高效,提供多種 RPC 模式,比如 RpcSources.StateAuthority、RpcSources.InputAuthority 等:

PUN2:

[PunRPC]
void Fire(Vector3 direction)
{
// 發射邏輯
}

// 呼叫RPC
photonView.RPC("Fire", RpcTarget.All, transform.forward);

Fusion2:

[Rpc(RpcSources.StateAuthority, RpcTargets.All)]
public void RPC_Fire(Vector3 direction)
{
// 發射邏輯
}

// 呼叫RPC
RPC_Fire(transform.forward);

房間和配對

Fusion2 的會話 Session 管理是透過 NetworkRunner, 類似 PUN2 中的 Room

public async void JoinOrCreateSession()
{
var runner = gameObject.AddComponent<NetworkRunner>();
await runner.StartGame(new StartGameArgs()
{
GameMode = GameMode.AutoHostOrClient,
SessionName = "MyGame",
Scene = SceneManager.GetActiveScene().buildIndex,
SceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>()
});
}

離線模式/單人遊戲

在遊戲開發過程中,離線模式是不可或缺的一部分,尤其在原型設計、功能測試以及UI驗證階段。 Photon PUN2 和 Fusion 2 都提供了各自的離線模式實現,讓開發者能在無需網路連接的情況下進行遊戲測試。以下是轉換過程中的一些關鍵步驟。

在 PUN2 中,離線模式的啟動十分簡易,只需設定 PhotonNetwork.OfflineMode 屬性即可。以下是啟用離線模式的 API:

// PUN2 離線模式
public void StartOfflineMode()
{
PhotonNetwork.OfflineMode = true;
Debug.Log("Client is in offline mode.");
}

Fusion 2 以不同的方式實現離線模式。在 Fusion 2 中,我們使用 GameMode.Single 配合 NetworkRunner.StartGame() 方法來模擬離線環境。以下是一個示範片段,展示如何在 Fusion 2 中實現離線模式:

using Fusion;
using UnityEngine;

public class OfflineModeManager : MonoBehaviour
{
private NetworkRunner _runner;

public async void StartOfflineMode()
{
// 確保場景中有一個 NetworkRunner
if (_runner == null)
_runner = gameObject.AddComponent<NetworkRunner>();

// 啟動離線模式遊戲
var startGameResult = await _runner.StartGame(new StartGameArgs()
{
GameMode = GameMode.Single,
Scene = SceneManager.GetActiveScene().buildIndex,
SceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>()
});

if (startGameResult.Ok)
{
Debug.Log("Offline mode started successfully.");
}
else
{
Debug.LogError($"Failed to start offline mode: {startGameResult.ShutdownReason}");
}
}
}

在上述 Fusion2 的示例中:

  1. NetworkRunner 組件:首先,我們確保場景中存在一個 NetworkRunner 組件,該組件是 Fusion 2 中管理網絡會話的核心。
  2. StartGame 方法:接著,調用 StartGame 方法並設定 GameModeGameMode.Single,以實現離線模式。
  3. 場景設定:我們指定當前場景的索引和場景管理器,這對於 Fusion 2 的正常運作是必要的。
  4. 異步處理:由於 StartGame 方法是非同步的,我們使用 async/await 語法來確保正確處理異步操作。
  5. 結果檢查:最後,檢查啟動結果,並根據結果輸出相應的日誌信息。

測試與集成

為了在遊戲中使用這個功能,你可以在適當的位置調用 StartOfflineMode 方法。例如,在 Game MainMenu 的 Button 事件中,可以這樣做:

public class MainMenu : MonoBehaviour
{
public OfflineModeManager offlineModeManager;

public void OnOfflineModeButtonClick()
{
offlineModeManager.StartOfflineMode();
}
}

儘管離線模式/單人模式在某些情況很有用,但若是想測試網路環境的話,Fusion 2 有特別提供的網路模擬器,能幫助你模擬各種網路條件,為遊戲的全面測試提供良好的支援。透過這些工具,你將能確保遊戲在各種網路環境下均能穩定運行,從而提供給玩家最佳的遊戲體驗。

Fusion 的缺點與誤解

儘管 Photon Fusion 2 在初登場時面臨若干疑慮與挑戰,然而,這些疑慮絕非不可克服。以下將深入解析Fusion 2 所遭遇的誤解及其潛在缺點,同時揭示如何透過正確的理解與適當的應用策略,轉化疑慮為實力。

資源較少的誤解

  • 官方資源豐富:Photon官方不吝於分享,提供詳盡的文檔、教學影片以及範例專案,確保開發者能迅速上手。
  • 活躍的社區互動:Fusion社群正以驚人的速度成長,無論是在 Discord或論壇 Facebook 上,都能見到熱烈的討論與交流。相比PUN,Fusion或許在網路上的舊資源數量上有些不足,但以目前現有的資源來言說, 已非常足夠應付多數的開發需求。

學習曲線的挑戰

  • 新概念的引入:Fusion的學習曲線確實較為陡峭,這歸咎於其獨特的架構與新穎的概念。特別是Host/Server模式,要求開發者深入理解網絡權威與狀態同步的機制,然而,這種深度的學習將長期而言降低維護成本。
  • Shared模式的平衡:Shared Mode 與 PUN2 較為相近,讓開發者得以快速入門,雖在處理複雜情境時,仍需額外的技巧與思維。但一般而言,開發者只需投入數週的時間,即可瞭解 Fusion 的運用,而遠非一兩年那般遙不可及。

新技術的擔憂

  • 穩健的基礎:儘管Fusion2 作為新興技術,其背後卻倚靠著Photon累積多年的網路廿多年的開發經驗,提供堅實的底蘊。
  • 持續的進步:Photon 團隊承諾定期更新與技術支援,確保Fusion的穩定性與效能,不斷修正可能出現的問題。事實上,已有眾多大型專案採用Fusion,無不彰顯其成熟度與可靠性。

學習成本的考量

  • 長期投資的回報:Fusion雖需額外的學習時間,特別是對習慣PUN2的開發者而言,但這項投資往往能在未來發揮效用,歸功於 Fusion2所提供的強大且靈活的功能。
  • 短時間內的精通:大多數開發者在幾週內即可掌握 Fusion2 的核心概念與操作技巧,實現效率與創新的雙贏。

定價策略的優勢

  • 免費版的吸引力:Fusion2 提供免費版本,支援最高 100個 同時在線用戶(CCU),不僅是開發專用,可為小型專案或初學者提供友善的起點。
  • 靈活的定價結構:相較於PUN2,Fusion2 的定價模式更加彈性,能夠滿足特定專案的獨特需求,尤其在高級功能與性能方面,Fusion2 往往展現出更高的成本效益。

Fusion2 提供免費的 100 CCU,可直接上架用,還不趕快用起來 ?!

縱使 Fusion 在學習曲線與初期資源數量上存在挑戰,然而,這些難題隨時間演進及社區擴張,將逐漸消弭。對於追求高效能與靈活網絡解決方案的開發者而言,Fusion仍舊是個極具吸引力的選項,其潛力無可限量。

小結

🎯 在從 PUN2 遷移到 Fusion 2的過程中,採取穩健的策略非常重要。建議不妨從規模較小的專案起步,不僅可以逐步瞭解 Fusion 2 的各項特色,還能有效避免大型專案初期可能遭遇的風險與挑戰。

成功完成小型專案的轉換後,信心實力一定倍增,進而可以挑戰更複雜的專案喔 !!

😃 有開發上的疑問嗎? 可到粉絲團直接發訊息討論討論喔!
https://www.facebook.com/photoncloudtw/

Stay Tuned!

--

--