揮別 PUN,擁抱 Fusion— (2) 多人遊戲開發者的新寵

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

對於正計劃從 PUN2 遷移到 Fusion 2 Shared Mode 的開發者們, 下列內容將詳細解析 Fusion 2 的核心特性、內建超好用的工具組件 Simple KCC、精準多功能又省頻寬的計時器 TickTimer、與 PUN2 的主要區別與性能對比, 以及轉換過程中可能遭遇的挑戰, 力求幫助各位開發者順利完成技術過渡。

從 PUN2 轉換至 Fusion 2 (Shared Mode)的方式

在說明 Fusion 2 的共享模式(Shared Mode)及其與 PUN2 的相似之處前,我們需理解共享模式的核心概念。

此模式下,Fusion 2 引入了分散式的權限管理,讓各客戶端能夠獨立控制場景中的特定遊戲物件。此一設計靈感源自 PUN2 的權限模型,其中每個網路物件皆可由不同客戶端擁有與操控,實現了類似的權限分配機制。

相似之處與遷移便利性

  • 權限管理:共享模式下的分散式權限,類似 PUN2 的權限模型,使每個網路物件可由不同客戶端擁有及控制。
  • 場景物件管理:如同 PUN2,共享模式自動將場景中的網路物件控制權交予最先進入房間的玩家,簡化了初始場景設定與物件管理。
  • 動態權限轉移:支持在程式運作時改變物件控制權,增進遊戲靈活性。
  • 網路模型相似性:共享模式在多方面模仿了 PUN2 的網路模型,降低了從 PUN2 遷移至 Fusion 2 的門檻。

從 PUN2 至 Fusion 2 共享模式的轉移

  • 房間與配對:PUN2 的房間(Room)概念直接對應至 Fusion 2 的會話(Session),而配對過程也類似,僅需調整 API 使用方式。
  • 物件實例化與銷毀:PUN2 的 PhotonNetwork.Instantiate() 轉換為 Fusion 2 的 Runner.Spawn(),物件銷毀的機制亦相似。
  • RPC: PUN2 的 RPC 系統可輕鬆轉換至 Fusion 2 的 RPC 系統,語法雖有差異,但功能相仿。

以下為從 PUN2 轉換至 Fusion 2 共享模式的幾個範例:

  • 連接遊戲
// ----
// PUN2
PhotonNetwork.ConnectUsingSettings();


// ----------------------
// Fusion 2 - Shared Mode
var runner = gameObject.AddComponent<NetworkRunner>();
await runner.StartGame(new StartGameArgs
{
GameMode = GameMode.Shared,
SessionName = "MyRoom",
Scene = SceneManager.GetActiveScene().buildIndex,
SceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>()
});
  • 實例化網路物件
// ----
// PUN2
PhotonNetwork.Instantiate("PlayerPrefab", position, rotation);


// ----------------------
// Fusion 2 - Shared Mode
runner.Spawn("PlayerPrefab", position, rotation, inputAuthority: runner.LocalPlayer);
  • 生命周期事件
// ---------------------- 
// Fusion 2 - Shared Mode

public class Enemy : NetworkBehaviour
{
public override void Spawned()
{
if (Object.HasStateAuthority)
{
// 初始化敵人的狀態
}
}
public override void Despawned(NetworkRunner runner, bool hasState)
{
// 清理資源或觸發死亡效果
}
}
  • 屬性同步
// ----
// PUN2
public class PlayerHealth : MonoBehaviourPunCallbacks, IPunObservable
{
private float health;

public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (stream.IsWriting)
{
stream.SendNext(health);
}
else
{
health = (float)stream.ReceiveNext();
}
}
}


// ----------------------
// Fusion 2 - Shared Mode
public class PlayerHealth : NetworkBehaviour
{
[Networked] public float Health { get; set; }

// 無需手動實現同步的邏輯
}

有沒有似曾相識的感覺呢?
而且, 應該有發現, Shared Mode 其實比 PUN2 還簡單好寫呢 !

對比 Host / Server 模式

儘管共享模式便於從 PUN2 遷移,Host / Server 模式卻提供了額外優勢:

  1. 中央化權限:所有遊戲狀態由主機或服務器控制,降低作弊可能性。
  2. 一致性保障:由於存在單一權威源,遊戲狀態一致性得以確保。
  3. 擴展性提升:更適合大規模多人遊戲。

