25. Contents support part 2: Matchmaking

This feature automatically matches users.

Matchmaking is divided into MatchmakingClient and MatchmakingServer.

  • MatchmakingClient is used when having players join or cancel joining a match. Though the name Client is added, the client is for server use to request matches from the game server; thus the name Client is added.
  • MatchmakingServer is used for the server that handles matchmaking, and when a player asks to join a match, it judges whether that player can join to allow or deny participation.

Tip

You can make a server that only handles matchmaking and run it in the MatchmakingServer flavor.

25.1. Matchmaking Client

25.1.1. Interface

You can start and stop matching using the StartMatchmaking() and CancelMatchmaking() MatchmakingClient classes. (Start() and Cancel() for C#) You can also use GetMatchmakingServerInfo() to check the MatchmakingServer status.

class MatchmakingClient {
 public:
  typedef Uuid MatchId;
  typedef int64_t Type;

  // Structure about player who joined the match
  struct Player {
    // identifier for player
    string id;

    // Custom json data
    // You can specify it when you call StartMatchmaking() to join the match.
    Json context;

    // the location of the server that player has joined.
    Rpc::PeerId location;
  };

  // Structure about match
  struct Match {
    explicit Match(Type _type);
    explicit Match(const MatchId &_match_id, Type _type);

    // Match identifier
    const MatchId match_id;

    // match type
    // You can specify Integer(enum) value as unique match type to identify match.
    const Type type;

    // player list in the match
    std::vector<Player> players;

    // custom context in the match
    // It is shared through same match type
    // and only accessible on specific callback(join, leave) in MatchmakingServer.
    // for example, you may use the context to setup the team.
    Json context;
  };

  // Match result in MatchCallback
  enum MatchResult {
    kMRSuccess = 0,
    kMRAlreadyRequested,
    kMRTimeout,
    kMRError = 1000
  };

  // Cancel result in CancelCallback
  enum CancelResult {
    kCRSuccess = 0,
    kCRNoRequest,
    kCRError = 1000
  };

  // Callback function that will be invoked after match completed
  typedef function<void(const string & /*player_id*/,
                        const Match & /*match*/,
                        MatchResult /*result*/)> MatchCallback;

  // Callback function that will be invoked after match updated
  // only available when 'enable_match_progress_callback' is 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;

  // Callback function that will be invoked after match cancelled
  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

The ProgressCallback 2 callback is available in version 1.0.0-2768 Experimental and higher.

25.1.1.1. Starting matching

Sends a request to find match partners.

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: The integer value meaning the match type. You can define this yourself, and differentiates matches of the same type on the MatchmakingServer.

  • player_id: Identifier for match participants.

  • player_context: The value used to judge participant ability level on the MatchmakingServer. You can freely specify match requirements in JSON format.

    Important

    You cannot use top-level request_time or elapsed_time attributes.

  • match_callback: Function invoked when matchmaking is complete.

  • target_server: Selects the matchmaking server.

Note

This can be automatically selected with enum TargetServerSelection or by sending PeerId directly.

  • progress_callback: Function invoked when matching progress changes. Ignored if the default value kNullProgrssCallback is sent.

Note

Only valid when the MatchmakingServer‘s enable_match_progress_callback is true.

  • timeout: The match is automatically canceled if not achieved within a set time, and match_callback is called with the kMRTimeout value as the result. Ignored if the default value kNullTimeout is sent.

25.1.1.2. Canceling matching

The matching request is canceled.

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: Same as the type entered in StartMatchmaking() (Client.Start() in C#).

  • player_id: Same as the player_id entered in StartMatchmaking().

  • callback: Function invoked after a matching request is canceled.

    Note

    If cancel is invoked for players for whom MatchCallback was invoked when matching was completed, kCRNoRequest is passed to the callback.

25.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);

25.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
  }
}

25.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

The feature is available in version 1.0.0-2768 Experimental and higher.

25.1.2. MANIFEST.json settings

Include a MatchmakingClient component in MANIFEST.json as follows.

