This document is about: FUSION 2
SWITCH TO

Matchmaking API

概述

建立多人遊戲的一個關鍵要求是能夠輕鬆地將具有相似技能、級別或想要玩相同遊戲類型或地圖的玩家配對在一起,使遊戲中的整體體驗盡可能愉快。
為此,Photon Fusion公開了一組API調用,可用於為尋找完美配對的玩家創造最佳體驗。

Photon Fusion與Photon Cloud透明地協同工作,因此與Photon後端服務的大多數互動都是在內部自動完成的。
在本頁中,介紹了Fusion Matchmaking API,該API用於建立具有可選自訂屬性的Game Session,玩家可以使用該自訂屬性根據其期望的遊戲體驗來篩選/加入最佳Session

術語

為了充分理解API,以下描述了本檔案中使用的與Matchmaking API的正確使用直接相關的一些術語。

  • Game Session:或簡稱為Session,是玩家見面進行比賽或交流的地方。這是在Photon Cloud中發佈的,可以讓其他客戶端搜尋、篩選和加入特定遊戲。任何在Session之外的通信都是不可能的,任何客戶端只能在一個Session中處於活躍中狀態。Game Sessions具有以下屬性和方法:可以按名稱建立和加入,Custom Properties,具有最大玩家數量,可以隱藏(不顯示在Lobby中)或可見,可以關閉(沒有人可以加入)或打開。在PUNPhoton Realtime中,它以前被稱為Room
  • Lobby:是Sessions的虛擬容器或「清單」。例如,可以使用多個大廳將遊戲階段分為不同的遊戲類型,因為這基本上是一種在任意一組遊戲階段中列出Game Session的方法。客戶端 無法Lobby進行通信,他們永遠不知道大廳裏還有另一個客戶端。客戶只能在LobbyGame Session或不在兩者中。

建立和加入遊戲階段

Game Session的建立和加入是同一過程的兩個部分,規則很簡單:
1. 如果沒有具有指定SessionNameSession,則將建立一個具有該SessionName的新遊戲階段(並非在所有情況下,如下文所述)。
2. 如果已經存在具有該SessionName的遊戲階段,則同儕節點將加入它。

就API而言,所有這些都是在啟動新的Fusion模擬時自動完成的。
下方列出了在建立新遊戲階段時,可用於自訂Session或在查找要加入的Session時用作篩選器的主要引數:

C#

NetworkRunner.StartGame(new StartGameArgs {
  // ...
  // Arguments related to the Matchmaking between peers
  SessionName = [string],
  SessionProperties = [Dictionary<string, SessionProperty>],
  CustomLobbyName = [string],
  EnableClientSessionCreation = [bool],
  PlayerCount = [int],
  IsOpen = [bool],
  IsVisible = [bool],
  MatchmakingMode = [MatchmakingMode],
});

所有與對戰配對相關的引數都是可選的,每個引數的預設值如下所述:

  • SessionNameGame SessionNameID,它將標識Photon Cloud上的遊戲階段,並且它在地區必須 是唯一的。如果沒有設定名稱,Fusion將生成一個隨機的GUID來標識遊戲階段。
  • SessionPropertiesSessionCustom Properties是在Game Session中包含中繼資料的方式,例如遊戲模式/類型或當前地圖。請記住,在創建Session時,所有屬性都會發佈到Lobby,當同儕節點加入隨機的Session時,這些屬性可以用作配對篩選器(下方閱讀更多)。建議是,始終儘量保持Property Keys盡可能短,以儘量減少流量。預設下,Session Custom Properties為空,不包含任何額外資訊。
  • CustomLobbyName:此引數用於設定與Session關聯的自訂Lobby Name。預設下,Fusion已經根據GameMode(在HostServerClient中啟動時為ClientServer Lobby,在Shared遊戲模式中啟動時,為Shared Lobby)分離了一個Session
  • EnableClientSessionCreation:此旗標更改哪些Client同儕節點類型可以建立新的Game Session或只能加入一個。請參閱下文瞭解更多資訊。
  • PlayerCount:定義可以加入Session的最大客戶端數。此參數僅在創建新的Session時使用,預設下,它從NetworkProjectConfig/Simulation上的Default Players欄位中獲取值。
  • IsOpen:定義正在創建的Game Session(適用時)是否將設定為開放,以便任何其他玩家加入。有關更多資訊,請參閱獲得與更新遊戲階段資訊
  • IsVisible:定義正在創建的Game Session(適用時)是否對其他玩家可見。有關更多資訊,請參閱獲得與更新遊戲階段資訊
  • MatchmakingMode:定義嘗試加入隨機Game Session時使用的對戰配對模式。
    • FillRoom:填充房間(最舊的先),以讓玩家儘快聚在一起。預設。
    • SerialMatching:按順序在可用房間中分配玩家,但會考慮篩選器。沒有篩選器的話,房間裡的玩家將均勻分配。
    • RandomMatching:加入(完全)隨機房間。預期的屬性必須匹配,但除此之外,可以選擇任何可用的房間。

