26. 内容支持Part 2: 匹配¶
它是自动在用户间选择Match对象的功能。
匹配分为 MatchmakingClient
和 MatchmakingServer
两部分。
MatchmakingClient
在让玩家参与和取消Match时使用。虽然它的名字里面有Client
,但它是服务器专用客户端,在从游戏服务器的角度请求匹配时使用,所以名字中带有Client
。MatchmakingServer
在处理匹配的服务器中使用,当玩家请求参与Match时,会对玩家是否符合参与条件等信息进行判断,让其参与Match或不让其参与Match。
Tip
可通过使``MatchmakingServer``仅在特定flavor下运行的方式来创建专门负责匹配的服务器。
26.1. Matchmaking Client¶
26.1.1. 接口¶
可利用MatchmakingClient类的 StartMatchmaking() 和 CancelMatchmaking() 开始或中断match。(C#为 Start()
和 Cancel()
)
同时,可利用 GetMatchmakingServerInfo() 掌握MatchmakingServer的状态。
class MatchmakingClient {
public:
typedef Uuid MatchId;
typedef int64_t Type;
// Match 에 참여한 플레이어의 정보입니다.
struct Player {
// 플레이어 식별자
string id;
// 플레이어 정보가 담긴 context로 Match 참여를 위한 StartMatchmaking() 함수를 호출할 때 전달합니다.
// 이 정보는 매치 완료 또는 ProgressCallback2를 통해 받은 Match의
// players 목록 안에서 확인할 수 있습니다.
Json context;
// 플레이어가 접속한 서버의 위치입니다.
Rpc::PeerId location;
};
// Match 에 대한 정보입니다.
struct Match {
explicit Match(Type _type);
explicit Match(const MatchId &_match_id, Type _type);
// Match 식별자
const MatchId match_id;
// 매치 타입입니다. Integer(enum) 값으로 개발사에서 임의로 지정하여
// 매치 타입을 구분지을 수 있습니다.
const Type type;
// 매치 타입에 매칭된 플레이어 list 입니다.
std::vector<Player> players;
// 해당 Match 만의 정보가 담긴 개별 context 입니다.
// 동일한 매치타입끼리 공유되진 않습니다.
// MatchmakingServer 의 join, leave callback 이 호출될 때
// 해당 callback 에서 팀을 편성하는 등의 정보를 자유롭게 입력할 수 있습니다.
Json context;
};
// MatchCallback 에 전달되는 매칭 요청 처리 결과입니다.
enum MatchResult {
kMRSuccess = 0,
kMRAlreadyRequested,
kMRTimeout,
kMRError = 1000
};
// CancelCallback 에 전달되는 매칭 요청 취소 처리 결과입니다.
enum CancelResult {
kCRSuccess = 0,
kCRNoRequest,
kCRError = 1000
};
// 매칭이 완료되면 호출될 함수입니다.
typedef function<void(const string & /*player_id*/,
const Match & /*match*/,
MatchResult /*result*/)> MatchCallback;
// 매칭의 진행상황이 변경되면 호출될 함수입니다.
// MatchmakingServer 의 `enable_match_progress_callback` 이 true 일 때만
// 유효합니다.
typedef function<void(const string & /*player_id*/,
const MatchId & /*match_id*/,
const string & /*player_id_joined*/,
const string & /*player_id_left*/)> ProgressCallback;
typedef function<void(const string & /*player_id*/,
const Match & /*match*/,
const string & /*player_id_joined_or_updated*/,
const string & /*player_id_left*/)> ProgressCallback2;
// 매칭 요청을 취소한 후 호출될 함수입니다.
typedef function<void(const string & /*player_id*/,
CancelResult /*result*/)> CancelCallback;
// 매치메이킹 서버를 선택하는 기준입니다.
enum TargetServerSelection {
kRandom = 0, // 무작위로 선택합니다.
kMostNumberOfPlayers, // 매치메이킹 요청이 많은 서버를 선택합니다.
// MatchmakingServer MANIFEST 의
// concurrent_number_of_players_threshold 를
// 초과한 서버는 최대한 배제합니다.
kLeastNumberOfPlayers // 매치메이킹 요청이 적은 서버를 선택합니다.
};
// 매치메이킹 서버의 정보입니다.
struct MatchmakingServerInfo {
Rpc::PeerId peer_id; // Peer ID 입니다.
size_t player_count; // 현재 매치메이킹 중인 플레이어 수입니다.
bool want_more_players; // player_count 값이 MatchmakingServer MANIFEST 의
// concurrent_number_of_players_threshold 를
// 초과하면 false 입니다.
};
typedef std::vector<MatchmakingServerInfo> MatchmakingServerInfoVector;
static const WallClock::Duration kNullTimeout;
static const ProgressCallback kNullProgressCallback;
// 매치메이킹 서버의 정보를 가져옵니다.
static MatchmakingServerInfoVector GetMatchmakingServerInfo();
// Match 상대를 찾는 요청을 보냅니다.
// target_server 인자로 전달된 기준으로 매치메이킹 서버를 선택합니다.
static void StartMatchmaking(const Type &type, const string &player_id,
const Json &player_context, const MatchCallback &match_callback,
const TargetServerSelection &target_server = kRandom,
const ProgressCallback &progress_callback = kNullProgressCallback,
const WallClock::Duration &timeout = kNullTimeout);
// Match 상대를 찾는 요청을 보냅니다.
// target_server 인자로 전달된 매치메이킹 서버로 요청합니다.
static void StartMatchmaking(const Type &type, const string &player_id,
const Json &player_context, const MatchCallback &match_callback,
const Rpc::PeerId &target_server,
const ProgressCallback &progress_callback = kNullProgressCallback,
const WallClock::Duration &timeout = kNullTimeout);
// Match 상대를 찾는 요청을 보냅니다.
// target_server 인자로 전달된 기준으로 매치메이킹 서버를 선택합니다.
static void StartMatchmaking2(const Type &type, const string &player_id,
const Json &player_context, const MatchCallback &match_callback,
const TargetServerSelection &target_server = kRandom,
const ProgressCallback2 &progress_callback = kNullProgressCallback2,
const WallClock::Duration &timeout = kNullTimeout);
// Match 상대를 찾는 요청을 보냅니다.
// target_server 인자로 전달된 매치메이킹 서버로 요청합니다.
static void StartMatchmaking2(const Type &type, const string &player_id,
const Json &player_context, const MatchCallback &match_callback,
const Rpc::PeerId &target_server,
const ProgressCallback2 &progress_callback = kNullProgressCallback2,
const WallClock::Duration &timeout = kNullTimeout);
// StartMatchmaking() 요청을 취소합니다.
static void CancelMatchmaking(const Type &type, const string &player_id,
const CancelCallback &cancel_callback);
};
using funapi;
namespace MatchMaking
{
// Match 에 참여한 플레이어의 정보입니다.
public struct Player
{
// 플레이어 식별자
public string Id;
// 플레이어 정보가 담긴 context 입니다.
// Match 참여를 요청할 때 입력한 context 를
// StartMatchmaking() 함수를 호출할 때 전달합니다.
public JObject Context;
// 플레이어가 접속한 서버의 위치(Rpc의 PeerId)입니다.
public System.Guid Location;
}
// Match 에 대한 정보입니다.
public struct Match
{
// Match 식별자
public System.Guid MatchId;
// 매치 타입입니다. Integer 값으로 개발사에서 임의로 지정하여
// 매치 타입을 구분지을 수 있습니다.
public Int64 MatchType;
// 매치 타입에 매칭된 플레이어 list 입니다.
public ReadOnlyCollection<Player> Players;
// 해당 Match 만의 정보가 담긴 개별 context 입니다.
// 동일한 매치타입끼리 공유되진 않습니다.
// MatchmakingServer 의 join, leave callback 이 호출될 때
// 해당 callback 에서 팀을 편성하는 등의 정보를 자유롭게 입력할 수 있습니다.
public JObject Context;
}
// MatchCallback 에 전달되는 매칭 요청 처리 결과입니다.
public enum MatchResult {
kSuccess = 0,
kAlreadyRequested,
kTimeout,
kError = 1000
};
// CancelCallback 에 전달되는 매칭 요청 취소 처리 결과입니다.
public enum CancelResult {
kSuccess = 0,
kNoRequest,
kError = 1000
};
public static class Client
{
// 매칭이 완료되면 호출될 함수입니다.
public delegate void MatchCallback(string player_id,
Match match,
MatchResult result);
// 매칭의 진행상황이 변경되면 호출될 함수입니다.
// MatchmakingServer 의 `enable_match_progress_callback` 이 true 일 때만
// 유효합니다.
public delegate void ProgressCallback(string player_id,
System.Guid match_id,
string player_id_joined,
string player_id_left);
public delegate void ProgressCallback2(string player_id,
Match match,
string player_id_joined_or_updated,
string player_id_left);
// 매칭 요청을 취소한 후 호출될 함수입니다.
public delegate void CancelCallback(string player_id,
CancelResult result);
// 매치메이킹 서버를 선택하는 기준입니다.
public enum TargetServerSelection {
kRandom = 0, // 무작위로 선택합니다.
kMostNumberOfPlayers, // 매치메이킹 요청이 많은 서버를 선택합니다.
// MatchmakingServer MANIFEST 의
// concurrent_number_of_players_threshold 를
// 초과한 서버는 최대한 배제합니다.
kLeastNumberOfPlayers // 매치메이킹 요청이 적은 서버를 선택합니다.
};
public struct MatchmakingServerInfo {
public System.Guid peer_id; // Peer ID 입니다.
public ulong player_count; // 현재 매치메이킹 중인 플레이어 수입니다.
public bool want_more_players; // player_count 값이 MatchmakingServer MANIFEST 의
// concurrent_number_of_players_threshold 를
// 초과하면 false 입니다.
};
// 매치메이킹 서버의 정보를 가져옵니다.
public static ReadOnlyCollection<MatchmakingServerInfo> GetMatchmakingServerInfo ();
// Match 상대를 찾는 요청을 보냅니다.
// target_server 인자로 전달된 기준으로 매치메이킹 서버를 선택합니다.
public static void Start (Int64 type,
string player_id,
JObject player_context,
MatchCallback match_callback,
TargetServerSelection target_server = TargetServerSelection.kRandom,
ProgressCallback progress_callback = null,
TimeSpan timeout = default(TimeSpan))
// Match 상대를 찾는 요청을 보냅니다.
// target_server 인자로 전달된 매치메이킹 서버로 요청합니다.
public static void Start (Int64 type,
string player_id,
JObject player_context,
MatchCallback match_callback,
System.Guid target_server,
ProgressCallback progress_callback = null,
TimeSpan timeout = default(TimeSpan))
// Match 상대를 찾는 요청을 보냅니다.
// target_server 인자로 전달된 기준으로 매치메이킹 서버를 선택합니다.
public static void Start2 (Int64 type,
string player_id,
JObject player_context,
MatchCallback match_callback,
TargetServerSelection target_server = TargetServerSelection.kRandom,
ProgressCallback2 progress_callback = null,
TimeSpan timeout = default(TimeSpan))
// Match 상대를 찾는 요청을 보냅니다.
// target_server 인자로 전달된 매치메이킹 서버로 요청합니다.
public static void Start2 (Int64 type,
string player_id,
JObject player_context,
MatchCallback match_callback,
System.Guid target_server,
ProgressCallback2 progress_callback = null,
TimeSpan timeout = default(TimeSpan))
// StartMatchmaking() 요청을 취소합니다.
public static void Cancel (Int64 type,
string player_id,
CancelCallback cancel_callback);
}
}
Note
ProgressCallback2
인자는 1.0.0-2768 Experimental 버전 이상에서만
사용 가능합니다.
26.1.1.1. 开始匹配¶
发送搜索Match对象的请求。
class MatchmakingClient {
public:
static void StartMatchmaking(const Type &type, const string &player_id,
const Json &player_context, const MatchCallback &match_callback,
const TargetServerSelection &target_server = kRandom,
const ProgressCallback &progress_callback = kNullProgressCallback,
const WallClock::Duration &timeout = kNullTimeout);
static void StartMatchmaking(const Type &type, const string &player_id,
const Json &player_context, const MatchCallback &match_callback,
const Rpc::PeerId &target_server,
const ProgressCallback &progress_callback = kNullProgressCallback,
const WallClock::Duration &timeout = kNullTimeout);
static void StartMatchmaking2(const Type &type, const string &player_id,
const Json &player_context, const MatchCallback &match_callback,
const TargetServerSelection &target_server = kRandom,
const ProgressCallback2 &progress_callback = kNullProgressCallback2,
const WallClock::Duration &timeout = kNullTimeout);
static void StartMatchmaking2(const Type &type, const string &player_id,
const Json &player_context, const MatchCallback &match_callback,
const Rpc::PeerId &target_server,
const ProgressCallback2 &progress_callback = kNullProgressCallback2,
const WallClock::Duration &timeout = kNullTimeout);
}
namespace matchmaking
{
public static class Client
{
public static void Start (Int64 type,
string player_id,
JObject player_context,
MatchCallback match_callback,
TargetServerSelection target_server = TargetServerSelection.kRandom,
ProgressCallback progress_callback = null,
TimeSpan timeout = default(TimeSpan))
public static void Start (Int64 type,
string player_id,
JObject player_context,
MatchCallback match_callback,
System.Guid target_server,
ProgressCallback progress_callback = null,
TimeSpan timeout = default(TimeSpan))
public static void Start2 (Int64 type,
string player_id,
JObject player_context,
MatchCallback match_callback,
TargetServerSelection target_server = TargetServerSelection.kRandom,
ProgressCallback2 progress_callback = null,
TimeSpan timeout = default(TimeSpan))
public static void Start2 (Int64 type,
string player_id,
JObject player_context,
MatchCallback match_callback,
System.Guid target_server,
ProgressCallback2 progress_callback = null,
TimeSpan timeout = default(TimeSpan))
}
}
type : 可通过表示match种类的integer值进行任意定义,该值用于在
MatchmakingServer
中区分相同的match种类。player_id : 识别Match参与者的标识符。
player_context : 在MatchmakingServer中判断参与者实力时所使用的值,根据match标准,自由地以JSON 格式构建必要参数即可。
Important
但top level的
request_time
、elapsed_time
attribute则不能使用。match_callback : 匹配结束后调用的函数。
target_server : 选择Matchmaking Server。
Note
可通过 enum TargetServerSelection 自动选择,或直接传输`PeerId`。
progress_callback : 匹配进行情况变更时所调用的函数。若传输默认值`kNullProgrssCallback`,则忽略。
Note
仅在
MatchmakingServer
的 enable_match_progress_callback 为true时有效。
timeout : 若指定的时间内Match未成功,则自动取消,以
kMRTimeout
值作为result调用match_callback
。若传输默认值 kNullTimeout ,则忽略。
26.1.1.2. 取消匹配¶
取消匹配请求。
class MatchmakingClient {
public:
static void CancelMatchmaking(const Type &type, const string &player_id,
const CancelCallback &cancel_callback);
}
namespace matchmaking
{
public static class Client
{
public static void Cancel (Int64 type,
string player_id,
CancelCallback cancel_callback);
}
}
type : 与在
StartMatchmaking()
(C#为Client.Start()
)函数中输入的type
相同。player_id : 与在
StartMatchmaking()
函数中输入的player_id
相同。callback : 取消匹配请求后所调用的函数。
Note
如已完成匹配并对已调用MatchCallback的玩家调用了取消函数时,则callback中将传输
kCRNoRequest
result。
26.1.1.3. 매치 진행 상태 확인¶
진행 상태 콜백을 통해 매치에 참가하거나 매치를 떠난 플레이어를 확인할 수 있습니다.
// 매칭의 진행상황이 변경되면 호출될 함수입니다.
// MatchmakingServer 의 `enable_match_progress_callback` 이 true 일 때만
// 유효합니다.
typedef function<void(const string & /*player_id*/,
const MatchId & /*match_id*/,
const string & /*player_id_joined*/,
const string & /*player_id_left*/)> ProgressCallback;
typedef function<void(const string & /*player_id*/,
const Match & /*match*/,
const string & /*player_id_joined_or_updated*/,
const string & /*player_id_left*/)> ProgressCallback2;
// 매칭의 진행상황이 변경되면 호출될 함수입니다.
// MatchmakingServer 의 `enable_match_progress_callback` 이 true 일 때만
// 유효합니다.
public delegate void ProgressCallback(string player_id,
System.Guid match_id,
string player_id_joined,
string player_id_left);
public delegate void ProgressCallback2(string player_id,
Match match,
string player_id_joined_or_updated,
string player_id_left);
26.1.1.4. 플레이어 컨텍스트 확인¶
매치 플레이어 컨텍스트 정보는 매치가 완료되거나 아래에서 설명할 상태 변경 콜백 호출 시 Match 정보를 통해 받을 수 있습니다. 각 플레이어의 context는 Match 안에 포함된 players 목록을 통해 볼 수 있습니다.
// Match 에 참여한 플레이어의 정보입니다.
struct Player {
...
// 플레이어 정보가 담긴 context로 Match 참여를 위한 StartMatchmaking() 함수를 호출할 때 전달합니다.
// 이 정보는 매치 완료 또는 ProgressCallback2를 통해 받은 Match의
// players 목록 안에서 확인할 수 있습니다.
Json context;
...
}
...
// Match 에 대한 정보입니다.
struct Match {
...
// 매치 타입에 매칭된 플레이어 list 입니다.
std::vector<Player> players;
...
}
// Match 에 참여한 플레이어의 정보입니다.
public struct Player
{
...
// 플레이어 정보가 담긴 context 입니다.
// Match 참여를 요청할 때 입력한 context 를
// StartMatchmaking() 함수를 호출할 때 전달합니다.
public JObject Context;
...
}
// Match 에 대한 정보입니다.
public struct Match
{
...
// 매치 타입에 매칭된 플레이어 list 입니다.
public ReadOnlyCollection<Player> Players;
...
}
context 형태는 다음과 같이 정의됩니다.
ex) StartMatchmaking 시 {"level":22} 를 context에 넣었을 경우
{
"request_time":28445593765, // 매치 요청 시간(단위: 타임스탬프)
"elapsed_time":0, // 매치 요청 후 지난 시간(단위: 초)
"user_data":
{
"level":22
}
}
ex) StartMatchmaking 시 {"level":22} 를 context에 넣었을 경우
{
"request_time":28445593765, // 매치 요청 시간(단위: 타임스탬프)
"elapsed_time":0, // 매치 요청 후 지난 시간(단위: 초)
"user_data":
{
"level":22
}
}
26.1.1.5. 매치 진행 상태 변경¶
매치메이킹 클라이언트에서는 매치 대기 중인 플레이어의 정보(context)를 변경할 수 있습니다. 이 때 변경된 플레이어의 정보는 ProgressCallback2의 인자(player_id_joined_or_updated)와 Match 안에 포함된 players 목록을 통해 볼 수 있으며 최종적으로는 매치 완료 시점에서 Match 안에 포함된 players를 확인할 수 있습니다.
static void UpdateMatchPlayerContext(
const Type &type, const string &player_id, const Json &new_context);
지원 예정입니다.
Note
1.0.0-2768 Experimental 버전 이상에서만 사용 가능합니다.
26.1.2. 设置MANIFEST.json¶
只要MANIFEST.json中存在如下所示``MatchmakingClient``组件即可。
1 2 3 4 | ...
"MatchmakingClient": {
},
...
|
26.2. Matchmaking Server¶
26.2.1. 接口¶
可利用MatchmakingServer类的 Start() 开启匹配服务器。在服务器的Install()函数中调用。
class MatchmakingServer {
public:
typedef MatchmakingClient::MatchId MatchId;
typedef MatchmakingClient::Type Type;
typedef MatchmakingClient::Player Player;
typedef MatchmakingClient::Match Match;
typedef MatchmakingClient::MatchResult MatchResult;
typedef MatchmakingClient::CancelResult CancelResult;
// Match 상태를 나타내며, JoinCallback 에서 매칭 완료인지 아닌지를
// 결정할 때 사용합니다.
enum MatchState {
// 해당 Match 에서 요구하는 플레이어가 모이지 않았을 때 사용합니다.
kMatchNeedMorePlayer = 0,
// 해당 Match 에서 요구하는 플레이어가 모두 모였을 때 사용합니다.
kMatchComplete
};
// 플레이어가 매치 요청을 보내면 호출됩니다.
// 이 함수가 호출되면 매치 요청한 플레이어가 참여 조건 만족하는지 확인합니다.
// 매치 요청을 한 플레이어가 2명 이상일 경우에만 호출됩니다.
typedef function<bool(const Player & /*player*/,
const Match & /*match*/)> MatchChecker;
// 매치가 완료되었는지 확인할 때 호출됩니다.
typedef function<MatchState(const Match & /*match*/)> CompletionChecker;
// 매치에 유저가 참여했을 때(MatchChecker callback 에서 참여 조건에 만족하여
// true 가 리턴되면) 호출됩니다. Json 변수인 match.context 에 팀 편성등의
// 정보를 저장할 때 이용합니다.
typedef function<void(const Player & /*player*/,
Match * /*match*/)> JoinCallback;
// 매치에서 유저가 나갔을 때 호출됩니다. CancelMatchmaking() 함수를
// 호출하거나 MatchmakingServer 의 enable_dynamic_match 가 true 일 때 임의로
// 호출됩니다.
typedef function<void(const Player & /*player*/,
Match * /*match*/)> LeaveCallback;
// 매치메이킹 처리를 위한 함수입니다.
static void Start(const MatchChecker &match_checker,
const CompletionChecker &completion_checker,
const JoinCallback &join_cb, const LeaveCallback &leave_cb);
};
namespace matchmaking
{
public static class Server
{
// Match 상태를 나타내며, JoinCallback 에서 매칭 완료인지 아닌지를
// 결정할 때 사용합니다.
public enum MatchState
{
// 해당 Match 에서 요구하는 플레이어가 모이지 않았을 때 사용합니다.
kMatchNeedMorePlayer = 0,
// 해당 Match 에서 요구하는 플레이어가 모두 모였을 때 사용합니다.
kMatchComplete
};
// 플레이어가 매치 요청을 보내면 호출됩니다.
// 이 함수가 호출되면 매치 요청한 플레이어가 참여 조건 만족하는지 확인합니다.
// 매치 요청을 한 플레이어가 2명 이상일 경우에만 호출됩니다.
public delegate bool MatchChecker (Player player, Match match);
// 매치가 완료되었는지 확인할 때 호출됩니다.
public delegate MatchState CompletionChecker (Match match);
// 매치에 유저가 참여했을 때(MatchChecker callback 에서 참여 조건에 만족하여
// true 가 리턴되면) 호출됩니다. Json 변수인 match.Context 에 팀 편성등의
// 정보를 저장할 때 이용합니다.
public delegate void JoinCallback (Player player, Match match);
// 매치에서 유저가 나갔을 때 호출됩니다. CancelMatchmaking() 함수를
// 호출하거나 MatchmakingServer 의 enable_dynamic_match 가 true 일 때 임의로
// 호출됩니다.
public delegate void LeaveCallback (Player player, Match match);
// 매치메이킹 처리를 위한 함수입니다.
public static void Start (MatchChecker match_checker,
CompletionChecker completion_checker,
JoinCallback join_callback,
LeaveCallback leave_callback);
}
}
26.2.1.1. 开启服务器¶
在游戏服务器的Install()中调用 Start() 。
class MatchmakingServer {
public:
static void Start(const MatchChecker &match_checker,
const CompletionChecker &completion_checker,
const JoinCallback &join_cb, const LeaveCallback &leave_cb);
}
namespace matchmaking
{
public static class Server
{
public static void Start (MatchChecker match_checker,
CompletionChecker completion_checker,
JoinCallback join_callback,
LeaveCallback leave_callback);
}
}
match_checker
须要确认Match条件时调用。
判断参与条件(等级、金币持有量等),根据可Match与否,返回
true
或false
即可。Return: 相关玩家可以参与match时,为true。
Argument:
player
: 想要并入match的玩家Argument:
match
: 确认是否可以参与的match instance
Note
player
和match.players
的context
变量中追加包含了名为elapsed_time
的attribute,并保存了相关玩家未能匹配的时间。请参考 Matchmaking示例 。completion_checker
通过match_checker ,player并入match后调用。
它是判断match是否已经完成的函数。
Return: 若
match
未完成,为kMatchNeedMorePlayer
;若完成,则为kMatchComplete
。Argument: 确认
match
是否已经完成的match instance
join_callback
通过match_checker ,player并入match后调用。
Argument:
player
: 已并入match的玩家Argument:
match
: match instance
Note
player
和match.players
的context
变量中追加包含了名为elapsed_time
的attribute,并保存了相关玩家未能匹配的时间。具体说明请参考 示例 。leave_callback
在已并入match中的player退出match时调用。 同时,在Matchmaking client端调用
CancelMatchmaking()
函数时也会被调用,由于在 Matchmaking server端MANIFEST.json的enable_dynamic_match
为true,所以可以组合两个不同的match时也会调用。取消已在join_callback进行的建组任务。
Argument:
player
: 取消匹配的玩家Argument:
match
: 错过玩家的match实例的Pointer
Important
由于通过已在匹配服务器中注册的回调函数传输的argument是通过iFun引擎进行管理的值,所以不可修改。
Match参数仅可在 join_callback 和 leave_callback 中修改。
Note
若 MANIFEST.json
中 MatchmakingServer
的 enable_dynamic_match
为ture,则上述函数可经常被调用。这是因为为了更快地进行匹配,iFun引擎经历了将两个不同的match合在一起的过程。
26.2.2. 设置MANIFEST.json¶
按如下所示,为MANIFEST添加 MatchmakingServer
组件。
...
"MatchmakingServer": {
"enable_dynamic_match": true,
"enable_match_progress_callback": false,
"concurrent_number_of_players_threshold": 3000
},
...
enable_dynamic_match: 若为ture,MatchmakingServer会针对未完成的match,通过将不同的match合在一起的方式重新搜索match对象。
enable_match_progress_callback: 若为true,则Matchmaking client作为
StartMatchmaking()
参数传输的progress_cb会定期被调用,据此可查看匹配的进行情况。concurrent_number_of_players_threshold: 指定可一次性处理的匹配请求数。如服务器在处理匹配时,也一同处理该数值,则即使在Mathmaking client中通过
StartMatchmaking()
的target_server参数指定了kMostNumberOfPlayers
,相应服务器也不会被选择。(但是,当所有服务器均超出该值时,将必须被选择。)
Important
不可同时使用 enable_dynamic_match
和 enable_match_progress_callback
。
26.3. Matchmaking示例¶
26.3.1. 示例1: 考虑到等级差异的2:2匹配¶
下面是玩家的等级差异为5以内时,2:2匹配的示例。 超过7秒以上时,将忽略等级差异,直接匹配。
26.3.1.1. Matchmaking client¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | using funapi;
using funapi.Matchmaking;
class MatchExample
{
enum MatchType
{
kMatch1Vs1 = 0,
kMatch2Vs2
};
static bool enable_match_timeout = true;
static bool enable_match_progress = true;
// 2:2 매치를 요청하는 클라이언트 패킷 핸들러입니다.
static void OnMatchMakingRequested(Session session, JObject message)
{
string player_id = ...;
JObject context = new JObject();
context["LEVEL"] = ...;
TimeSpan timeout = TimeSpan.Zero;
if (enable_match_timeout) {
timeout = WallClock.FromMsec (10 * 1000);
}
// 매치의 진행상황을 받기 위해 아래처럼 콜백을 등록합니다.
// 콜백은 MatchmakingServer 의 enable_match_progress_callback 이
// true 일 때만 호출됩니다.
Client.ProgressCallback progress_cb = null;
if (enable_match_progress) {
progress_cb = (string player_id2,
Guid match_id,
string player_id_joined,
string player_id_left,
JObject match_context) => {
// 1. player_id_joined.empty() 가 아니라면 player_id 에 해당하는 유저가
// player_id_joined 에 해당하는 유저와 예비 매칭 되었을 때 불립니다.
// 2. player_id_left.empty() 가 아니라면 player_id 에 해당하는 유저와
// player_id_left 에 해당하는 유저의 예비 매칭이 취소 되었을 때
// 불립니다.
});
}
// matchmaking 을 요청합니다.
// 매치가 성사되면 OnMatched 함수가 호출됩니다.
Client.Start ((long) MatchType.kMatch2Vs2,
player_id,
context,
OnMatched,
Client.TargetServerSelection.kMostNumberOfPlayers,
progress_cb,
timeout);
// 클라이언트에 응답을 보내는 작업 등의 후속처리를 합니다.
}
// 매치 취소를 요청하는 클라이언트 패킷 핸들러입니다.
static void OnCancelRequested(Session session, JObject message)
{
string player_id = "player_id";
// matchmaking 취소를 요청합니다.
// matchmaking 취소 처리가 완료되면 OnCancelled 함수가 호출됩니다.
Client.Cancel((long) MatchType.kMatch2Vs2, player_id, OnCancelled);
}
static void OnMatched(string player_id, Match match, MatchResult result)
{
if (result == MatchResult.kAlreadyRequested)
{
return;
} else if (result == MatchResult.kTimeout) {
return;
} else if (result == MatchResult.kError) {
return;
}
Log.Assert (result == MatchResult.kSuccess);
// 매치가 성사 되었습니다.
// match.players 에는 match 에 참여한 player 들이 포함되어 있으나,
// matchmaking server 에서 match.context 에 아래와 같이 플레이어를
// 팀으로 구분해 두었습니다.
Guid match_id = match.MatchId;
string team_a_player1 = (string) match.Context ["TEAM_A"] [0];
string team_a_player2 = (string) match.Context ["TEAM_A"] [1];
string team_b_player1 = (string) match.Context ["TEAM_B"] [0];
string team_b_player2 = (string) match.Context ["TEAM_B"] [1];
// match id 또는 A, B 팀의 각 player id 를 이용하여 대전을 시작하도록 합니다.
// 클라이언트에 응답을 보내는 작업 등의 후속처리를 합니다.
// ...
}
static void OnCancelled(string player_id, CancelResult result)
{
if (result == CancelResult.kNoRequest) {
return;
} else if (result == CancelResult.kError) {
return;
}
Log.Assert (result == CancelResult.kSuccess);
// 클라이언트에 응답을 보내는 작업 등의 후속처리를 합니다.
...
}
};
|
26.3.1.2. Matchmaking server¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | // 매치메이킹 클라이언트 예제에서 살펴본 MatchType 과 동일합니다.
enum MatchType {
kMatch1Vs1 = 0,
kMatch2Vs2
};
static bool MyServer::Install(const ArgumentMap &arguments) {
...
// MatchmakingServer 역할을 하는 서버에서 Start 함수를 호출하여
// MatchmakingServer 를 시작합니다. 다음 4 개의 함수를 인자로 전달합니다.
MatchmakingServer::Start(CheckMatch, CheckCompletion, OnJoined, OnLeft);
...
}
// player 가 match 에 참여해도 되는지 검사합니다.
bool CheckMatch(const MatchmakingServer::Player &player,
const MatchmakingServer::Match &match) {
if (match.type == kMatch1Vs1) {
...
} else if (match.type == kMatch2Vs2) {
// 이 match 에 참여한 player 들 중에 match 를 찾기 시작한지
// 가장 오래된 유저의 경과 시간이 7 초가 넘었으면
// 레벨 차이를 무시하고 match 에 합류한다고 가정하겠습니다.
// context 의 elapsed_time 값으로 경과시간을 알 수 있습니다.
int64_t max_elapsed_time_in_sec =
player.context["elapsed_time"].GetInteger();
for (size_t i = 0; i < match.players.size(); ++i) {
max_elapsed_time_in_sec =
std::max(elapsed_time_in_sec,
match.players[i].context["elapsed_time"].GetInteger());
}
if (max_elapsed_time_in_sec > 7) {
// 7 초가 넘었다면 레벨 차이를 무시하고 match 에 합류시킵니다.
return true;
}
// MatchmakingClient::StartMatchmaking() 함수를 호출할 때 전달했던 context 로부터
// match 에 참여하려는 플레이어의 레벨을 가져옵니다.
int64_t player_level = player.context["LEVEL"].GetInteger();
// match 에 참여한 플레이어들의 레벨을 가져옵니다.
for (size_t i = 0; i < match.players.size(); ++i) {
int64_t member_level = match.players[i].context["LEVEL"].GetInteger();
// match 에 참여하려는 플레이어의 레벨이
// match 에 참여한 플레이어들과의 레벨 차이가 5보다 크다면
// match 에 합류시키지 않습니다.
if (abs(player_level - member_level) > 5) {
return false;
}
}
// 모든 조건에 만족하여 플레이어를 match 에 합류시킵니다.
return true;
}
}
// JoinMatch 함수가 불린 후 호출됩니다. 해당 매치가 완료되었는지 판단합니다.
MatchmakingServer::MatchState CheckCompletion(
const MatchmakingServer::Match &match) {
if (match.type == kMatch1Vs1) {
...
} else if (match.type == kMatch2Vs2) {
if (match.players.size() == 4) {
return MatchmakingServer::kMatchComplete;
}
// 위의 CheckMatch 함수와 마찬가지로 match.players[i].context 의
// elapsed_time 을 읽어 일정 시간이 지났다면 두 명만 있더라도 AI 를
// 포함하여 대전을 진행시키는 등의 처리를 할 수 있습니다.
}
return MatchmakingServer::kMatchNeedMorePlayer;
}
// 조건을 만족하여 CheckMatch 함수가 true 를 반환하면 이 함수가 호출됩니다.
// 이제 플레이어는 match 에 참여하게 되었습니다.
// 여기서 팀을 구분해 줍니다. (Json 타입인 match->context 에 자유롭게
// match 의 context 를 저장할 수 있습니다.)
void OnJoined(const MatchmakingServer::Player &player,
MatchmakingServer::Match *match) {
if (match->type == kMatch1Vs1) {
...
} else if (match->type == kMatch2Vs2) {
// 만약 match 에 대한 context 가 만들어지지 않았다면
// 초기 설정을 진행합니다.
if (match->context.IsNull()) {
match->context.SetObject();
match->context["TEAM_A"].SetArray();
match->context["TEAM_B"].SetArray();
}
// match->context 에 구성되어 지는 팀 정보를 토대로
// 인원수가 적은 팀으로 참가시킵니다.
if (match->context["TEAM_A"].Size() < match->context["TEAM_B"].Size()) {
match->context["TEAM_A"].PushBack(player.id);
} else {
match->context["TEAM_B"].PushBack(player.id);
}
}
}
// Match 에 참여해 있던 player 가 MatchmakingClient::CancelMatchmaking() 을
// 요청했습니다.
// match->context 의 팀 정보에서 해당 player 를 삭제합니다.
void OnLeft(const MatchmakingServer::Player &player,
MatchmakingServer::Match *match) {
if (match->type == kMatch1Vs1) {
...
} else if (match->type == kMatch2Vs2) {
// OnJoined() 에서 match->context 에 입력했던 팀 정보에서 삭제합니다.
std::vector<Json *> teams;
teams.push_back(&(match->context["TEAM_A"]));
teams.push_back(&(match->context["TEAM_B"]));
// 매치 취소를 요청한 플레이어 id 와 동일하면
// member list 에서 제거하여 팀 정보에서 플레이어를 삭제합니다.
BOOST_FOREACH(Json *member_list, teams) {
Json::ValueIterator itr = member_list->Begin();
while (itr != member_list->End()) {
if (player.id == itr->GetString()) {
member_list->RemoveElement(itr);
return;
}
++itr;
}
}
}
// 이 함수 호출이 완료되면 MatchmakingClient::CancelMatchmaking() 함수에
// 전달한 CancelCallback 이 호출됩니다.
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | //////////////////////////////////////////////////////////////////////////////
// game server, matchmaking server 공통
//////////////////////////////////////////////////////////////////////////////
// 매치메이킹 클라이언트 예제에서 살펴본 MatchType 과 동일합니다.
enum MatchType {
kMatch1Vs1 = 0,
kMatch2Vs2
};
//////////////////////////////////////////////////////////////////////////////
// matchmaking server
//////////////////////////////////////////////////////////////////////////////
public static void Install(ArgumentMap arguments)
{
...
// MatchmakingServer 역할을 하는 서버에서 Start 함수를 호출하여
// MatchmakingServer 를 시작합니다. 다음 4 개의 함수를 인자로 전달합니다.
funapi.Matchmaking.Server.Start (CheckMatch, CheckCompletion,
OnJoined, OnLeft);
...
}
public static bool CheckMatch(Player player, Match match)
{
if (match.MatchType.Equals(MatchType.kMatch1Vs1)) {
// 별도 설명을 생략합니다. kMatch2Vs2 를 참고하시기 바랍니다.
...
} else if (match.MatchType.Equals(MatchType.kMatch2Vs2)) {
// 이 match 에 참여한 player 들 중에 match 를 찾기 시작한지
// 가장 오래된 유저의 경과 시간이 7 초가 넘었으면
// 레벨 차이를 무시하고 match 에 합류한다고 가정하겠습니다.
// context 의 elapsed_time 값으로 경과시간을 알 수 있습니다.
long max_elapsed_time_in_sec =
(long)player.Context ["elapsed_time"];
foreach (Player other in match.Players) {
max_elapsed_time_in_sec = System.Math.Max (
max_elapsed_time_in_sec, (long)other.Context ["elapsed_time"]);
}
if (max_elapsed_time_in_sec > 7) {
// 7 초가 넘었다면 레벨 차이를 무시하고 match 에 합류시킵니다.
return true;
}
// match 의 다른 player 들과 5 레벨 이상 차이나면
// match 에 참여하지 않는다고 가정하겠습니다.
// match 에 참여하려는 플레이어의 레벨을 가져옵니다.
// 레벨은 MatchmakingClient.Start() 함수를 호출할 때
// 전달했던 context 에 있습니다.
long player_level = (long)player.Context ["LEVEL"];
// match 에 참여한 플레이어들의 레벨을 가져옵니다.
foreach (Player other in match.Players) {
long member_level = (long)other.Context ["LEVEL"];
// match 에 참여하려는 플레이어의 레벨이
// match 에 참여한 플레이어들과의 레벨 차이가 5보다 크다면
// match 에 합류시키지 않습니다.
if (System.Math.Abs (player_level - member_level) > 5) {
return false;
}
}
// 필요하다면 다른 조건도 검사합니다.
// 모든 조건에 만족하여 플레이어를 match 에 합류시킵니다.
return true;
}
return false;
}
public static funapi.Matchmaking.Server.MatchState CheckCompletion(
Match match)
{
if (match.MatchType.Equals(MatchType.kMatch1Vs1)) {
// 별도 설명을 생략합니다. kMatch2Vs2 를 참고하시기 바랍니다.
...
} else if (match.MatchType.Equals(MatchType.kMatch2Vs2)) {
if (match.Players.Count == 4) {
// 4 명이 모두 모였습니다.
return funapi.Matchmaking.Server.MatchState.kMatchComplete;
}
// 위의 CheckMatch 함수와 마찬가지로 match.players[i].context 의
// elapsed_time 을 읽어 일정 시간이 지났다면 두 명만 있더라도 AI 를
// 포함하여 대전을 진행시키는 등의 처리를 할 수 있습니다.
}
return funapi.Matchmaking.Server.MatchState.kMatchNeedMorePlayer;
}
public static void OnJoined(Player player, Match match)
{
// CheckMatch 함수에서와 마찬가지로 여기서도 kMatch2Vs2 만 처리하겠습니다.
if (match.MatchType.Equals(MatchType.kMatch1Vs1)) {
// 별도 설명을 생략합니다. kMatch2Vs2 를 참고하시기 바랍니다.
...
} else if (match.MatchType.Equals(MatchType.kMatch2Vs2)) {
if (match.Context["TEAM_A"] == null &&
match.Context["TEAM_B"] == null) {
match.Context["TEAM_A"] = new JArray();
match.Context["TEAM_B"] = new JArray();
}
// match.Context 에 구성되어 지는 팀 정보를 토대로
// 인원수가 적은 팀으로 참가시킵니다.
JArray team_a_arr = (JArray) match.Context ["TEAM_A"];
JArray team_b_arr = (JArray) match.Context ["TEAM_B"];
if (team_a_arr.Count < team_b_arr.Count) {
team_a_arr.Add (player.Id);
} else {
team_b_arr.Add (player.Id);
}
}
}
public static void OnLeft(Player player, Match match)
{
// OnJoined 함수에서와 마찬가지로 여기서도 kMatch2Vs2 만 처리하겠습니다.
if (match.MatchType.Equals(MatchType.kMatch1Vs1)) {
// 별도 설명을 생략합니다. kMatch2Vs2 를 참고하시기 바랍니다.
...
} else if (match.MatchType.Equals(MatchType.kMatch2Vs2)) {
// OnJoined() 에서 match.Context 에 입력했던 팀 정보에서 삭제합니다.
// 편의를 위해 teams 에 팀 정보를 입력합니다.
List<JArray> teams = new List<JArray> ();
teams.Add ((JArray) match.Context ["TEAM_A"]);
teams.Add ((JArray) match.Context ["TEAM_B"]);
// 매치 취소를 요청한 플레이어 id 와 동일하면
// member list 에서 제거하여 팀 정보에서 플레이어를 삭제합니다.
foreach (var team in teams) {
int index = 0;
foreach (string id in team) {
if (id == player.Id) {
team.RemoveAt (index);
return;
}
index++;
}
}
}
}
|
26.3.2. 示例2: 匹配不成功时,更改条件重新匹配¶
下面是按照Stage进行Matchmaking的示例。各Stage的匹配条件 均不同,特定时间内未能Matchamking时,将在下一Stage中尝试。
26.3.2.1. 匹配客户端¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | enum Stage {
kStage1 = 1,
kStage2,
kStage3,
kStageEnd
};
// Matchmaking 을 시작하기 위한 클라이언트 패킷 핸들러입니다.
// Stage 1 로 matchmaking 을 시작합니다.
void OnMatchmakingRequested(const Ptr<Session> &session, const Json &message) {
StartMatchmaking(session, kStage1);
}
void StartMatchmaking(const Ptr<Session> &session, int64_t stage) {
string player_id = ...
Json player_context;
player_context["player_id"] = player_id;
player_context["player_level"] = 10;
player_context["character_level"] = 60;
player_context["play_count"] = 100;
LOG(INFO) << "start matchmaking: id=" << player_id << ", stage=" << stage;
WallClock::Value timeout = WallClock::FromSec(10);
MatchmakingClient::StartMatchmaking(stage, player_id, player_context,
bind(&OnMatched,
session, stage, _1, _2, _3),
MatchmakingClient::kMostNumberOfPlayers,
MatchmakingClient::kNullProgressCallback,
timeout);
}
// Matchmaking 결과를 처리합니다.
void OnMatched(const Ptr<Session> &session, const int64_t &stage,
const string &player_id,
const MatchmakingClient::Match &match,
MatchmakingClient::MatchResult result) {
if (result == MatchmakingClient::kMRError) {
LOG(ERROR) << "matchmaking error.";
return;
} else if (result == MatchmakingClient::kMRAlreadyRequested) {
LOG(WARNING) << "matchmaking already requested.";
return;
} else if (result == MatchmakingClient::kMRTimeout) {
// 타임아웃 되었으면, 다음 stage 로 다시 시도합니다.
int64_t next_stage = stage + 1;
if (next_stage == kStageEnd) {
LOG(WARNING) << "no stage to try.";
return;
}
StartMatchmaking(session, next_stage);
return;
}
LOG(INFO) << "matchmaking completed!";
// Match 가 성사되었습니다. 게임을 시작하도록 합니다.
// player_id 에 대한 콜백이며, match.players 에 다른 플레이어들의
// 정보가 담겨 있습니다.
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | enum Stage {
kStage1 = 1,
kStage2,
kStage3,
kStageEnd
};
// Matchmaking 을 시작하기 위한 클라이언트 패킷 핸들러입니다.
// Stage 1 로 matchmaking 을 시작합니다.
public static void OnMatchmakingRequested(Session session, JObject message)
{
StartMatchmaking(session, kStage1);
}
public static void StartMatchmaking(Session session, long stage)
{
string player_id = ...
JObject player_context = new JObject ();
player_context ["player_id"] = player_id;
player_context ["player_level"] = 10;
player_context ["character_level"] = 60;
player_context ["play_count"] = 100;
Log.Info("start matchmaking: id= {0}, stage= {1}", player_id, stage);
Client.MatchCallback match_cb = new Client.MatchCallback((
string match_player_id, Match match, MatchResult result) => {
OnMatched(session, stage, match_player_id, match, result);
});
TimeSpan timeout = WallClock.FromSec(10);
funapi.Matchmaking.Client.Start(stage,
player_id,
player_context,
match_cb,
Client.TargetServerSelection.kMostNumberOfPlayers,
null,
timeout);
}
// Matchmaking 결과를 처리합니다.
public static void OnMatched(Session session,
long stage,
string player_id,
Match match,
MatchResult result) {
if (result == MatchResult.kError) {
Log.Error ("matchmaking error.");
return;
} else if (result == MatchResult.kAlreadyRequested) {
Log.Warning ("matchmaking already requested.");
return;
} else if (result == MatchResult.kTimeout) {
// 타임아웃 되었으면, 다음 stage 로 다시 시도합니다.
long next_stage = stage + 1;
if (next_stage == (long) Stage.kStageEnd) {
Log.Warning ("no stage to try again.");
return;
}
StartMatchmaking (session, next_stage);
return;
}
Log.Info ("matchmaking completed!");
// Match 가 성사되었습니다. 게임을 시작하도록 합니다.
// player_id 에 대한 콜백이며, match.players 에 다른 플레이어들의
// 정보가 담겨 있습니다.
}
|
26.3.2.2. 匹配服务器¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | enum Stage {
kStage1 = 1,
kStage2,
kStage3,
kStageEnd
};
static bool MyServer::Install(const ArgumentMap &arguments) {
MatchmakingServer::Start(MatchChecker, CompletionChecker, JoinCb, LeaveCb);
}
// 주어진 player 가 match 에 합류할지 판단합니다.
bool MatchChecker(const MatchmakingServer::Player &player1,
const MatchmakingServer::Match &match) {
BOOST_ASSERT(match.players.size() > 0);
const MatchmakingServer::Player &player2 = match.players.front();
if (match.type == kStage1) {
// player level 로 판단합니다.
int64_t player_level1 = player1.context["player_level"].GetInteger();
int64_t player_level2 = player2.context["player_level"].GetInteger();
if (abs(player_level1 - player_level2) <= 3) {
return true;
}
} else if (match.type == kStage2) {
// character level 로 판단합니다.
int64_t char_level1 = player1.context["character_level"].GetInteger();
int64_t char_level2 = player2.context["character_level"].GetInteger();
if (abs(char_level1 - char_level2) <= 10) {
return true;
}
} else if (match.type == kStage3) {
// play count 로 판단합니다.
int64_t play_count1 = player1.context["character_level"].GetInteger();
int64_t play_count2 = player2.context["character_level"].GetInteger();
if (abs(play_count1 - play_count2) <= 30) {
return true;
}
} else {
BOOST_ASSERT(false);
}
return false;
}
// Match 가 완료 되었는지 판단합니다.
MatchmakingServer::MatchState CompletionChecker(const MatchmakingServer::Match &match) {
if (match.players.size() == 2) {
return MatchmakingServer::kMatchComplete;
}
return MatchmakingServer::kMatchNeedMorePlayer;
}
// Match 에 player 가 참여할 때 불립니다. 필요시 match->context 에 JSON 형태로
// 추가적인 데이터를 저장할 수 있습니다.
void JoinCb(const MatchmakingServer::Player &player,
MatchmakingServer::Match *match) {
// do nothing.
}
// Match 에서 player 가 나갈 때 불립니다. player 가 Cancel() 을 호출한 경우입니다.
// JoinCb() 에서 match->context 에 저장한 것을 삭제하는 등의 처리를 합니다.
void LeaveCb(const MatchmakingServer::Player&player,
MatchmakingServer::Match *match) {
// do nothing.
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | enum Stage {
kStage1 = 1,
kStage2,
kStage3,
kStageEnd
};
public static void Install (ArgumentMap arguments)
{
...
funapi.Matchmaking.Server.Start (MatchChecker, CompletionChecker,
JoinCb, LeaveCb);
...
}
// 주어진 player 가 match 에 합류할지 판단합니다.
bool MatchChecker(Player player1, Match match) {
Log.Assert (match.Players.Count > 0);
Player player2 = match.Players[0];
if (match.MatchType.Equals(Stage.kStage1))
{
// player level 로 판단합니다.
long player_level1 = (long) player1.Context["player_level"];
long player_level2 = (long) player2.Context["player_level"];
if (System.Math.Abs(player_level1 - player_level2) <= 3) {
return true;
}
} else if (match.MatchType.Equals(Stage.kStage2)) {
// character level 로 판단합니다.
long char_level1 = (long) player1.Context["character_level"];
long char_level2 = (long) player2.Context["character_level"];
if (System.Math.Abs(char_level1 - char_level2) <= 10) {
return true;
}
} else if(match.MatchType.Equals(Stage.kStage3)) {
// play count 로 판단합니다.
long play_count1 = (long) player1.Context["character_level"];
long play_count2 = (long) player2.Context["character_level"];
if (System.Math.Abs(play_count1 - play_count2) <= 30) {
return true;
}
} else {
Log.Assert (false);
}
return false;
}
// Match 가 완료 되었는지 판단합니다.
funapi.Matchmaking.Server.MatchState CompletionChecker(Match match) {
if (match.Players.Count == 2) {
return funapi.Matchmaking.Server.MatchState.kMatchComplete;
}
return funapi.Matchmaking.Server.MatchState.kMatchNeedMorePlayer;
}
// Match 에 player 가 참여할 때 불립니다. 필요시 match.Context 에 JSON 형태로
// 추가적인 데이터를 저장할 수 있습니다.
void JoinCb(Player player, Match match) {
// do nothing.
}
// Match 에서 player 가 나갈 때 불립니다. player 가 Cancel() 을 호출한 경우입니다.
// JoinCb() 에서 match.Context 에 저장한 것을 삭제하는 등의 처리를 합니다.
void LeaveCb(Player player, Match match) {
// do nothing.
}
|
26.3.3. 示例3: Group vs. Group¶
Matchmaking除了以玩家为单位以外,还可以以群组为单位。 下面是在由2人组成的群组中,与平均等级差异小于10的其他群组进行matchmaking的示例。 这就相当于好友组队后与其他队伍进行对战的脚本。
26.3.3.1. 匹配客户端¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | enum MatchType {
kMatch1Vs1 = 0,
kMatch2Vs2,
kGroupMatch2Vs2
};
// 클라이언트로부터 Group matchmaking 요청을 수신하는 패킷 핸들러입니다.
// 어떻게 그룹핑 되는지는 패킷 안에 실려 온다고 가정합니다.
void OnMatchmakingRequested(const Ptr<Session> &session, const Json &message) {
string group_member1_id = ...;
int64_t group_member1_level = ...;
string group_member2_id = ...;
int64_t group_member2_level = ...;
// group 의 첫 번째 player 가 group 을 대표하도록 합니다.
string group_id = group_member1_id;
Json group_ctxt;
Json group_member1;
group_member1["id"] = group_member1_id;
group_member1["level"] = group_member1_level;
group_ctxt["members"].PushBack(group_member1);
Json group_member2;
group_member2["id"] = group_member2_id;
group_member2["level"] = group_member2_level;
group_ctxt["members"].PushBack(group_member2);
MatchmakingClient::StartMatchmaking(kGroupMatch2Vs2, group_id, group_ctxt,
OnMatched,
MatchmakingClient::kMostNumberOfPlayers,
MatchmakingClient::kNullProgressCallback,
WallClock::FromSec(60));
}
void OnMatched(const string &group_id,
const MatchmakingClient::Match &match,
MatchmakingClient::MatchResult result) {
if (result == MatchmakingClient::kMRAlreadyRequested) {
...
return;
} else if (result == MatchmakingClient::kMRTimeout) {
...
return;
} else if (result == MatchmakingClient::kMRError) {
...
return;
}
BOOST_ASSERT(result == MatchmakingClient::kMRSuccess);
// matchmaking server 에서 match.context 에 아래와 같이 플레이어의
// 정보를 입력해 두었습니다.
MatchmakingClient::MatchId match_id = match.match_id;
string group_a_player1 = match.context["GROUP_A"][0].GetString();
string group_a_player2 = match.context["GROUP_A"][1].GetString();
string group_b_player1 = match.context["GROUP_B"][0].GetString();
string group_b_player2 = match.context["GROUP_B"][1].GetString();
// group a 와 group b 의 대전을 시작하는 처리를 합니다.
// (클라이언트에 응답을 보내는 작업 등)
...
}
|
추후 업데이트 됩니다.
26.3.3.2. 匹配服务器¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | enum MatchType {
kMatch1Vs1 = 0,
kMatch2Vs2,
kGroupMatch2Vs2
};
static bool MyServer::Install(const ArgumentMap &arguments) {
...
MatchmakingServer::Start(CheckMatch, CheckCompletion, OnJoined, OnLeft);
...
}
// Group 이 match 에 참여해도 되는지 검사합니다.
bool CheckMatch(const MatchmakingServer::Player &group,
const MatchmakingServer::Match &match) {
if (match.type == kMatch1Vs1) {
...
} else if (match.type == kMatch2Vs2) {
...
} else if (match.type == kGroupMatch2Vs2) {
// 레벨은 MatchmakingClient::StartMatchmaking() 함수를 호출할 때
// 전달했던 context 에 있습니다.
int64_t group_a_avg_level = 0;
{
const Json &ctxt = group.context;
int64_t member_1 = ctxt["members"][0]["level"].GetInteger();
int64_t member_2 = ctxt["members"][1]["level"].GetInteger();
group_a_avg_level = (member_1 + member_2) / 2;
}
int64_t group_b_avg_level = 0;
{
const Json &ctxt = match.players.front().context;
int64_t member_1 = ctxt["members"][0]["level"].GetInteger();
int64_t member_2 = ctxt["members"][1]["level"].GetInteger();
group_b_avg_level = (member_1 + member_2) / 2;
}
if (abs(group_a_avg_level - group_b_avg_level) > 10) {
return false;
}
// 필요하다면 다른 조건도 검사합니다.
// 모든 조건에 만족하여 플레이어를 match 에 합류시킵니다.
return true;
}
}
// JoinMatch 함수가 불린 후 호출됩니다. 해당 매치가 성사 되었는지 판단합니다.
MatchmakingServer::MatchState CheckCompletion(
const MatchmakingServer::Match &match) {
if (match.type == kMatch1Vs1) {
...
} else if (match.type == kMatch2Vs2) {
...
} else if (match.type == kGroupMatch2Vs2) {
if (match.players.size() == 2) {
// 2 그룹이 모두 모였습니다. (총 4 명)
return MatchmakingServer::kMatchComplete;
}
// 각 그룹이 고정된 인원수가 아니면(여기서는 2 명) 아래처럼
// 처리할 수 있습니다.
// int64_t members = 0;
// for (size_t i = 0; i < match.players.size(); ++i) {
// const MatchmakingServer::Player &group = match.players[i];
// members += group.context["members"].Size();
// }
// if (members >= 4) {
// return MatchmakingServer::kMatchComplete;
// }
}
return MatchmakingServer::kMatchNeedMorePlayer;
}
// 조건을 만족하여 CheckMatch 함수가 true 를 반환하면 이 함수가 호출됩니다.
// 이제 group 은 match 에 참여하게 되었습니다. 여기서 match 의 context 를
// 업데이트 합니다. 이 context 는 OnMatched 에서 쓰게 됩니다.
void OnJoined(const MatchmakingServer::Player &group,
MatchmakingServer::Match *match) {
if (match->type == kMatch1Vs1) {
...
} else if (match->type == kMatch2Vs2) {
...
} else if (match->type == kGroupMatch2Vs2) {
// join 한 group 을 group A 또는 group B 로 지정합니다.
if (not match->context.HasAttribute("GROUP_A")) {
match->context["GROUP_A"].SetArray();
match->context["GROUP_A"].PushBack(group.context["members"][0]["id"]);
match->context["GROUP_A"].PushBack(group.context["members"][1]["id"]);
return;
}
if (not match->context.HasAttribute("GROUP_B")) {
match->context["GROUP_B"].SetArray();
match->context["GROUP_B"].PushBack(group.context["members"][0]["id"]);
match->context["GROUP_B"].PushBack(group.context["members"][1]["id"]);
return;
}
BOOST_ASSERT(false);
}
}
// match 에 참여해 있던 group 이 MatchmakingClient::CancelMatchmaking() 을
// 요청했습니다. 위 OnJoined() 함수에서 업데이트한 정보를 삭제합니다.
void OnLeft(const MatchmakingServer::Player &group,
MatchmakingServer::Match *match) {
if (match->type == kMatch1Vs1) {
...
} else if (match->type == kMatch2Vs2) {
...
} else if (match->type == kGroupMatch2Vs2) {
string id = group.context["members"][0]["id"].GetString();
if (match->context.HasAttribute("GROUP_A")) {
if (match->context["GROUP_A"][0].GetString() == id) {
match->context.RemoveAttribute("GROUP_A");
return;
}
}
if (match->context.HasAttribute("GROUP_B")) {
if (match->context["GROUP_B"][0].GetString() == id) {
match->context.RemoveAttribute("GROUP_B");
return;
}
}
BOOST_ASSERT(false);
}
// 이 함수 호출이 완료되면 MatchmakingClient::CancelMatchmaking() 함수에
// 전달한 CancelCallback 이 호출됩니다.
}
|
추후 업데이트 됩니다.