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
中)或可見,可以關閉(沒有人可以加入)或打開。在PUN
和Photon Realtime
中,它以前被稱為Room
。Lobby
:是Sessions
的虛擬容器或「清單」。例如,可以使用多個大廳將遊戲階段分為不同的遊戲類型,因為這基本上是一種在任意一組遊戲階段中列出Game Session
的方法。客戶端 無法 在Lobby
進行通信,他們永遠不知道大廳裏還有另一個客戶端。客戶只能在Lobby
、Game Session
或不在兩者中。
建立和加入遊戲階段
Game Session
的建立和加入是同一過程的兩個部分,規則很簡單:
1. 如果沒有具有指定SessionName
的Session
,則將建立一個具有該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],
});
所有與對戰配對相關的引數都是可選的,每個引數的預設值如下所述:
- SessionName:
Game Session
的Name
或ID
,它將標識Photon Cloud
上的遊戲階段,並且它在地區內 必須 是唯一的。如果沒有設定名稱,Fusion將生成一個隨機的GUID
來標識遊戲階段。 - SessionProperties:
Session
的Custom Properties
是在Game Session
中包含中繼資料的方式,例如遊戲模式/類型或當前地圖。請記住,在創建Session
時,所有屬性都會發佈到Lobby
,當同儕節點加入隨機的Session
時,這些屬性可以用作配對篩選器(下方閱讀更多)。建議是,始終儘量保持Property Keys
盡可能短,以儘量減少流量。預設下,Session Custom Properties
為空,不包含任何額外資訊。 - CustomLobbyName:此引數用於設定與
Session
關聯的自訂Lobby Name
。預設下,Fusion已經根據GameMode
(在Host
、Server
或Client
中啟動時為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
的創建和加入,因為它取決於SessionName
、GameMode
以及啟動模擬時是否啟用了EnableClientSessionCreation
。
遊戲模式 | 遊戲階段名稱 | |||||
---|---|---|---|---|---|---|
有效 | 空的/空值 | |||||
伺服器/主機 | 建立或加入特定遊戲階段 | 以隨機ID建立或加入遊戲階段 | ||||
EnableClientSessionCreation | ||||||
空值(預設) | 真 | 偽 | 空值(預設) | 真 | 偽 | |
客戶端 | 加入遊戲階段 | 建立或加入遊戲階段 | 加入遊戲階段 | 加入隨機遊戲階段 | 加入隨機或建立 | 加入隨機遊戲階段 |
共享 | 建立或加入遊戲階段 | 建立或加入遊戲階段 | 加入遊戲階段 | 加入隨機或建立 | 加入隨機或建立 | 加入隨機遊戲階段 |
AutoHostOrClient | 建立或加入遊戲階段 | 建立或加入遊戲階段 | 加入遊戲階段 | 加入隨機或建立 | 加入隨機或建立 | 加入隨機遊戲階段 |
獲取和更新遊戲階段資訊
Fusion提供了有關當前連線的Game Session
的大量資訊,如Name
和Region
。
這些資料可以透過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}]
:如果Session
在Lobby
上為Visible
,則發出訊號。使Session
不可見,是更改此屬性的事情。IsOpen [bool{get,set}]
:如果Session
是Open
加入,則發出訊號。要關閉或打開Sesion
,只需更改此屬性。PlayerCount [int{get}]
:Session
中當前的Players
數量。僅在大廳可用。MaxPlayers [int{get}]
:可以加入Session
的同儕節點的Max Number
,此值還包括Server/Host
同儕節點的槽。僅在大廳可用。
請記住,例如,Session
資訊和主要的Custom Properties
應僅用於對戰配對目的,比如 絕不能 與遊戲客戶端同步遊戲狀態資訊。我們強烈反對這種使用。
如果您需要在整個遊戲階段範圍內交換與遊戲遊玩相關的資訊,Fusion提供了很多選擇,比如擁有一個全域的NetworkObject
或使用RPC
來獲取一次性資料。
NetworkRunner
還提供了一些其他與Session
和Photon Cloud
相關的屬性,可以在遊戲中使用,比如:
NetworkRunner.IsCloudReady
:如果本機同儕節點連線到Photon Cloud
並且能夠建立/加入房間或加入大廳,則發出訊號。NetworkRunner.UserId
:在本機同儕節點通過身份驗證後,保存與本機同儕節點關聯的UserId
。此資訊來自您的應用程式使用的身份驗證服務。NetworkRunner.AuthenticationValues
:保存啟動Fusion時用於對本機同儕節點進行身份驗證的AuthenticationValues
的參照。NetworkRunner.CurrentConnectionType
:描述同儕節點使用的當前連線類型,是與遠端Server
的Direct
或Relayed
連線。請記住,在SharedMode
中,客戶端始終透過中繼連線。NetworkRunner.NATType
:當啟用NAT穿透系統時,Fusion將嘗試確定本機同儕節點正在運行的當前網路的當前NAT類型,此屬性會公開此資訊。NAT類型可以是:Invalid
、UdpBlocked
、OpenInternet
、FullCone
或Symmetric
。NetworkRunner.IsSharedModeMasterClient
:布林值旗標,描述本機同儕節點是否也是Shared Game Session
的Master Client
。這僅在SharedMode
中運行時有效,可用於根據其他客戶端和Master Client
之間的差異來確定應在哪個同儕節點執行哪些特定操作。
另一個相關欄位是NetworkRunner.LobbyInfo
,它公開了同儕節點連線到的當前Lobby
的資訊(請記住,同儕節點只能處於Lobby
、Game Session
或中斷連線)。這裡我們列出了LobbyInfo
的主要屬性:
IsValid [bool{get}]
:如果LobbyInfo
已準備好進行讀取/寫入,則發出訊號。Name [string{get}]
:包含當前大廳的名稱。
遊戲階段瀏覽器
遊戲階段瀏覽器在90年代和21世紀初很受歡迎。基本設計仍然有效,但如今他們的主要目的(找到合適的遊戲並快速加入)已被對戰配對所取代。
不鼓勵使用遊戲階段瀏覽器的原因有很多:
- 現時存在安全問題;
- 擷取活躍中遊戲階段清單對於伺服器來說是一項效能繁重的操作;
- 清單總是特定於玩家當前連線的區域; 並且,
- 最重要的是 這是一種過時的設計模式,如今有更好的UX選項可供選擇。
Fusion Matchmaking API提供的最新替代方案包括:
- 為了快速填充/加入遊戲階段:加入隨機開放的房間。
- 為了加入特定類型的比賽:加入一個隨機的開放房間,同時使用遊戲階段屬性進行篩選。
- 為了讓特定玩家加入並一起玩:建立邀請碼和/或按照名稱加入遊戲階段。
除非遊戲有大量運行的社群伺服器,而且這些伺服器有自己的自訂/修改模式和地圖,否則幾乎沒有理由要求提供完整的房間清單,並透過遊戲階段瀏覽器向使用者提供這些房間。
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 Session
的Custom 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
清單遵循的流程略有不同:
- 加入大廳:使用Fusion Runner參照,只需調用
NetworkRunner.JoinSessionLobby([SessionLobby], [string])
,以使同儕節點連線到Photon Cloud並加入特定的Lobby
。此方法接收兩個引數:SessionLobby
:可以是以下值之一:ClientServer
加入預設的ClientServer Lobby
;Shared
加入預設的Shared Lobby
;及,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
可以透過這種方式加入ClientServer
、Shared
或Custom
大廳。
舉個例子,下方顯示了伺服器/主機如何在自訂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}");
}
}
獲取遊戲階段清單:在使用Fusion時,API的主要入口點之一是
INetworkRunnerCallbacks
,這是Fusion用來展示一系列不同事件的特殊介面,包括Lobby
的遊戲階段清單。每次遊戲階段更改時,無論是建立/刪除遊戲階段還是更新遊戲階段屬性,都會叫用OnSessionListUpdated(NetworkRunner runner, List<SessionInfo> sessionList)
回調,並從大廳中獲取完整的遊戲階段清單。SessionInfo
與上述類型相同。然後可以顯示、篩選、排序清單等。加入遊戲階段:選擇要加入的
Session
後,可以使用常用的NetworkRunner.StartGame()
啟動Fusion。但在這種情況下,用於啟動客戶端的SessionName
必須是遊戲階段中的名稱。這樣,Client
將加入該特定的Game Session
。- 像往常一樣選擇正確的
GameMode
,因為同儕節點正在加入Session
,它必須是GameMode.Client
或GameMode.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