1
2
3
4
...
"MatchmakingClient": {
},
...

25.2. Matchmaking Server

25.2.1. Interface

You can use the matchmaking server class Start() to start the matchmaking server. This is invoked by the server’s Install() function.

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);
  }
}

25.2.1.1. Starting the server

Start() is invoked by the game server’s Install().

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

    Invoked when match requirements must be checked.

    Requirements for participation (level, gold owned, etc.) are judged and this is returned as true or false depending on whether a match is available.

    • Return: True if this user can join the match.
    • Argument: player: Player to join the match
    • Argument: match: Match instance to evaluate

    Note

    An attribute called elapsed_time is additionally included in player and match.players context variables and saves the time this user has remained unmatched. Please refer to Matchmaking examples.

  • completion_checker

    Invoked by match_checker after a player joins a match.

    This function checks whether a match has been made.

    • Return: If there was no match, then kMatchNeedMorePlayer; if there was a match, then kMatchComplete.
    • Argument: Match instance to evaluate for whether a match was made
  • join_callback

    Invoked by match_checker after a player joins a match.

    • Argument: player: Player who joined the match
    • Argument: match : match instance

    Note

    An attribute called elapsed_time is additionally included in player and match.players context variables and saves the time this user has remained unmatched. Here are some examples.

  • leave_callback

    Invoked when a player who had joined a match leaves it. Also invoked when the CancelMatchmaking() function is invoked by the matchmaking client, and two different matches can be combined when the matchmaking server’s MANIFEST.json enable_dynamic_match is true.

    Handles cancellation of team formation from join_callback.

    • Argument: player: Player for whom matching was canceled
    • Argument: match: Pointer for match instances missing players

Important

This cannot be modified because arguments passed as callbacks registered on the matchmaking server are managed by iFun Engine.

Only join_callback and leave_callback match parameters can be modified.

Note

If the MatchmakingServer‘s enable_dynamic_match is true in MANIFEST.json, the above functions may be invoked often. This is because iFun Engine combines different matches to achieve faster matches.

25.2.2. MANIFEST.json settings

Add the MatchmakingServer component to MANIFEST as follows.

...
"MatchmakingServer": {
  "enable_dynamic_match": true,
  "enable_match_progress_callback": false,
  "concurrent_number_of_players_threshold": 3000
},
...
  • enable_dynamic_match: If true, the MatchmakingServer searches again for matches by combining incomplete matches.
  • enable_match_progress_callback: If true, progress_cb, passed as a StartMatchmaking() parameter by the matchmaking client, is periodically invoked, and matchmaking progress can be checked through this.
  • concurrent_number_of_players_threshold: Sets the number of matchmaking requests to handle concurrently. If a server’s number of matchmaking requests reaches this number, that server is not matched even if kMostNumberOfPlayers is set in the matchmaking client on StartMatchmaking()‘s target_server. (Note that if all servers exceed this value, they may be unavoidably selected.)

Important

You cannot use enable_dynamic_match and enable_match_progress_callback simultaneously.

25.3. Matchmaking examples

25.3.1. Example 1: 2:2 matching with level differences

The following is an example of a 2:2 match when the level difference between players is less than 5. If 7 seconds are exceeded, a match is made regardless of level differences.

25.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
enum MatchType {
  kMatch1Vs1 = 0,
  kMatch2Vs2
};


