This document is about: FUSION 2
SWITCH TO

Quiz Network

Level 4

概述

Fusion Quiz Network 範例是共享模式的機智問答遊戲,可加入20名玩家,並且使用Photon Voice。玩家會被問到一系列冷知識問題,快速回答者會獲得分數。此Shared Mode範例展示了使用預設資料加入遊戲階段、切換主客戶端State AuthorityTick 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 RoomJoin 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包含名為TriviaPlayerNetworkBehaviour。每個玩家都有一系列使用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物件的nameTexttext屬性。需要注意的是,當一個新玩家生成時,不會自動調用OnChangedRender方法。取而代之地,最好在NetworkObjectSpawned方法中更新這些屬性。

此外,當生成時,玩家會被新增到一個靜態清單中,該清單包含對所有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();
        }
    }
}

在這種方法中,首先檢查TriviaManagerGameState,以確保在此時顯示一個問題。如果TriviaPlayerLocalPlayer參照沒有選擇由ChosenAnwswer指定的小於0的答案,則它們在Unity側定義的所選值將設定為ChosenAnswer。此外,TimerBonusScore被設定為基於TriviaManagerTickTimer的剩餘時間和Unity側定義值的值。

然後,在TriviaManagerFixedUpdateNetwork方法中,檢查每個玩家的答案。

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將遞增,如果它與玩家數量匹配,則TriviaManagerGameState將變為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,停止它,並且TriviaManagerGameState設定為TriviaGameState.GameOver

透過設定GameStateOnTriviaGameStateChanged作為GameStateOnChangedRender屬性的一部分被觸發。在這種方法中,調用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的初始設定。
`Fusion Voice Client`元件的預覽。
  • Recorder:此元件被新增到TriviaPlayer預製件中,並記錄用戶的語音以透過網路發送
  • Speaker:也新增到TriviaPlayer預製件中,這個元件從其他玩家接收已錄製的音訊,並通過附加的AudioSource元件播放。
  • Voice Network Object:這個附加到TriviaPlayerNetworkBehaviour處理RecorderSpeaker的設定,以便與Photon Fusion一起使用。
三個先前提及的元件及它們的設定。

在遊戲中,切換一個圖標來指示本機玩家何時被錄製或其他玩家何時發言。TriviaPlayerUpdate函數中的以下程式碼展示了這一點:

C#

private void Update()
{
    speakingIcon.enabled = (_voiceNetworkObject.SpeakerInUse && _voiceNetworkObject.IsSpeaking) || (_voiceNetworkObject.RecorderInUse && _voiceNetworkObject.IsRecording);
}

首先,VoiceNetworkObjectSpeakerInUseIsSpeaking内容說明遠端玩家正在講話;同時,RecorderInUseIsRecording表示本機玩家正在講話。

在這個範例中,本機玩家可以禁止他們的音訊傳輸,並向其他玩家顯示他們已靜音。這是透過以下方式實現的:

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側ButtonOnClick事件觸發的函數,該函數為具有StateAuthority的玩家切換Muted值,並設定Recover.TransmitEnabledMuted相反。設定Recover.TransmitEnabled設定為偽,將禁止錄製本機玩家。

遊戲中一個玩家講話,另一個玩家靜音的預覽。

您可以在此閱讀更多以Photon Fusion設定Photon Voice的資訊。

第三方資產

Quiz Network範例包含多個Kenney提供的第三方資產,其使用一個CC0,授權意味著它們是公共領域的,可以用於專案,無論是商業專案還是其他專案,無需署名。

Back to top