可以隨機或使用特定的SessionName(例如,對遊戲邀請有用)來創建和加入Game Session,還可以使用自訂屬性啟用Session篩選,以便只加入具有特定設置的遊戲。
這在管理遊戲階段時提供了很大的靈活性。

下表總結了Fusion如何處理Game Session的創建和加入,因為它取決於SessionNameGameMode以及啟動模擬時是否啟用了EnableClientSessionCreation

遊戲模式 遊戲階段名稱
有效 空的/空值
伺服器/主機 建立或加入特定遊戲階段 以隨機ID建立或加入遊戲階段
EnableClientSessionCreation
空值(預設) 空值(預設)
客戶端 加入遊戲階段 建立或加入遊戲階段 加入遊戲階段 加入隨機遊戲階段 加入隨機或建立 加入隨機遊戲階段
共享 建立或加入遊戲階段 建立或加入遊戲階段 加入遊戲階段 加入隨機或建立 加入隨機或建立 加入隨機遊戲階段
AutoHostOrClient 建立或加入遊戲階段 建立或加入遊戲階段 加入遊戲階段 加入隨機或建立 加入隨機或建立 加入隨機遊戲階段

獲取和更新遊戲階段資訊

Fusion提供了有關當前連線的Game Session的大量資訊,如NameRegion
這些資料可以透過SessionInfo屬性直接在NetworkRunner中獲得。
下方列出了SessionInfo類型的所有可用欄位:

  • IsValid [bool{get}]:如果SessionInfo已準備好進行讀取/寫入,則發出訊號。
  • Name [string{get}]Session Name
  • Region [string{get}]:目前已連線的Region
  • Properties [Dictionary<string, SessionProperty>{get}]:一個具有當前Session Custom Properties的唯讀字典。為了更新這些屬性,只需使用SessionInfo.UpdateCustomProperties(Dictionary<string, SessionProperty>)方法,並傳遞一組新的屬性。
  • IsVisible [bool{get,set}]:如果SessionLobby上為Visible,則發出訊號。使Session不可見,是更改此屬性的事情。
  • IsOpen [bool{get,set}]:如果SessionOpen加入,則發出訊號。要關閉或打開Sesion,只需更改此屬性。
  • PlayerCount [int{get}]Session中當前的Players數量。僅在大廳可用
  • MaxPlayers [int{get}]:可以加入Session的同儕節點的Max Number,此值還包括Server/Host同儕節點的槽。僅在大廳可用

請記住,例如,Session資訊和主要的Custom Properties應僅用於對戰配對目的,比如 絕不能 與遊戲客戶端同步遊戲狀態資訊。我們強烈反對這種使用。
如果您需要在整個遊戲階段範圍內交換與遊戲遊玩相關的資訊,Fusion提供了很多選擇,比如擁有一個全域的NetworkObject或使用RPC來獲取一次性資料。

NetworkRunner還提供了一些其他與SessionPhoton Cloud相關的屬性,可以在遊戲中使用,比如:

  • NetworkRunner.IsCloudReady:如果本機同儕節點連線到Photon Cloud並且能夠建立/加入房間或加入大廳,則發出訊號。
  • NetworkRunner.UserId:在本機同儕節點通過身份驗證後,保存與本機同儕節點關聯的UserId。此資訊來自您的應用程式使用的身份驗證服務。
  • NetworkRunner.AuthenticationValues:保存啟動Fusion時用於對本機同儕節點進行身份驗證的AuthenticationValues的參照。
  • NetworkRunner.CurrentConnectionType:描述同儕節點使用的當前連線類型,是與遠端ServerDirectRelayed連線。請記住,在SharedMode中,客戶端始終透過中繼連線。
  • NetworkRunner.NATType:當啟用NAT穿透系統時,Fusion將嘗試確定本機同儕節點正在運行的當前網路的當前NAT類型,此屬性會公開此資訊。NAT類型可以是:InvalidUdpBlockedOpenInternetFullConeSymmetric
  • NetworkRunner.IsSharedModeMasterClient:布林值旗標,描述本機同儕節點是否也是Shared Game SessionMaster Client。這僅在SharedMode中運行時有效,可用於根據其他客戶端和Master Client之間的差異來確定應在哪個同儕節點執行哪些特定操作。

另一個相關欄位是NetworkRunner.LobbyInfo,它公開了同儕節點連線到的當前Lobby的資訊(請記住,同儕節點只能處於LobbyGame Session或中斷連線)。這裡我們列出了LobbyInfo的主要屬性:

  • IsValid [bool{get}]:如果LobbyInfo已準備好進行讀取/寫入,則發出訊號。
  • Name [string{get}]:包含當前大廳的名稱。