// 2:2 매치를 요청하는 클라이언트 패킷 핸들러입니다.
void OnMatchmakingRequested(const Ptr<Session> &session, const Json &message) {
  string player_id = ...;

  Json context;
  context["LEVEL"] = ...;

  WallClock::Duration timeout = MatchmakingClient::kNullTimeout;
  if (enable_match_timeout) {
    timeout = WallClock::FromMsec(10 * 1000);
  }

  // 매치의 진행상황을 받기 위해 아래처럼 콜백을 등록합니다.
  // 콜백은 MatchmakingServer 의 enable_match_progress_callback 이
  // true 일 때만 호출됩니다.
  MatchmakingClient::ProgressCallback prog_cb =
      MatchmakingClient::kNullProgressCallback;
  if (enable_match_progress) {
    prog_cb = [](const string &player_id,
                 MatchmakingClient::MatchId &match_id,
                 const string &player_id_joined,
                 const string &player_id_left,
                 const Json &match_context) {
      // 1. player_id_joined.empty() 가 아니라면 player_id 에 해당하는 유저가
      //    player_id_joined 에 해당하는 유저와 예비 매칭 되었을 때 불립니다.
      // 2. player_id_left.empty() 가 아니라면 player_id 에 해당하는 유저와
      //    player_id_left 에 해당하는 유저의 예비 매칭이 취소 되었을 때
      //    불립니다.
    };
  }

  // matchmaking 을 요청합니다.
  // 매치가 성사되면 OnMatched 함수가 호출됩니다.
  MatchmakingClient::StartMatchmaking(kMatch2Vs2, player_id, context,
                                      OnMatched,
                                      MatchmakingClient::kMostNumberOfPlayers,
                                      prog_cb, timeout);

  // 클라이언트에 응답을 보내는 작업 등의 후속처리를 합니다.
}


// 매치 취소를 요청하는 클라이언트 패킷 핸들러입니다.
void OnCancelRequested(const Ptr<Session> &session, const Json &message) {
  string player_id = ...;

  // matchmaking 취소를 요청합니다.
  // matchmaking 취소 처리가 완료되면 OnCancelled 함수가 호출됩니다.
  MatchmakingClient::CancelMatchmaking(kMatch2Vs2, player_id, OnCancelled);
}


// 매치가 성사되면 호출됩니다.
void OnMatched(const string &player_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);

  // 매치가 성사 되었습니다.
  // match.players 에는 match 에 참여한 player 들이 포함되어 있으나,
  // matchmaking server 에서 match.context 에 아래와 같이 플레이어를
  // 팀으로 구분해 두었습니다.
  MatchmakingClient::MatchId match_id = match.match_id;

  string team_a_player1 = match.context["TEAM_A"][0].GetString();
  string team_a_player2 = match.context["TEAM_A"][1].GetString();

  string team_b_player1 = match.context["TEAM_B"][0].GetString();
  string team_b_player2 = match.context["TEAM_B"][1].GetString();

  // match id 또는 A, B 팀의 각 player id 를 이용하여 대전을 시작하도록 합니다.
  // 클라이언트에 응답을 보내는 작업 등의 후속처리를 합니다.
  ...
}


// 매치가 취소되면 호출됩니다.
void OnCancelled(const string &player_id, MatchmakingClient::CancelResult result) {
  if (result == MatchmakingClient::kCRNoRequest) {
    return;
  } else if (result == MatchmakingClient::kCRError) {
    return;
  }

  BOOST_ASSERT(result == MatchmakingClient::kCRSuccess);

  // 클라이언트에 응답을 보내는 작업 등의 후속처리를 합니다.

  ...
}
  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);

    // 클라이언트에 응답을 보내는 작업 등의 후속처리를 합니다.
    ...
  }
};

25.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++;
      }
    }
  }
}

25.3.2. Example 2: Changing requirements and rematching when matching fails

The following example shows matchmaking divided into stages. Match requirements differ at each stage, and if matchmaking is unsuccessful within a certain time, the next stage is attempted.

25.3.2.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
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 에 다른 플레이어들의
  // 정보가 담겨 있습니다.
}

25.3.2.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
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.
}

25.3.3. Example 3: Group vs. group

Matchmaking can also match groups rather than players. The following is an example of matchmaking with different 2-person groups with average level differences of 10 or more. The scenario assumes competition between parties of friends.

25.3.3.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
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 의 대전을 시작하는 처리를 합니다.
  // (클라이언트에 응답을 보내는 작업 등)
  ...
}
추후 업데이트 됩니다.

25.3.3.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
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 이 호출됩니다.
}
추후 업데이트 됩니다.