然而,PUN2 的某些概念無法直接轉移到 Host / Server 模式,主要因為:

  1. 權限模型差異:Host / Server 模式中,服務器掌握絕大多數權限。
  2. 網路架構不同:需重新思考網路相關的處理邏輯。
  3. 預測與回滾機制:Host / Server 模式常需要使用到客戶端預測與服務器回滾機制。

Character Controller 超進化 - Simple KCC Addon

在多人網路遊戲開發中,角色控制器(Character Controller)扮演著關鍵的部份,負責處理玩家的移動、互動以及與遊戲世界物理環境的反應。

PUN 的 Character Controller

在 PUN 中,雖然提供了基本的 CharacterController 功能,但其效能和功能性相對有限,尤其是在網路遊戲的複雜環境下。主要問題包括:

  • 效能不足:在網絡遊戲中,角色控制需要處理延遲補償、平滑移動等問題,PUN 的基本 CharacterController 在這些方面表現不佳。
  • 自定義需求:由於功能受限,開發者往往需要自行編寫專門的控制器來滿足遊戲的特定需求,這不僅增加了開發時間,還可能引入新的問題。
Simple KCC addon (for Fusion2)

Fusion2 的 Simple KCC Addon

Fusion2 的 Simple KCC Addon 是專為網路遊戲設計的角色控制器解決方案,它克服了 PUN2 中存在的問題

使用 Simple KCC 的示例

以下是一個使用 Fusion2 中 Simple KCC 的基本示範:

using Fusion;
using Fusion.KCC;
using UnityEngine;

public class PlayerController : NetworkBehaviour
{
[Networked] public NetworkKCC KCC { get; set; }
public float MoveSpeed = 5f;
public float JumpForce = 5f;

public override void Spawned()
{
KCC = GetComponent<NetworkKCC>();
}

public override void FixedUpdateNetwork()
{
if (GetInput(out NetworkInputData input))
{
// 移動
Vector3 moveDirection = new Vector3(input.MoveInput.x, 0, input.MoveInput.y).normalized;
KCC.SetVelocity(moveDirection * MoveSpeed);

// 跳躍
if (input.JumpPressed && KCC.IsGrounded)
{
KCC.Jump(Vector3.up * JumpForce);
}
}
}
}

public struct NetworkInputData : INetworkInput
{
public Vector2 MoveInput;
public NetworkBool JumpPressed;
}

Simple KCC 相較於 PUN 的 CharacterController,好用之處:

  1. 最佳化性能:Simple KCC 高度最佳化,能更好地處理網路延遲和同步問題,確保角色動作在網路環境中更為流暢。
  2. 易用性:API 設計直觀,易於集成到現有項目中,降低學習曲線。
  3. 功能豐富:開箱即用的功能,如碰撞檢測、斜坡移動、跳躍等,減少了開發者自行實現的需要。
  4. 靈活性:雖然是預製解決方案,但 Simple KCC 保留了足夠的自定義空間,以滿足特定遊戲需求。
  5. 網路同步:與 Fusion2 的網路架構緊密集成,提供更一致和可靠的網路同步體驗。

TickTimer 網路遊戲時間管理的精妙利器

在 Fusion 2 中的內建組件,TickTimer 提供了一個既簡潔又高效的方案,用以處理網路遊戲中與時間相關的操作。與 PUN2 孤獨的 ServerTime 相較,TickTimer 具備下列優點與應用:

  • 簡單易用
    TickTimer 的 API 界面直觀,如 CreateFromSecondsCreateFromTicks 方法,讓計時器的建立變得輕而易舉。例如:
[Networked] TickTimer cooldownTimer { get; set; }

void StartCooldown()
{
cooldownTimer = TickTimer.CreateFromSeconds(Runner, 5); // 5秒冷卻時間
}
  • 網路效率
    使用目標 tick 代替浮點數進行計時,TickTimer 在網路同步時節省了更多帶寬資源。
  • 精確性
    基於 tick 的計時方式,使得 TickTimer 在網路環境中表現得更加精確,不受網絡延遲影響。
  • 多功能性
    TickTimer 適用于多種時間相關的操作,包括倒計時、延遲執行、週期性事件等。

示範應用

  • 技能冷卻
[Networked] TickTimer skillCooldown { get; set; }