遊戲階段瀏覽器

可以在Fusion中建立遊戲階段瀏覽器;然而,我們強烈反對這樣做。

遊戲階段瀏覽器在90年代和21世紀初很受歡迎。基本設計仍然有效,但如今他們的主要目的(找到合適的遊戲並快速加入)已被對戰配對所取代。

不鼓勵使用遊戲階段瀏覽器的原因有很多:

  • 現時存在安全問題;
  • 擷取活躍中遊戲階段清單對於伺服器來說是一項效能繁重的操作;
  • 清單總是特定於玩家當前連線的區域; 並且,
  • 最重要的是 這是一種過時的設計模式,如今有更好的UX選項可供選擇。

Fusion Matchmaking API提供的最新替代方案包括:

  1. 為了快速填充/加入遊戲階段:加入隨機開放的房間。
  2. 為了加入特定類型的比賽:加入一個隨機的開放房間,同時使用遊戲階段屬性進行篩選。
  3. 為了讓特定玩家加入並一起玩:建立邀請碼和/或按照名稱加入遊戲階段。

除非遊戲有大量運行的社群伺服器,而且這些伺服器有自己的自訂/修改模式和地圖,否則幾乎沒有理由要求提供完整的房間清單,並透過遊戲階段瀏覽器向使用者提供這些房間。

API使用示例

加入隨機遊戲階段

為了加入任何遊戲階段,如果玩家只想快速加入遊戲,只需啟動NetworkRunner,讓它找到一個可用的Game Session,而不需要任何額外參數:

C#

public async Task StartPlayer(NetworkRunner runner) {

  var result = await runner.StartGame(new StartGameArgs() {
    GameMode = GameMode.AutoHostOrClient, // or GameMode.Shared
  });

  if (result.Ok) {
    // all good
  } else {
    Debug.LogError($"Failed to Start: {result.ShutdownReason}");
  }
}

這樣,本機同儕節點將啟動並連線到隨機的Game Session,如果找不到,它將建立一個具有隨機Session Name的新遊戲階段(因為它正在使用GameMode.AutoHostOrClient)。如果使用GameMode.Shared啟動NetworkRunner,對於共享模式下的遊戲階段,這也是有效的。

使用自訂屬性啟動新的遊戲階段

在這個例子中,Host將建立一個具有一些自訂屬性的Game Session,因此稍後,Clients可以使用這些屬性篩選Sessions

C#

// Some predefined types used as values for the Game Session Properties
public enum GameType : int {
  FreeForAll,
  Team,
  Timed
}

public enum GameMap : int {
  Forest,
  City,
  Desert
}

// Utility method to start a Host using a defined GameMap and GameType
public async Task StartHost(NetworkRunner runner, GameMap gameMap, GameType gameType) {

  var customProps = new Dictionary<string, SessionProperty>();

  customProps["map"] = (int)gameMap;
  customProps["type"] = (int)gameType;

  var result = await runner.StartGame(new StartGameArgs() {
    GameMode = GameMode.Host,
    SessionProperties = customProps,
  });

  if (result.Ok) {
    // all good
  } else {
    Debug.LogError($"Failed to Start: {result.ShutdownReason}");
  }
}

示例代碼顯示了對Game SessionCustom Properties值使用Enums,但這只是為值增加意義的一種方式。
調用runner.StartGame作為Host (GameMode = GameMode.Host),已經足以用Random Name(因為未傳遞SessionName引數)啟動一個新的遊戲階段,並透過使用SessionProperties引數,Fusion將這些屬性包含在Session”中。

使用篩選器加入隨機遊戲階段

考慮到上面的示例程式碼,這裡展示了如何啟動一個Client,該客戶端將加入任何GameMap上的任何Game Session,但具有特定的GameType
啟動程式碼基本相同,只是GameMode現在設定為GameMode.Client,而customProps僅包含具有所需gameType值的type鍵值。

C#

public async Task StartClient(NetworkRunner runner, GameType gameType) {

  var customProps = new Dictionary<string, SessionProperty>() {
    { "type", (int)gameType }
  };

  var result = await runner.StartGame(new StartGameArgs() {
    GameMode = GameMode.Client,
    SessionProperties = customProps,
  });

  if (result.Ok) {
    // all good
  } else {
    Debug.LogError($"Failed to Start: {result.ShutdownReason}");
  }
}

這足以讓你的客戶端加入一個具有特定GameType的隨機Session

從大廳加入遊戲階段

