Fusion Impostor 遊戲開發導讀
瞭解 Fusion SDK 開發規則與架構引導
此篇文章中,我們將介紹一個以 Fusion SDK 制作的超熱門社交推理類型遊戲: Fusion Impostor。此範例使用 Host Mode 拓撲,展示如何開發一個一局可容納 10名玩家的遊戲,並且結合使用 Photon Voice SDK 來進行通話。
此範例具有豐富的功能和特點,其中包括語音通信、網路化的遊戲狀態機、可定制的遊戲設置等。通常, 愈是簡單的房間設置和讓玩家加入的方法,愈是能讓遊戲具有更高的可玩性,所以此範例也是採用簡單的設計方式,開發者也可以直接深入網路設計的核心。在遊戲開發中,使用網路狀態機來同步和控制遊戲邏輯是很重要的,所以此範例也包含模組化互動系統的多種 mini game的船員任務,希望開發者們瞭解用法後,更能讓自己的專案有更多的發想空間。
主要亮點
- 遊戲大廳和遊戲中的語音通信
- 完全網路化的遊戲狀態機和系統
涵蓋遊戲前準備、遊戲進行、會議和遊戲結果 - 共享(同步)的互動點,如任務站和船員的屍體
- 可定制的遊戲設置(冒名者 Imposter 數量、移動速度、玩家碰撞等)
- 世界中的物體狀態同步,如:門的狀態
- 基於模組化互動系統的多種船員任務 (Task, Mini Game)
- 使用Photon Voice 處理各種語音通信類型
- 使用代號(Room Code)設置房間,可讓玩家自行輸入後直接進入房間
- 區域設置、暱稱和麥克風選擇
本篇文章是為進階網路遊戲開發者提供一個簡潔的的引導。希望各位能在學習這個範例的過程中,也能感受到遊戲開發的樂趣。歡迎繼續踏上此次的學習之旅,探索網路遊戲開發的奧秘!😄
所以, 趕快下載來使用吧 !!
範例下載
主要功能簡介
玩家
玩家的行為, 用了三個不同的組件, 各有其用途.
- PlayerObject
存儲與該物件關聯的 PlayerRef的引用,包括房間索引、暱稱和選定顏色 - PlayerMovement
負責玩家移動和輸入,以及遊戲所需的數據和方法 - PlayerData
主要處理材質、設置動畫屬性和實例化暱稱UI
PlayerRegistry
存儲每個玩家在房間中的引用,並提供對玩家進行選擇和操作的實用方法
遊戲狀態 GameState
遊戲邏輯的流程和行為由 GameState NetworkBehaviour 控制
加入遊戲
用戶可以使用房間代號加入或主持(Host)一個房間
遊戲前
進入房間後, 遊戲正式開玩之前的階段,玩家可以選擇顏色、設定麥克風,主持人可以自定義遊戲設置並負責開始遊戲
輸入處理
網路化輸入是在 PlayerInputBehaviour.cs 進行檢查,並在此執行輸入篩選,以進行輸入身份的分類判別。之後, 會在 PlayerMovement.cs 進行服務端(Host Input)檢查,然後執行輸入操作.
鍵盤和滑鼠
W A S D 鍵行走, E 鍵互動, Enter鍵開始遊戲 (僅在遊戲前階段作為主持人), 左鍵行走,點按UI中的按鈕可進行互動
可互動物件
遊戲中有多個可互動的物件,
例如顏色選擇機、設置機、緊急按鈕、任務和屍體😅
任務
地圖上分布著 14個任務站,包括 5個獨特的任務小遊戲,
如調整恆溫器、滑動滑塊、匹配圖案、按數字順序和下載文件
語音
Fusion Impostor 遊戲範例使用 Photon Voice SDK v2 提供語音通信功能,
包括 FusionVoiceNetwork 和 VoiceNetworkObject 兩隻程式的使用
(注意) 遊戲範例使用 Unity 2021.3 版本開發
以上這段, 即是 Fusion Impostor遊戲範例的主題功能簡介.
接下來我們來為幾個重點大項目做些解說.
資料夾結構
主要的腳本資料夾 /Scripts 下有一個名為 Networking 的子資料夾,包含示例中的主要網路實現以及網路化狀態機。其他子資料夾如 Player 和Managers,分別包含遊戲行為和管理的邏輯。
加入遊戲
用戶可以使用房間代碼加入或創建一個房間。
如果用戶選擇創建房間,則輸入房間代號是可用的。
一旦進入房間,屏幕底部將顯示用於加入的代號。
房間代號可以由:runner.SessionInfo.Name 來取得。
NetworkStartBridge 充當 NetworkDebugStart 的中間介面。
如果沒有指定特定的房間代號,StartHost() 將從 RoomCode 中獲取一個隨機的 4個字元的字串。我們來看看 RoomCode 是怎麼做事的:
RoomCode.cs
定義了一個名為 RoomCode 的靜態類別,用於生成遊戲房間代號。
這個類別有一個靜態方法 Create(),該方法接受一個可選參數 length(代號的長度),預設值為 4。
- Create() 方法內部定義了一個字元組 chars,該字元組包含了用於生成房間代號的字元。
特別的地方是,字集中沒有容易混淆的字符(如 I、O、1、0 等)。 - 方法內部還定義了一個空字串 str,用於存儲生成的房間代號。
- 接下來,使用一個 for 迴圈來生成房間代號。
迴圈從 0 開始,執行次數等於 length(代碼的長度)。 - 在每次迴圈中,從 chars 字組中隨機選取一個字元,並將其添加到 str 字串中。Random.Range(0, chars.Length) 函數用於生成一個介於 0(包含)和 chars.Length(不包含)之間的隨機整數。
- 最後,當迴圈結束時,返回生成的房間代號(str 字串)。
這個簡單的類別可用於在遊戲中生成唯一的、不容易混淆的房間代號,以便玩家可以簡單地加入到其所指定的遊戲房間中。
遊戲狀態
遊戲邏輯的流程和行為由 GameState NetworkBehaviour控制。
GameState為遊戲階段定義了一個列舉,網路化的 StateMachine 將其用作狀態。
StateMachine<T>定義了一個包含 3個階段的 StateHooks類:onEnter、onExit 和 onUpdate。
在使用 StateMachine 類時,每個列舉狀態都可能具有 StateHooks,用於定義進入、退出和更新狀態時發生的操作。
GameState.cs
這段程式碼 主要目的是在多人遊戲中管理遊戲的不同狀態。這個 GameState
類別繼承自 NetworkBehaviour
,負責控制遊戲中的狀態轉換。
程式碼首先定義了一個 EGameState
列舉,表示遊戲的不同狀態,如遊戲前(Pregame)、遊戲中(Play)、會議中(Meeting)、投票結果(VoteResults)、船員勝利(CrewWin)和假冒者(殺手)勝利(ImpostorWin)等。
接著,類別中定義了一些變數,包括當前狀態、上一個狀態、延遲計時器以及延遲狀態。
StateMachine
是一個用於管理狀態轉換的實例。在 Spawned()
函數中,設置了不同狀態之間轉換時的行為。例如,在進入 Pregame 狀態時,會初始化各個玩家的位置和狀態;在進入 Play 狀態時,會將玩家分配到隨機的初始位置,並為假冒者(殺手)分配任務等。
FixedUpdateNetwork()
函數則用於在每個網路更新時檢查是否應該更新狀態。如果是伺服器,並且延遲計時器到期,則會更新狀態。如果是客戶端,則只需根據收到的狀態更新來更新本地狀態。
Server_SetState()
函數用於在伺服器上設置新狀態,而 Server_DelaySetState()
函數則用於在指定延遲後設置新狀態。
這樣可以確保在遊戲的關鍵時刻,例如投票結果公佈後,給予玩家足夠的時間在進入下一個狀態之前做出反應。
整體來說,這段程式碼用於管理遊戲的狀態轉換,並根據狀態的改變來更新遊戲世界,使多人遊戲保持同步。
處理輸入
在 PlayerInputBehaviour.cs 腳本中,網路輸入會不斷的查詢及傳送。在這裡還會進行輸入阻擋並篩選,以排除非本機玩家的輸入, 確保只會使用到本機玩家的輸入。此外,在執行輸入之前,PlayerMovement.cs 會進行服務器端檢查。本地輸入輪詢(polling) 使用 FixedInput.cs 類完成。
PlayerInputBehaviour.cs
定義了 PlayerInputBehaviour 的類別,該類別繼承自 Fusion.Behaviour 並實現了 INetworkRunnerCallbacks 界面。此類別用於處理玩家的輸入並將其同步到遊戲的網路層。
- OnInput(NetworkRunner runner, NetworkInput input) 方法在玩家有輸入時被呼叫。它檢查玩家當前的狀態和遊戲狀態,並根據按鍵狀態設置 frameworkInput 的按鈕值。最後,再將按鈕值存儲在 input 中。
- OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input) 是一個空方法 (empty method),用於實現 INetworkRunnerCallbacks 界面。
- 接下來的其他方法,也都是 INetworkRunnerCallbacks 界面所需的空方法,這些方法在特定事件發生時被呼叫,例如玩家加入或離開遊戲、與服務器連接或斷開連接等。
- 在這個類別中,這些空方法 (empty method),主要用於實現界面,並未進行任何實際操作。而一般正式遊戲就會看須要而加入適當的程式來處理相對應的事項。
PlayerInputBehaviour 類別用於處理玩家的輸入並將其同步到遊戲的網路層。通過實現 INetworkRunnerCallbacks 界面,它可以在特定事件發生時被呼叫。然而,在這個類別中,我們只專注於處理玩家的輸入,所以直接使用空方法。
玩家
玩家的行為由三個不同的組件定義:
PlayerObject, PlayerMovement, PlayerData
PlayerObject
這是一個玩家物件(PlayerObject)類別,它繼承自 NetworkBehaviour,用於表示遊戲中的玩家,包含與該物件相關的 PlayerRef 的引用、玩家在房間中的索引、暱稱和選定顏色。PlayerObject 也是呼叫 Rpc_Kill 方法的入口點。
- 在 PlayerObject 類別內,定義了一些變數:
Local:表示本地玩家。
Ref、Index、Nickname 和 ColorIndex:用於保存玩家的基本資訊,並使用 Networked 屬性實現網路同步。
Controller、VoiceObject 和 KillRadiusTrigger:這些是玩家物件所需的組件引用。 - Server_Init() 方法:這個方法在伺服器上初始化玩家物件,設置玩家的 PlayerRef、索引和顏色。
- Spawned() 方法:這個方法在玩家物件生成時被呼叫。它首先呼叫基礎的 Spawned() 方法,然後根據物件的 StateAuthority 和 InputAuthority,對玩家物件進行設置。
- Rpc_SetNickname() 和 Rpc_SetColor() 方法:這兩個方法都是遠程呼叫(RPC)方法,允許玩家在網路上設置自己的昵稱和顏色。
- Rpc_Kill() 方法:這是一個 RPC 方法,用於處理玩家之間的擊殺行為。它根據距離判定是否可以擊殺,如果可以,則將被擊殺玩家設為死亡狀態,並在遊戲場景中生成一個 “死亡玩家物件”。
此外,還會為擊殺者設置擊殺計時器。 - NicknameChanged() 和 ColorChanged() 方法:這兩個方法是 Networked 變數的 OnChanged 事件處理程序。當玩家的昵稱或顏色發生變化時,會更新玩家物件的顯示。
PlayerMovement
負責玩家的移動和輸入。它還包含遊戲所需的數據和方法,尤其是IsDead(是否死亡)、IsSuspect(是否可疑)和 EmergencyMeetingUses(緊急會議使用次數)屬性。
PlayerData
玩家的視覺組件。主要負責處理材質、設置動畫屬性並實例化暱稱 UI。
可互動物件
遊戲中的可互動物件,幫助玩家在遊戲中互動和參與各種活動。
Interactable.cs
定義了 Interactable 的抽象類別,該類別繼承自 NetworkBehaviour。用來定義遊戲中可與玩家互動的物件的基類。所有具有互動功能的物件,如死去的玩家或其他遊戲物件,都繼承自這個類別。
- 定義了兩個 bool 變數 isInteractionInstant 和 isGhostAccessible。isInteractionInstant 用於標記互動是否立即生效,
isGhostAccessible 用於標記物件是否可以被幽靈玩家訪問。 - 定義了一個抽象屬性 CanInteract,它是一個 bool 變數,用於確定玩家是否可以與物件互動。這個屬性必須在衍生類別中實現。
- 定義了一個抽象方法 Interact(),該方法用於實現物件與玩家的互動功能。這個方法必須在衍生類別中實現。
Interactable 類別是一個基礎類別,它為遊戲中需要與玩家互動的物件提供了統一的界面。開發人員可以通過繼承 Interactable 類別,實現具體物件的互動功能。由於 Interactable 是一個抽象類別,開發人員需要在派生類中實現 CanInteract 屬性和 Interact() 方法,以確保物件能夠正確地與玩家互動。
顏色機器:
位於預遊戲房間中央的桌子上。玩家可以從12種預設顏色中選擇一個未被其他玩家選擇的顏色。
設置機器:
位於預遊戲房間的頂部,房主可以在此選擇遊戲設置並開始遊戲。
緊急按鈕:
每一回合緊急按鈕可以按有限次數來召集會議。
EmergencyButton.cs
定義了 EmergencyButton 的類別,繼承自先前定義的 Interactable 類別。EmergencyButton 類別表示遊戲中的緊急按鈕,玩家可以與之互動來召開緊急會議。
- 實現了 Interactable 類別中的抽象屬性 CanInteract。在此類別中,條件為本地玩家必須存活(PlayerMovement.Local.IsDead 為 false)並且有緊急會議次數(PlayerMovement.Local.EmergencyMeetingUses > 0)。
- 實現了 Interactable 類別中的抽象方法 Interact()。在此類別中,當玩家與緊急按鈕互動時,呼叫 GameManager 實例的 Rpc_CallMeeting() 方法,並將 Runner.LocalPlayer 和 null 作為參數傳遞。此 null 參數表示在此場景中沒有特定的互動物件,而是通過緊急按鈕發起的會議。
EmergencyButton 類別用於實現遊戲中的緊急按鈕功能。通過繼承 Interactable 類別,它實現了與玩家的交互功能。在玩家與緊急按鈕互動時,可以觸發遊戲中的緊急會議。
任務:
地圖上分布著14個任務站,提供5個獨特的任務小遊戲供船員們完成。
屍體:
被謀殺玩家的屍體可以被船員自由地報告召集會議,或者詐騙者試圖掩蓋他們的行蹤。
DeadPlayer.cs
它定義了一個名為 DeadPlayer 的類別,繼承自 Interactable。
該類別表示遊戲中死去的玩家物件,用於處理與死去玩家的互動。
- 在類別內部,定義了一個 Renderer 類型的數組 modelMeshes,用於存儲死去玩家物件的顏色。
- 使用 Networked 屬性同步 PlayerRef,並在 OnRefChanged 事件處理程序中處理 Ref 的變更。
- Spawned() 方法:這個方法在物件生成時被呼叫。它首先呼叫基類的 Spawned() 方法,然後將物件添加到 GameManager 的管理中。
- SetColour() 方法:這個方法用於設置死去玩家物件的顏色。它實例化一個新的材質並設置顏色,然後遍歷 modelMeshes 中的所有 Renderer,將顏色應用到對應的材質上。
- OnRefChanged() 方法:這是一個靜態方法,用於處理 PlayerRef 發生變更的情況。當 PlayerRef 發生變化時,將死去玩家的顏色設置為與生存玩家相同。
- Interact() 方法:這是一個覆寫的方法,用於處理與死去玩家物件的交互。當玩家與死去玩家交互時,呼叫 GameManager 實例的 Rpc_CallMeeting() 方法。
- CanInteract 屬性:這是一個覆寫的屬性,用於確定玩家是否可以與死去玩家物件交互。只有當本地玩家存活時(IsDead 為 false),才能與死去玩家物件交互。
這個類別主要用於表示遊戲中死去的玩家物件,並處理與該物件的交互。通過繼承 Interactable 類別,它實現了與玩家的交互功能。在玩家與死去玩家物件交互時,可以觸發遊戲中的相應事件,例如召開緊急會議。
任務 (Task)種類
在地圖上可以找到各種任務站。當船員在範圍內時,可以與它們互動。
調節溫度(TemperatureTask.cs)
通過按上下箭頭使兩個數字相等。
滑塊(SlidersTask.cs)
將每個滑塊拖動至與紅色輪廓對齊。當定位正確時,滑塊將被鎖定。
圖案匹配(PatternMatchTask.cs)
按右邊面板上的按鈕,使其與左邊面板上閃爍的燈光序列相匹配。
數字順序(NumberSequenceTask.cs)
按從小到大的數字順序(1–10)。
下載文件(DownloadTask.cs)
按下載按鈕,等待進度條填充以完成任務。
任務說明: 圖案匹配 MiniGame
因為篇幅的關係, 這裡以五個小遊戲其中的 — 圖案匹配 — 來舉列說明, 其它程式的寫法架構也會很類似.
直接來看 PatternMatchTask.cs 程式 , 它是遊戲中的一個任務,名為 “Pattern Match”。該任務要求玩家按照顯示的模式(顏色和音調組合)重複按鈕。以下是各部分的詳細解釋。
類別聲明:
該程式碼定義了一個名為 PatternMatchTask
的類別,該類別繼承自 TaskBase
,表示它是一個遊戲中的任務。
變數和屬性:
pitches
:一個浮點數數組,表示音調的值,共有9個。Name
:重寫基類的屬性,返回任務的名稱,即 "Pattern Match"。- UI相關的變數,例如
patternStageLights
,patternSquares
,matchStageLights
和matchButtons
,用於存儲和控制UI。 - 顏色變數,例如
stageLightOff
、stageLightOn
、patternSquareOff
、patternSquareOn
和patternSquareWrong
,用於設定顏色。 pattern
和match
:用於存儲模式和玩家的輸入。
方法:(這裡用了很多不同的協程 Coroutine 來達成)
ResetTask()
:重置任務,將所有 UI 元素和變數恢復到初始狀態,並在適當情況下啟動顯示模式的協程。OnEnable()
:當物件啟用時,啟動顯示模式的協程。ShowPattern()
:協程,用於顯示模式。在顯示過程中,會向模式中添加隨機的數字,並將對應的顯示元素顏色和音調播放出來。顯示完成後,啟用按鈕,讓玩家進行輸入。PressMatch(int index)
:玩家按下按鈕時呼叫的方法。將玩家的輸入添加到match
列表中,並根據輸入是否正確或任務是否完成來啟動相應的協程。WrongInput()
:協程,用於處理玩家輸入錯誤的情況。在錯誤提示結束後,重置任務。DelayCompleted()
:協程,用於在任務完成後的延遲。在延遲結束後,呼叫Completed()
方法。
所以, 這個 PatternMatchTask
類實現了一個名為 "Pattern Match" 的任務。玩家需要按照遊戲顯示的模式(顏色和音調組合)來按下按鈕。該類包含了一系列的變數、屬性和方法,以實現遊戲任務的邏輯和UI互動。
圖案匹配(任務小遊戲)的過程如下:
- 遊戲顯示模式:遊戲會生成一個隨機的模式,並將其顯示給玩家。模式由顏色和音調組成。
- 玩家輸入:顯示模式後,玩家需要按照顯示的模式按下按鈕。
- 輸入檢查:遊戲會檢查玩家的輸入是否與模式相配。如果不對,遊戲將顯示錯誤提示,並重新開始任務。如果相同,遊戲將繼續顯示新的模式,直到指定的長度(本例中為5)達成。
當達到指定長度時,遊戲將認為任務完成,並執行 DelayCompleted()
協程,最後呼叫Completed()
方法。
該類涵蓋了遊戲任務的主要邏輯,並與Unity和Fusion相關的UI元素互動,以提供玩家一個具有限時挑戰性和趣味性的遊戲體驗。
並且因為這些狀態不用跟其它玩家同步,只會在本機執行,所以直接用協程Coroutine 來進行就好,非常方便有效。
語音:
在 Fusion Impostor中,使用 Photon Voice SDK v2 提供的兩個程式來實現語音功能,用法如下:
- 將 FusionVoiceNetwork 添加到 PrototypeRunner.prefab 中。
- 在玩家預制件( Player.prefab )上使用 VoiceNetworkObject,並將 Speaker 作為給定 prefab 的子物件。
VoiceNetworkObject.cs 說明:
定義了 VoiceNetworkObject
的類別,繼承自 Fusion.NetworkBehaviour
。
此類別用於在遊戲中設置和管理 Photon Voice 的相關組件,像是Recorder
和 Speaker
組件(分別用於處理玩家語音的錄製和播放)。通過這個類別,遊戲中的玩家就可以使用語音聊天功能。
在遊戲開發過程中,開發者可以直接將 VoiceNetworkObject
添加到需要語音功能的遊戲物件上,並根據需要配置相應的 Recorder
和 Speaker
組,就如此範例一樣的作法。這樣,玩家在遊戲中就能夠使用語音聊天功能,與其他玩家進行實時語音交流,提高遊戲的互動性和沉浸感。
小結
在本篇文章中,我們向您介紹了 Fusion Impostor遊戲範例,並深入講解了其中幾個重要的核心架構及簡單應用的場合。若為網路程式的進階者,相信在熟悉此範例的過程中,對於使用 Fusion SDK 做網路遊戲開發會有更深入的瞭解。🚀
此範例涵蓋了 Fusion SDK 和 Photon Voice SDK, 對於還不熟悉 Photon 產品的朋友,可能有些地方會混淆; 所以, 若是網路程式的初學者,我們會建議先看看我們過往的 Fusion SDK 的一些使用引導, 有了初步的經驗後, 再來看這篇會更適合喔. 😀
所以,通掌握這些新知識與技術架構,將能夠更快速、有效地開發具有高度互動性和吸引力的遊戲,快速地成為網路遊戲的開發高手,並獲得更多的成就解鎖與快樂的心情喔 !! 🎉
有開發上的疑問嗎? 直接到 粉絲團 發訊息 (private message) 來討論吧 !
https://www.facebook.com/photoncloudtw/