public void UseSkill()
{
if (skillCooldown.ExpiredOrNotRunning(Runner))
{
// 使用技能
skillCooldown = TickTimer.CreateFromSeconds(Runner, 10); // 10秒冷卻
}
else
{
float remainingTime = skillCooldown.RemainingTime(Runner);
Debug.Log($"技能冷卻中,還需 {remainingTime:F1} 秒");
}
}
  • 延遲執行
[Networked] TickTimer delayedAction { get; set; }

public void ScheduleAction()
{
delayedAction = TickTimer.CreateFromSeconds(Runner, 3); // 3秒後執行
}

public override void FixedUpdateNetwork()
{
if (delayedAction.Expired(Runner))
{
PerformAction();
delayedAction = TickTimer.None; // 重置計時器
}
}
  • 遊戲回合計時
[Networked] TickTimer roundTimer { get; set; }

public void StartRound()
{
roundTimer = TickTimer.CreateFromSeconds(Runner, 60); // 60秒回合時間
}

public override void FixedUpdateNetwork()
{
if (roundTimer.Expired(Runner))
{
EndRound();
}
else
{
float remainingTime = roundTimer.RemainingTime(Runner);
UpdateTimerDisplay(remainingTime);
}
}
  • 週期性事件
[Networked] TickTimer periodicEvent { get; set; }

public override void FixedUpdateNetwork()
{
if (periodicEvent.ExpiredOrNotRunning(Runner))
{
TriggerEvent();
periodicEvent = TickTimer.CreateFromSeconds(Runner, 5); // 每5秒觸發一次
}
}
  • 自定義功能,可擴展 TickTimer 的功能,例如 “進度” 標準化值
public struct CustomTickTimer : INetworkStruct
{
// ... 其他Code ...

public float NormalizedValue(NetworkRunner runner)
{
if (runner == null || runner.IsRunning == false || IsRunning == false)
return 0;

if (Expired(runner))
return 1;

return ElapsedTicks(runner) / (_target - (float)_initialTick);
}
}

// 使用
[Networked] CustomTickTimer progressTimer { get; set; }

public override void FixedUpdateNetwork()
{
float progress = progressTimer.NormalizedValue(Runner);
UpdateProgressBar(progress);
}

Fusion 2 內建的 TickTimer 為網路遊戲開發者提供了一個強大而靈活的工具,用以處理各種時間相關操作。相比於 PUN2 中需自行實現的複雜時間同步邏輯,TickTimer 的引入大幅簡化了此過程,同時提升了性能與網路效率。其設計讓開發者能夠輕鬆應對從基本”冷卻計時”到複雜”遊戲機制”時間管理需求,有效提高開發效率,降低因時間同步問題引起的 bug,最終實現更可靠、精確的網路遊戲時間管理。

Fusion 2 的最佳實踐與性能優化

  • 預測與插值:網路延遲平滑化。
  • Fusion 內建組件:如 NetworkTransform,簡化網路任務。
  • 定期測試:利用網路模擬器評估不同網路條件下遊戲的表現。

建議的實戰練習

以一個簡單的多人射擊遊戲來說,從 PUN2 遷移到 Fusion2 最重要的地方:

  1. PlayerMovement:繼承自 NetworkBehaviour
  2. 移動與射擊功能:使用 Networked 屬性同步玩家狀態。
  3. NetworkRunner:管理遊戲會話 Game Session。
  4. FixedUpdateNetwork:更新 Game Logic。
  5. 配對系統:支援玩家建立或加入遊戲。
  6. Fusion2 RPC:處理玩家名稱、顏色、文字等不須即時處理的部份。

透過此練習,將可以深入了解 Fusion2 的核心概念,並實際應用所學知識。

結語

Fusion 2 的 Shared Mode 共享模式,為開發者提供了一條從 PUN2 平穩遷移的途徑。其保留了許多相似概念與工作流程,同時融入 Fusion 2 的先進特性。

🎯 對於追求類似 PUN2 開發體驗的專案而言,Fusion 2 中的 Shared Mode 共享模式是最理想的選擇。

然而,對於需要更嚴謹控制與更高擴展性的專案,則可能需考慮遷移到 Host 或是 Server 模式,儘管這可能涉及較大的重構工作,但也絕對值得花些時間研究一番。

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

Stay Tuned!

--

--