找到正確的Game Session的另一種方法是提供一個Sessions清單,允許玩家選擇一個加入。
在這種情況下,加入Lobby是正確的做法,儘管我們強烈建議在 實在 沒有必要的情況下避免這種方法。
對於大多數遊戲類型,基於屬性篩選器加入Session是最好的方式,但Fusion也使Session清單變得非常容易。

與使用通常的流程並如上所述啟動Fusion不同,Session清單遵循的流程略有不同:

  1. 加入大廳:使用Fusion Runner參照,只需調用NetworkRunner.JoinSessionLobby([SessionLobby], [string]),以使同儕節點連線到Photon Cloud並加入特定的Lobby。此方法接收兩個引數:
    • SessionLobby:可以是以下值之一:
      1. ClientServer加入預設的ClientServer Lobby
      2. Shared加入預設的Shared Lobby;及,
      3. Custom,與自訂LobbyName結合使用。
    • LobbyName:這應該是之前建立Game Session時使用的Custom Lobby Name

C#

// Utility method to Join the ClientServer Lobby
public async Task JoinLobby(NetworkRunner runner) {

  var result = await runner.JoinSessionLobby(SessionLobby.ClientServer);

  if (result.Ok) {
    // all good
  } else {
    Debug.LogError($"Failed to Start: {result.ShutdownReason}");
  }
}

如前所述,Client可以透過這種方式加入ClientServerSharedCustom大廳。
舉個例子,下方顯示了伺服器/主機如何在自訂Lobby中建立Session

C#

public async Task StartHost(NetworkRunner runner) {

  var result = await runner.StartGame(new StartGameArgs() {
    GameMode = GameMode.Host,
    CustomLobbyName = "MyCustomLobby"
  });

  if (result.Ok) {
    // all good
  } else {
    Debug.LogError($"Failed to Start: {result.ShutdownReason}");
  }
}

以及Client如何加入自訂Lobby

C#

// Utility method to Join a Custom Lobby
public async Task JoinLobby(NetworkRunner runner) {

  var result = await runner.JoinSessionLobby(SessionLobby.Custom, "MyCustomLobby");

  if (result.Ok) {
    // all good
  } else {
    Debug.LogError($"Failed to Start: {result.ShutdownReason}");
  }
}
  1. 獲取遊戲階段清單:在使用Fusion時,API的主要入口點之一是INetworkRunnerCallbacks,這是Fusion用來展示一系列不同事件的特殊介面,包括Lobby的遊戲階段清單。每次遊戲階段更改時,無論是建立/刪除遊戲階段還是更新遊戲階段屬性,都會叫用OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList)回調,並從大廳中獲取完整的遊戲階段清單。SessionInfo與上述類型相同。然後可以顯示、篩選、排序清單等。

  2. 加入遊戲階段:選擇要加入的Session後,可以使用常用的NetworkRunner.StartGame()啟動Fusion。但在這種情況下,用於啟動客戶端的SessionName必須是遊戲階段中的名稱。這樣,Client將加入該特定的Game Session

    • 像往常一樣選擇正確的GameMode,因為同儕節點正在加入Session,它必須是GameMode.ClientGameMode.Shared模式。
    • SessionName欄位必須設定為SessionInfo.Name,因為這是Game Session的識別字。
    • 所有其他參數都是可選的,應相應地初始化。

C#

public class MyBehaviour : Fusion.Behaviour, INetworkRunnerCallbacks {

  // other callbacks...

  // Receive the List of Sessions from the current Lobby
  public void OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList) {

    Debug.Log($"Session List Updated with {sessionList.Count} session(s)");

    // Example
    // Join first session from the list

    // Check if there are any Sessions to join
    if (sessionList.Count > 0) {

      // Get first Session from the list
      var session = sessionList[0];

      Debug.Log($"Joining {session.Name}");

      // Join
      runner.StartGame(new StartGameArgs() {
        GameMode = GameMode.Client, // Client GameMode, could be Shared as well
        SessionName = session.Name, // Session to Join
        // ...
      });
    }

    // OR

    // Example
    // Search the list for a Session with a specific Property

    // Store the target session
    SessionInfo session = null;

    foreach (var sessionItem in sessionList) {

      // Check for a specific Custom Property
      if (sessionItem.Properties.TryGetValue("type", out var propertyType) && propertyType.IsInt) {

        var gameType = (int)propertyType.PropertyValue;

        // Check for the desired Game Type
        if (gameType == 1) {

          // Store the session info
          session = sessionItem;
          break;
        }
      }
    }

    // Check if there is any valid session
    if (session != null) {
      Debug.Log($"Joining {session.Name}");

      // Join
      runner.StartGame(new StartGameArgs() {
        GameMode = GameMode.Client, // Client GameMode, could be Shared as well
        SessionName = session.Name, // Session to Join
        // ...
      });
    }
  }
}
Back to top