揮別 PUN,擁抱 Fusion — (1) 多人遊戲開發Z世代
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 SDK:Unity Asset Store 及 Photon官網都可以取得最新版本。
- PUN2 專案源代碼:作為轉換的基礎。
有兩種途徑可以將 Fusion 2 SDK 整合至您的 Unity 專案中:
- 透過 Unity Package Manager 安裝:在 Unity 的 Package Manager 中搜尋 “Fusion”,並選擇安裝最新版本。
- 從 Photon 官網下載 SDK:訪問 Photon 的官方網站下載 Fusion 2 SDK,隨後將其導入至您的 Unity 專案中。
設置 Fusion 應用, 在正式開始開發前, 您需要在 Photon Engine Dashboard 中建立一個新的 Fusion 應用:
- 登錄 Photon Dashboard, 選擇 “Create New App” 選項以創建一個新的 Fusion 應用。
- 設定應用名稱與相關細節後, 系統將生成一個專屬的 App ID。
接下來, 您需在 Unity 環境中完成以下設定:
- 開啟 Fusion Hub:在 Unity 編輯器中, 從頂部菜單選擇 “Fusion > Fusion Hub” 選項, 這將打開 Fusion Hub 的設定面板。
- 輸入 App ID:將先前在 Photon Dashboard 中生成的 App ID 貼入 Fusion Hub 設定面板的 “App Id Fusion” 欄位中。
完成上述步驟後, 您的 Unity 專案已經成功整合了 Photon Fusion 2 SDK, 接下來即可開始利用 Fusion 2 的 Shared Mode 進行多人遊戲的開發工作。
程式 API 轉換方式
以下是最重要的類別和方法的對照:
- PhotonNetwork →
SimulationBehaviour.Runner
和SimulationBehaviour.Object
- MonoBehaviourPunCallbacks →
SimulationBehaviour
和NetworkBehaviour
- PhotonNetwork.Instantiate() →
Runner.Spawn()
- PhotonView →
NetworkObject
- [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 中,NetworkObject
和 NetworkBehaviour
為網路實體的核心組件。範例如下:
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 的例子中:
- 使用
Object.InputAuthority
來取出當前玩家的 PlayerRef。 - 可以使用
Object.HasInputAuthority
來檢查是否是本地玩家。 - 可以遍歷
Runner.ActivePlayers
來取出所有活躍玩家的 PlayerRef。 - 使用
Runner.TryGetPlayerObject
來根據 PlayerRef 取得對應的 NetworkObject。
轉換注意事項
轉換過程中,應留意以下幾點差異:
- 範圍差異:PUN2 的
ActorNumber
從 1 開始,而 Fusion 2 的PlayerRef
從 0 開始。 - 主機處理:Fusion 2 中,主機 Host 的
PlayerRef
總是最後一個值 (MaxPlayers - 1
)。 - 穩定性:Fusion 2 的
PlayerRef
在整個遊戲會話(Game Session)期間保持不變,而 PUN2 的ActorNumber
可能會因玩家的離開和加入而改變。 - 使用方法: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 的示例中:
- NetworkRunner 組件:首先,我們確保場景中存在一個
NetworkRunner
組件,該組件是 Fusion 2 中管理網絡會話的核心。 - StartGame 方法:接著,調用
StartGame
方法並設定GameMode
為GameMode.Single
,以實現離線模式。 - 場景設定:我們指定當前場景的索引和場景管理器,這對於 Fusion 2 的正常運作是必要的。
- 異步處理:由於
StartGame
方法是非同步的,我們使用async/await
語法來確保正確處理異步操作。 - 結果檢查:最後,檢查啟動結果,並根據結果輸出相應的日誌信息。
測試與集成
為了在遊戲中使用這個功能,你可以在適當的位置調用 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/