Quiz Network
概述
Fusion Quiz Network 範例是共享模式的機智問答遊戲,可加入20名玩家,並且使用Photon Voice
。玩家會被問到一系列冷知識問題,快速回答者會獲得分數。此Shared Mode
範例展示了使用預設資料加入遊戲階段、切換主客戶端State Authority
、Tick Timers
的使用情況等。
下載
版本 | 發佈日期 | 下載 | |
---|---|---|---|
2.0.1 | Jun 19, 2024 | Fusion Quiz Network 2.0.1 Build 576 |
連線到Fusion
FusionConnection
類別負責為遊戲階段建立NetworkRunner
。它也儲存本機玩家的名稱和本機玩家將建立的遊戲階段遊戲的名稱(如果指定的話)。
FusionConnection
是作為一個單一資料庫來執行的,這意味著它只能有一個執行個體。該執行個體位於Awake
方法中,附有以下程式碼:
C#
private void Awake()
{
// ...
if (Instance != null)
{
Destroy(gameObject);
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
在Quiz Network
的主選單中,當玩家選擇Create Room
或Join Room
,玩家嘗試透過以下的開始引數來連線:
C#
public async void StartGame(bool joinRandomRoom)
{
StartGameArgs startGameArgs = new StartGameArgs()
{
GameMode = GameMode.Shared,
SessionName = joinRandomRoom ? string.Empty : LocalRoomName,
PlayerCount = 20,
};
// ...
}
GameMode
:使用的Game Mode
,在這個案例中是Shared Mode
,其中客戶端連線到Photon Cloud房間,每個玩家都對他們生成的網路物件擁有狀態授權。SessionName
:將建立的遊戲階段的名稱。如果沒有指定遊戲階段,或者joinRandomRoom
為真,則對戰配對將嘗試讓玩家加入開啟的遊戲階段。如果這樣做失敗,則使用System.Guid
建立新的遊戲階段。否則,玩家將嘗試使用LocalRoomName
加入遊戲階段;如果這個遊戲階段不存在,他們將使用這個名稱建立一個新遊戲階段。PlayerCount
:定義遊戲階段中允許的玩家數量,在本例中為20。如果LocalRoomName
定義的遊戲階段有20名玩家,如果新的玩家試圖加入該遊戲階段,則會顯示錯誤。
在此之後,具現化一個新的NetworkRunner
,並嘗試連線這些StartGameArgs
。
C#
// ...
NetworkRunner newRunner = Instantiate(_networkRunnerPrefab);
StartGameResult result = await newRunner.StartGame(startGameArgs);
if (result.Ok)
{
roomName.text = "Room: " + newRunner.SessionInfo.Name;
GoToGame();
}
else
{
roomName.text = string.Empty;
GoToMainMenu();
errorMessageObject.SetActive(true);
TextMeshProUGUI gui = errorMessageObject.GetComponentInChildren<TextMeshProUGUI>();
if (gui) {
gui.text = result.ErrorMessage;
}
Debug.LogError(result.ErrorMessage);
}
// ...
因為NetworkRunner.StartGame
是一個非同步函數,在設定StartGameResult
之前會有延遲。如果完成,如果成功加入遊戲階段,它將顯示遊戲階段名稱;如果失敗,將顯示一個錯誤畫面。
冷知識玩家
當玩家加入,就會生成他們的虛擬人偶。此NetworkObject
包含名為TriviaPlayer
的NetworkBehaviour
。每個玩家都有一系列使用Networked
屬性的屬性。
C#
[Networked(), OnChangedRender(nameof(OnPlayerNameChanged))]
public NetworkString<_16> PlayerName { get; set; }
此屬性管理玩家名稱的顯示方式。它使用NetworkString
,這是Fusion的一個獨特類型,用於處理具有強制限制的字串,在本例中為16個字元。它也使用第二個屬性OnChangedRender
和函數的名稱OnPlayerNamed
。
C#
void OnPlayerNameChanged()
{
nameText.text = PlayerName.Value;
}
它更新作為TextMeshProUGUI
物件的nameText
的text
屬性。需要注意的是,當一個新玩家生成時,不會自動調用OnChangedRender
方法。取而代之地,最好在NetworkObject
的Spawned
方法中更新這些屬性。
此外,當生成時,玩家會被新增到一個靜態清單中,該清單包含對所有TriviaPlayers
的參照以及對本機TriviaPlayer
的靜態參照,稍後將對此進行解釋。
冷知識管理器
Trivia Manager
是一種NetworkBehaviour
,用於處理遊戲開始後的調換問題順序和更新將詢問的問題,以及處理使用的TickTimer
。當生成Trivia Manager
時,遊戲開始;不需要遠端程序調用(RPC),因為當NetworkRunner
生成Trivia Manager
時,將為所有玩家生成它。
在Shared Mode
中,沒有一個主機端玩家像在Host Mode
中那樣對場景具有狀態授權。然而,只有一個玩家可以對該物件具有狀態授權,在這種情況下,它就是Shared Mode Master Client
。設定時,Trivia Manager
被指定為主客戶端物件。
這意味著主客戶端將對該物件擁有狀態授權,而如果他們離開,狀態授權將移交給新的主客戶端。由於Trivia Manager
執行IStateAuthorityChanged
介面,因此當發生此移交時,它將調用StateAuthorityChanged
。
Trivia Manager
也有一個TickTimer
,用於更新遊戲的各種狀態。僅在FixedUpdateNetwork
期間檢查TickTimer
,該過程僅由具有狀態授權的玩家執行,因此TickTimer
的視覺效果更新是在Update
中處理的,這是因為所有玩家都將執行此方法。
C#
public void Update()
{
// Updates the timer visual
float? remainingTime = timer.RemainingTime(Runner);
if (remainingTime.HasValue)
{
float percent = remainingTime.Value / timerLength;
timerVisual.fillAmount = percent;
timerVisual.color = timerVisualGradient.Evaluate(percent);
}
else
{
timerVisual.fillAmount = 0f;
}
}
當在TickTimer
上輪詢剩餘時間時,結果可以是Nullable
,用float?
表示,這意味著它可以具有值或為null
。然後以不同的方式處理這些結果。
回答問題
當Trivia Manager
透過增加FixedUpdateNetwork
中的CurrentQuestion
來更新當前問題時,玩家只需按一下按鈕即可回答問題,這將觸發PickAnswer
。
C#
public void PickAnswer(int index)
{
// If we are in the question state and the local player has not picked an answer...
if (GameState == TriviaGameState.ShowQuestion)
{
// For now, if Chosen Answer is less than 0, this means they haven't picked an answer.
// We don't allow players to pick new answers at this time.
if (TriviaPlayer.LocalPlayer.ChosenAnswer < 0)
{
_confirmSFX.Play();
TriviaPlayer.LocalPlayer.ChosenAnswer = index;
// Colors the highlighted question cyan.
answerHighlights[index].color = Color.cyan;
float? remainingTime = timer.RemainingTime(Runner);
if (remainingTime.HasValue)
{
float percentage = remainingTime.Value / this.timerLength;
TriviaPlayer.LocalPlayer.TimerBonusScore = Mathf.RoundToInt(timeBonus * percentage);
}
else
{
TriviaPlayer.LocalPlayer.TimerBonusScore = 0;
}
}
else
{
_errorSFX.Play();
}
}
}
在這種方法中,首先檢查TriviaManager
的GameState
,以確保在此時顯示一個問題。如果TriviaPlayer
的LocalPlayer
參照沒有選擇由ChosenAnwswer
指定的小於0的答案,則它們在Unity側定義的所選值將設定為ChosenAnswer
。此外,TimerBonusScore
被設定為基於TriviaManager
的TickTimer
的剩餘時間和Unity側定義值的值。
然後,在TriviaManager
的FixedUpdateNetwork
方法中,檢查每個玩家的答案。
C#
// ...
// We check to see if every player has chosen answer, and if so, go to the show answer state.
if (GameState == TriviaGameState.ShowQuestion)
{
int totalAnswers = 0;
for (int i = 0; i < TriviaPlayer.TriviaPlayerRefs.Count; i++)
{
if (TriviaPlayer.TriviaPlayerRefs[i].ChosenAnswer >= 0)
{
totalAnswers++;
}
}
if (totalAnswers == TriviaPlayer.TriviaPlayerRefs.Count)
{
timerLength = 3f;
timer = TickTimer.CreateFromSeconds(Runner, timerLength);
GameState = TriviaGameState.ShowAnswer;
}
}
檢查GameState
,如果顯示問題,則TriviaPlayerRefs
中的每個TriviaPlayer
參照都會被迭代。如果他們已經回答了問題,totalAnswers
將遞增,如果它與玩家數量匹配,則TriviaManager
的GameState
將變為TriviaGameState.ShowAnswer
並顯示答案。這就是為什麼當每個如前所述的TriviaPlayer
生成時,都會儲存對其的參照。
結束遊戲
Trivia Manager
也透過其FixedUpdateNetwork
方法處理遊戲的結束。
C#
// When the timer expires...
if (timer.Expired(Runner))
{
// If we are showing a question, we then show an answer...
if (GameState == TriviaGameState.ShowQuestion)
{
timerLength = 3f;
timer = TickTimer.CreateFromSeconds(Runner, timerLength);
GameState = TriviaGameState.ShowAnswer;
return;
}
else if (QuestionsAsked < maxQuestions)
{
TriviaPlayer.LocalPlayer.ChosenAnswer = -1;
CurrentQuestion++;
QuestionsAsked++;
timerLength = questionLength;
timer = TickTimer.CreateFromSeconds(Runner, timerLength);
GameState = TriviaGameState.ShowQuestion;
}
else
{
timer = TickTimer.None;
GameState = TriviaGameState.GameOver;
}
return;
}
當TickTimer
到期時,將檢查QuestionsAsked
,如果所問問題的數量不再小於在Unity側設定的maxQuestions
定義的一回合中的問題數量,則將TickTimer
設定為TickerTime.None
,停止它,並且TriviaManager
的GameState
設定為TriviaGameState.GameOver
。
透過設定GameState
,OnTriviaGameStateChanged
作為GameState
的OnChangedRender
屬性的一部分被觸發。在這種方法中,調用OnGameStateGameOver
並評估最終分數。
C#
private void OnGameStateGameOver()
{
// ...
// Sorts all players in a list and keeps the three highest players.
List<TriviaPlayer> winners = new List<TriviaPlayer>(TriviaPlayer.TriviaPlayerRefs);
winners.RemoveAll(x => x.Score == 0);
winners.Sort((x, y) => y.Score - x.Score);
if (winners.Count > 3)
{
winners.RemoveRange(3, winners.Count - 3);
}
endGameObject.Show(winners);
if (winners.Count == 0)
{
triviaMessage.text = "No winners";
}
else
{
triviaMessage.text = winners[0].PlayerName.Value + " Wins!";
}
// ...
}
這將所有當前玩家帶到一個新的清單,該清單按分數排序,前三名玩家將被保留並提供給endGameObject.Show
,它將獲勝者安排到最後決賽。此外,Shared Mode Master Client
也顯示了一個按鈕,用於開始新一輪的問題。
Photon Voice
已連線玩家可以透過Photon Voice
以麥克風進行通信。在本範例中,使用以下元件來實現此目的:
Fusion Voice Client
:此元件被新增到NetworkRunner
預製件中,並定義Photon Voice
的初始設定。
Recorder
:此元件被新增到TriviaPlayer
預製件中,並記錄用戶的語音以透過網路發送Speaker
:也新增到TriviaPlayer
預製件中,這個元件從其他玩家接收已錄製的音訊,並通過附加的AudioSource
元件播放。Voice Network Object
:這個附加到TriviaPlayer
的NetworkBehaviour
處理Recorder
和Speaker
的設定,以便與Photon Fusion
一起使用。
在遊戲中,切換一個圖標來指示本機玩家何時被錄製或其他玩家何時發言。TriviaPlayer
的Update
函數中的以下程式碼展示了這一點:
C#
private void Update()
{
speakingIcon.enabled = (_voiceNetworkObject.SpeakerInUse && _voiceNetworkObject.IsSpeaking) || (_voiceNetworkObject.RecorderInUse && _voiceNetworkObject.IsRecording);
}
首先,VoiceNetworkObject
的SpeakerInUse
和IsSpeaking
内容說明遠端玩家正在講話;同時,RecorderInUse
和IsRecording
表示本機玩家正在講話。
在這個範例中,本機玩家可以禁止他們的音訊傳輸,並向其他玩家顯示他們已靜音。這是透過以下方式實現的:
C#
[Networked(), OnChangedRender(nameof(OnMuteChanged))]
public NetworkBool Muted { get; set; }
public void OnMuteChanged()
{
muteSpeakerIcon.enabled = Muted;
}
public void ToggleVoiceTransmission()
{
if (HasStateAuthority)
{
Muted = !Muted;
_recorder.TransmitEnabled = !Muted;
}
}
Muted
,一個具有OnChangedRender
屬性的NetworkBool
,稱為OnMuteChanged
,它更新muteSpeakerIcon
,這是一種顯示玩家被靜音的視覺效果表示。ToggleVoiceTransmission
是一種透過Unity側Button
的OnClick
事件觸發的函數,該函數為具有StateAuthority
的玩家切換Muted
值,並設定Recover.TransmitEnabled
與Muted
相反。設定Recover.TransmitEnabled
設定為偽,將禁止錄製本機玩家。
您可以在此閱讀更多以Photon Fusion設定Photon Voice的資訊。
第三方資產
Quiz Network範例包含多個Kenney提供的第三方資產,其使用一個CC0,授權意味著它們是公共領域的,可以用於專案,無論是商業專案還是其他專案,無需署名。
Back to top