Matchmaker #

Nakama 매치메이커 기능을 사용하면 사용자는 다른 사용자(팀 동료 또는 상대방)를 검색하고 짝을 지어 대결, 그룹을 형성하거나 프로젝트에 내장된 기타 소셜 기능에 참여할 수 있습니다.

친구, 그룹, 팀원 또는 상대방를 찾기 위한 매치메이킹
친구, 그룹, 팀원 또는 상대방를 찾기 위한 매치메이킹

매치메이커는 사용자 풀과 매치메이킹 요청(“티켓”)을 유지 관리하고 좋은 대결이 발견될 때마다 이들을 함께 배치합니다. 특정 사용자에 대한 “좋은 대결"의 정의는 해당 매치메이커 티켓에 정의된 기준을 기반으로 합니다. 대결이 발견되기까지의 시간은 기준이 얼마나 구체적으로 정의되었는지와 현재 매치메이킹 풀에 있는 사용자 수에 따라 매우 다양합니다. 매치메이킹 기간이 너무 길거나 대결이 전혀 발견되지 않으면 기준을 넓혀 보십시오.

매치메이킹은 활성 플레이어(오픈 소켓 연결이 있는 사용자)에만 적용됩니다. 매치메이커 티켓을 제출하면(사용 가능한 플레이어의 매치메이커 풀에 자신을 추가) 사용자는 대결을 찾거나 요청을 취소할 때까지 풀에 남습니다. 사용자가 연결을 끊으면 보류 중인 매치메이킹 요청도 취소됩니다.

오프라인 매치메이킹
사용 사례에 오프라인 매치메이킹이 필요한 경우 Heroic Labs에 도움을 요청하세요.

매치메이킹은 Nakama의 대결 목록 기능과 다릅니다. 매치메이커가 사용자를 함께 배치하여 새 대결을 시작하는 데 사용되는 반면, 대결 목록은 사용자에게 즉시 가입할 수 있는 기존 대결을 표시하는 데 사용됩니다. 매치메이킹 또는 대결 목록 사용을 결정하는 것은 프로젝트 목표 및 요구 사항을 기반으로 하는 설계 고려 사항(기술적 고려 사항 아님)입니다.

구성 #

매치메이커 기능에 영향을 미치는 몇 가지 매개변수를 Nakama 구성에 사용할 수 있습니다.

사용자가 한 번에 가질 수 있는 최대 동시 티켓 수를 설정하여 사용자가 지나치게 많은 티켓을 제출하는 것을 방지하고 이전 티켓을 절대 취소하지 못하도록 만들 수 있습니다.

매치메이커가 사용자의 “이상적인”(크기) 대결을 찾으려고 시도하는 시간 간격과, 덜 이상적인(크기) 대결을 허용하기까지의 간격의 수를 설정하여, 사용자의 대기 시간을 조정하고 이상적인 대결을 찾는 것과 더 빨리 대결을 시작하는 것 사이의 균형을 조정할 수 있습니다.

역매칭 정밀도임계값 플래그는 역매칭 정밀도 활성화 여부와 기간을 지정하는 데 사용됩니다. 활성화된 경우(rev_precisiontrue로 설정된 경우), 매치메이커는 양방향으로 매치의 유효성을 검사합니다(즉, 플레이어 A가 플레이어 B와 매치할 때 플레이어 B도 플레이어 A와 매치하는지 확인합니다). 양방향 매칭이 아닌 경우, 매치메이커는 rev_threshold 간격 동안 양방향 매칭을 계속 검색합니다. 간격 수(기본값은 1) 내에 양방향 매치를 찾지 못하면 매치메이커는 단방향 매치를 반환합니다.

매치메이킹 기준 #

매치메이킹을 시작하기 위해 사용자는 매치메이킹 풀에 자신을 추가합니다. 매치메이커 티켓의 일부로 원하는 대결을 설명하기 위해 포함시킬 수 있는 선택적 기준이 있습니다: 속성, 최소 수 및 최대 수, 수 승수쿼리.

속성 #

속성은 매치메이킹 티켓을 제출하는 사용자를 설명하는 키-값 쌍으로 문자열 또는 숫자입니다. 속성에 제공할 수 있는 몇 가지 일반적인 예에는 사용자의 게임 순위/레벨, 기술 등급, 연결 지역 또는 선택한 대결 유형(예: 모두 무료, 깃발 뺏기 등) 등이 있습니다.

Client
1
2
3
4
5
6
7
const stringProperties = {
  region: "europe"
};

const numericProperties = {
  rank: 8
};
Client
1
2
3
4
5
6
7
var stringProperties = new Dictionary<string, string>() {
    {"region", "europe"}
};

var numericProperties = new Dictionary<string, int>() {
    {"rank", 8}
};
Client
1
2
3
4
5
NStringMap stringProperties;
NStringDoubleMap numericProperties;

stringProperties.emplace("region", "europe");
numericProperties.emplace("rank", 8.0);
Client
1
2
3
4
5
6
7
Map<String, String> stringProperties = new HashMap<String, String>() {{
    put("region", "europe");
}};

Map<String, Double> numericProperties = new HashMap<String, Double>() {{
    put("rank", 8.0);
}};
Client
1
2
var string_properties = { "region": "europe" }
var numeric_properties = { "rank": 8 }

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.

이러한 속성은 사용자가 매치메이킹 프로세스를 시작할 때 제출되며 티켓마다 다를 수 있습니다. 서버는 포함된 모든 속성을 병합하여 매치메이커 티켓의 일부인 전체 속성을 만듭니다.

매치메이킹이 완료되면 이러한 속성은 매치된 모든 사용자에게 표시됩니다. 클라이언트에게 유용한 경우 매치메이킹 프로세스 자체에 영향을 주지 않고 추가 정보를 저장할 수 있습니다. 매치메이킹 프로세스의 일부로 쿼리되지 않은 속성만 제출하면 됩니다.

매치메이커에 사용자를 추가할 때 before 후크를 사용하여 속성을 정식으로 제어할 수도 있습니다:

Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
var (
	errInternal = runtime.NewError("internal server error", 13)
)

func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
	initializer.RegisterBeforeRt("MatchmakerAdd", func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *rtapi.Envelope) (*rtapi.Envelope, error) {
		message, ok := in.Message.(*rtapi.Envelope_MatchmakerAdd)
		if !ok {
			return nil, errInternal
		}

		// If the string properties contains a region value of "europe", modify it to "europe-west"
		if value, ok := message.MatchmakerAdd.StringProperties["region"]; ok && value == "europe" {
			message.MatchmakerAdd.StringProperties["region"] = "europe-west"
		}

		return in, nil
	})

	return nil
}
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function InitModule(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
  initializer.registerRtBefore("MatchmakerAdd", beforeMatchmakerAdd)
}

const beforeMatchmakerAdd : nkruntime.RtBeforeHookFunction<nkruntime.EnvelopeMatchmakerAdd> = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, envelope: nkruntime.EnvelopeMatchmakerAdd) : nkruntime.EnvelopeMatchmakerAdd | void {
  const region = envelope.matchmakerAdd.stringProperties["region"];

  // If the string properties contain a region value of "europe", modify it to "europe-west"
  if (region && region == "europe") {
    envelope.matchmakerAdd.stringProperties["region"] = "europe-west";
  }

  return envelope;
}
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
nk.register_rt_before(function(context, payload)
    local region = payload.matchmaker_add.string_properties["region"]

    -- If the string properties contain a region value of "europe", modify it to "europe-west"
    if region == "europe" then
        payload.matchmaker_add.string_properties["region"] = "europe-west"
    end

    return payload
end, "MatchmakerAdd")

최소 및 최대 수 #

매치메이커 요청을 제출할 때 사용자는 최소 및 최대 수를 모두 지정해야 합니다. 여기서 minCount은(는) 허용 가능한 가장 작은 대결 크기를, maxCount은(는) 허용 가능한 가장 큰 대결 크기를 나타내며 둘 다 요청을 제출하는 플레이어가 포함됩니다.

매치메이커는 항상 제공된 최대 수에서 대결을 시도합니다. 사용자가 충분하지 않은 경우 최소 수 이상이라면 최대 수에 가장 가까운 크기가 대결로 반환됩니다.

예를 들어 최소 수가 2이고 최대 수가 4인 경우 매치메이커는 사용자와 일치시킬 다른 3명의 플레이어를 찾으려고 시도합니다. 3명의 일치하는 플레이어가 없는 경우 매치메이커는 다른 2명과 일치시키려고 시도하고 2명이 안되는 경우 마지막으로 다른 플레이어 1명만 찾습니다.

사용 가능한 사용자가 최소 수에도 못 미치면 대결이 반환되지 않고 사용자는 풀에 남습니다.

Client
1
2
3
4
const query = "*";
const minCount = 2;
const maxCount = 4;
var ticket = await socket.addMatchmaker(query, minCount, maxCount);
Client
1
2
3
4
var query = "*";
var minCount = 2;
var maxCount = 4;
var matchmakerTicket = await socket.AddMatchmakerAsync(query, minCount, maxCount);
Client
1
2
3
4
String query = "*";
int minCount = 2;
int maxCount = 4;
MatchmakerTicket matchmakerTicket = socket.addMatchmaker(query, minCount, maxCount).get();
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
var query = "*"
var min_count = 2
var max_count = 4

var matchmaker_ticket : NakamaRTAPI.MatchmakerTicket = yield(
    socket.add_matchmaker_async(query, min_count, max_count),
    "completed"
)

if matchmaker_ticket.is_exception():
    print("An error occurred: %s" % matchmaker_ticket)
    return

print("Got ticket: %s" % [matchmaker_ticket])

Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.

사용자는 동일한 최소 및 최대 수를 제출하여 정확한 수의 상대방을 검색할 수 있습니다.

Client
1
2
3
4
const query = "*";
const minCount = 4;
const maxCount = 4;
var ticket = await socket.addMatchmaker(query, minCount, maxCount);
Client
1
2
3
4
var query = "*";
var minCount = 4;
var maxCount = 4;
var matchmakerTicket = await socket.AddMatchmakerAsync(query, minCount, maxCount);
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
auto successCallback = [](const NMatchmakerTicket& ticket)
{
    std::cout << "Matchmaker ticket: " << ticket.ticket << std::endl;
};

int32_t minCount = 2;
int32_t maxCount = 4;
string query = "*";

rtClient->addMatchmaker(minCount, maxCount, query, {}, {}, successCallback);
Client
1
2
3
4
5
String query = "*";
int minCount = 4;
int maxCount = 4;

MatchmakerTicket matchmakerTicket = socket.addMatchmaker(query, minCount, maxCount).get();
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
var query = "*"
var min_count = 4
var max_count = 4

var matchmaker_ticket : NakamaRTAPI.MatchmakerTicket = yield(
    socket.add_matchmaker_async(query, min_count, max_count),
    "completed"
)

if matchmaker_ticket.is_exception():
    print("An error occurred: %s" % matchmaker_ticket)
    return

print("Got ticket: %s" % [matchmaker_ticket])

Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.

또한 매치메이커에 사용자를 추가할 때 before 후크를 사용하여 최소 및 최대 수를 정식으로 제어할 수 있습니다.

Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
var (
	errInternal = runtime.NewError("internal server error", 13)
)

func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
	initializer.RegisterBeforeRt("MatchmakerAdd", func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *rtapi.Envelope) (*rtapi.Envelope, error) {
		message, ok := in.Message.(*rtapi.Envelope_MatchmakerAdd)
		if !ok {
			return nil, errInternal
		}

		// Force min count to be 4 and max count to be 8
        message.MatchmakerAdd.MinCount = 4
        message.MatchmakerAdd.MaxCount = 8

		return in, nil
	})

	return nil
}
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function InitModule(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
  initializer.registerRtBefore("MatchmakerAdd", beforeMatchmakerAdd)
}

const beforeMatchmakerAdd : nkruntime.RtBeforeHookFunction<nkruntime.EnvelopeMatchmakerAdd> = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, envelope: nkruntime.EnvelopeMatchmakerAdd) : nkruntime.EnvelopeMatchmakerAdd | void {
  // Force min count to be 4 and max count to be 8
  envelope.matchmakerAdd.minCount = 4
  envelope.matchmakerAdd.maxCount = 8

  return envelope;
}
Server
1
2
3
4
5
6
7
nk.register_rt_before(function(context, payload)
  -- Force min count to be 4 and max count to be 8
  payload.matchmaker_add.min_count = 4
  payload.matchmaker_add.max_count = 8

  return payload
end, "MatchmakerAdd")

수 승수 #

countMultiple 매개변수는 허용 가능한 대결 크기에 대해 특정 승수를 적용해야 할 때 사용할 수 있습니다(즉, 결과가 5의 배수여야 함).

예를 들어, 다음 요청을 사용합니다:

Client
1
2
3
4
5
const query = "*";
const minCount = 5;
const maxCount = 25;
const countMultiple = 5;
var ticket = await socket.addMatchmaker(query, minCount, maxCount, countMultiple);
Client
1
2
3
4
5
var query = "*";
var minCount = 5;
var maxCount = 25;
var countMultiple = 5;
var matchmakerTicket = await socket.AddMatchmakerAsync(query, minCount, maxCount, countMultiple);
Client
1
2
3
4
5
String query = "*";
int minCount = 5;
int maxCount = 25;
int countMultiple = 5;
MatchmakerTicket matchmakerTicket = socket.addMatchmaker(query, minCount, maxCount, countMultiple).get();
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var query = "*"
var min_count = 5
var max_count = 25
var count_multiple = 5

var matchmaker_ticket : NakamaRTAPI.MatchmakerTicket = yield(
    socket.add_matchmaker_async(query, min_count, max_count, count_multiple),
    "completed"
)

if matchmaker_ticket.is_exception():
    print("An error occurred: %s" % matchmaker_ticket)
    return

print("Got ticket: %s" % [matchmaker_ticket])

Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.

매치메이커는 5의 배수인 총 일치 플레이어 수가 포함된 결과만 반환하며, 먼저 최대 25개를 반환한 다음 20, 15 등의 방식으로 최대 수를 반환합니다. 23명의 일치하는 플레이어가 있어도 반환된 결과는 20명의 플레이어입니다.

매치메이커에 사용자를 추가할 때 before 후크를 사용하여 수 승수를 정식으로 제어할 수도 있습니다:

Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
var (
	errInternal = runtime.NewError("internal server error", 13)
)

func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
	initializer.RegisterBeforeRt("MatchmakerAdd", func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *rtapi.Envelope) (*rtapi.Envelope, error) {
		message, ok := in.Message.(*rtapi.Envelope_MatchmakerAdd)
		if !ok {
			return nil, errInternal
		}

		// Force the count multiple to be in multiples of 5
		message.MatchmakerAdd.CountMultiple = &wrapperspb.Int32Value{Value: 5}

		return in, nil
	})

	return nil
}
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function InitModule(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
  initializer.registerRtBefore("MatchmakerAdd", beforeMatchmakerAdd)
}

const beforeMatchmakerAdd : nkruntime.RtBeforeHookFunction<nkruntime.EnvelopeMatchmakerAdd> = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, envelope: nkruntime.EnvelopeMatchmakerAdd) : nkruntime.EnvelopeMatchmakerAdd | void {
  // Force the count multiple to be in multiples of 5
  envelope.matchmakerAdd.countMultiple = 5;

  return envelope;
}
Server
1
2
3
4
5
6
nk.register_rt_before(function(context, payload)
  -- Force the count multiple to be in multiples of 5
  payload.matchmaker_add.count_multiple = 5

  return payload
end, "MatchmakerAdd")

쿼리 #

여기서 properties은(는) 다른 플레이어를 _검색_하는 사용자를 나타내고 query은(는) 다른 사용자 내에서 _검색하는 속성_을 나타냅니다.

모든 사용자의 매치메이커 속성은 properties 접두사 아래의 쿼리에서 사용할 수 있습니다. 정확히 일치하거나 값 범위로 속성 필터를 조합하여 상대방을 찾을 수 있습니다.

쿼리에 사용할 수 있는 문법 및 연산자에 대해 알아보려면 쿼리 구문을 참조하세요.

이 예에서는 반드시 europe 에 있고 5에서 10 사이에 rank을(를) 갖고 있어야 하는 상대방을 검색합니다:

Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const query = "+properties.region:europe +properties.rank:>=5 +properties.rank:<=10";
const minCount = 2;
const maxCount = 4;

const stringProperties = {
  region: "europe"
};

const numericProperties = {
  rank: 8
};

var ticket = await socket.addMatchmaker(query, minCount, maxCount, stringProperties, numericProperties);
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var query = "+properties.region:europe +properties.rank:>=5 +properties.rank:<=10";

var stringProperties = new Dictionary<string, string>() {
    {"region", "europe"}
};

var numericProperties = new Dictionary<string, int>() {
    {"rank", 8}
};

var matchmakerTicket = await socket.AddMatchmakerAsync(query, 2, 4, stringProperties, numericProperties);
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
auto successCallback = [](const NMatchmakerTicket& ticket)
{
    std::cout << "Matchmaker ticket: " << ticket.ticket << std::endl;
};

int32_t minCount = 2;
int32_t maxCount = 4;
string query = "+properties.region:europe +properties.rank:>=5 +properties.rank:<=10";
NStringMap stringProperties;
NStringDoubleMap numericProperties;

stringProperties.emplace("region", "europe");
numericProperties.emplace("rank", 8.0);

rtClient->addMatchmaker(minCount, maxCount, query, stringProperties, numericProperties, successCallback);
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
String query = "+properties.region:europe +properties.rank:>=5 +properties.rank:<=10";
int minCount = 2;
int maxCount = 4;

Map<String, String> stringProperties = new HashMap<String, String>() {{
    put("region", "europe");
}};

Map<String, Double> numericProperties = new HashMap<String, Double>() {{
    put("rank", 8.0);
}};

MatchmakerTicket matchmakerTicket = socket.addMatchmaker(query, minCount, maxCount, stringProperties, numericProperties).get();
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
var query = "+properties.region:europe +properties.rank:>=5 +properties.rank:<=10"
var string_properties = { "region": "europe"}
var numeric_properties = { "rank": 8 }

var matchmaker_ticket : NakamaRTAPI.MatchmakerTicket = yield(
    socket.add_matchmaker_async(query, 2, 4, string_properties, numeric_properties),
    "completed"
)

if matchmaker_ticket.is_exception():
    print("An error occurred: %s" % matchmaker_ticket)
    return

print("Got ticket: %s" % [matchmaker_ticket])

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.

와일드카드 쿼리 "*"을(를) 사용하여 상대방의 속성을 무시하고 어떤 상대방하고도 일치시킬 수 있습니다:

Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const query = "*";
const minCount = 2;
const maxCount = 4;

const stringProperties = {
  region: "europe"
};

const numericProperties = {
  rank: 8
};

var ticket = await socket.addMatchmaker(query, minCount, maxCount, stringProperties, numericProperties);
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
var query = "*";
var minCount = 2;
var maxCount = 4;

var stringProperties = new Dictionary<string, string>() {
    {"region", "europe"}
};

var numericProperties = new Dictionary<string, int>() {
    {"rank", 8}
};

var matchmakerTicket = await socket.AddMatchmakerAsync(query, minCount, maxCount, stringProperties, numericProperties);
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
auto successCallback = [](const NMatchmakerTicket& ticket)
{
    std::cout << "Matchmaker ticket: " << ticket.ticket << std::endl;
};

int32_t minCount = 2;
int32_t maxCount = 4;
string query = "*";
NStringMap stringProperties;
NStringDoubleMap numericProperties;

stringProperties.emplace("region", "europe");
numericProperties.emplace("rank", 8.0);

rtClient->addMatchmaker(minCount, maxCount, query, stringProperties, numericProperties, successCallback);
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
String query = "*";
int minCount = 2;
int maxCount = 4;

Map<String, String> stringProperties = new HashMap<String, String>() {{
    put("region", "europe");
}};

Map<String, Double> numericProperties = new HashMap<String, Double>() {{
    put("rank", 8.0);
}};

MatchmakerTicket matchmakerTicket = socket.addMatchmaker(query, minCount, maxCount, stringProperties, numericProperties).get();
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
var query = "*"
var min_count = 2
var max_count = 4
var string_properties = { "region": "europe" }
var numeric_properties = { "rank": 8 }

var matchmaker_ticket : NakamaRTAPI.MatchmakerTicket = yield(
    socket.add_matchmaker_async(query, min_count, max_count, string_properties, numeric_properties),
    "completed"
)

if matchmaker_ticket.is_exception():
    print("An error occurred: %s" % matchmaker_ticket)
    return

print("Got ticket: %s" % [matchmaker_ticket])

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.

매치메이커에 사용자를 추가할 때 before 후크를 사용하여 쿼리를 정식으로 제어할 수도 있습니다:

Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
var (
	errInternal = runtime.NewError("internal server error", 13)
)

func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
	initializer.RegisterBeforeRt("MatchmakerAdd", func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, in *rtapi.Envelope) (*rtapi.Envelope, error) {
		message, ok := in.Message.(*rtapi.Envelope_MatchmakerAdd)
		if !ok {
			return nil, errInternal
		}

		// Force the matchmaking request to use the * query
		message.MatchmakerAdd.Query = "*"

		return in, nil
	})

	return nil
}
Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function InitModule(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
  initializer.registerRtBefore("MatchmakerAdd", beforeMatchmakerAdd)
}

const beforeMatchmakerAdd : nkruntime.RtBeforeHookFunction<nkruntime.EnvelopeMatchmakerAdd> = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, envelope: nkruntime.EnvelopeMatchmakerAdd) : nkruntime.EnvelopeMatchmakerAdd | void {
  // Force the matchmaking request to use the * query
  envelope.matchmakerAdd.query = "*";

  return envelope;
}
Server
1
2
3
4
5
6
nk.register_rt_before(function(context, payload)
  -- Force the matchmaking request to use the * query
  payload.matchmaker_add.query = "*"

  return payload
end, "MatchmakerAdd")

기준 확장 #

매치메이킹 기간Duration

매치메이킹 요청의 성공 또는 대결을 찾는 데 필요한 시간은 모두 매치메이커에서 활성화된 사용자 풀과 대결에서 찾는 특정 기준에 따라 달라지므로 보장되지 않습니다.

동일한 요청을 반복적으로 제출해도 결과는 같습니다. 이러한 이유로 매치메이킹 요청에 인위적인 시간 제한을 두는 것은 권장되지 않습니다.

활성 사용자 수와 매치메이킹에 사용된 해당 기준에 따라 원하는 정확한 대결을 찾기가 어렵거나 불가능할 수 있습니다.

사용 중인 기준을 효과적으로 “확장"하려면 플레이어는 티켓마다 이전 티켓보다 더 허용적인 쿼리가 있는 여러 티켓을 제출해야 합니다.

예를 들어, 플레이어가 해당 지역에 있고 정확히 동일한 기술 수준에 있는 다른 플레이어와 대결하기를 원하지만 결과를 얻지 못하는 경우, 후속 티켓은 다른 지역을 포함하도록 확장될 수 있고 플레이어의 수준과 비슷한 기술 수준 _범위_가 허용될 수 있습니다.

Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
let query = "+properties.region:europe +properties.rank:5";
const minCount = 2;
const maxCount = 4;

const stringProperties = {
  region: "europe"
};

const numericProperties = {
  rank: 8
};

var ticket = await socket.addMatchmaker(query, minCount, maxCount, stringProperties, numericProperties);

// ... if no match is found within a certain time, request a new ticket with looser criteria
query = "+properties.region:europe +properties.rank:>=3 +properties.rank:<=7";
var newTicket = await socket.addMatchmaker(query, minCount, maxCount, stringProperties, numericProperties);
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var query = "+properties.region:europe +properties.rank:5";

var stringProperties = new Dictionary<string, string>() {
    {"region", "europe"}
};

var numericProperties = new Dictionary<string, int>() {
    {"rank", 8}
};

var matchmakerTicket = await socket.AddMatchmakerAsync(query, 2, 4, stringProperties, numericProperties);

// ... if no match is found within a certain time, request a new ticket with looser criteria
query = "+properties.region:europe +properties.rank:>=3 +properties.rank:<=7";
var newMatchmakerTicket = await socket.AddMatchmakerAsync(query, 2, 4, stringProperties, numericProperties);
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
auto successCallback = [](const NMatchmakerTicket& ticket)
{
    std::cout << "Matchmaker ticket: " << ticket.ticket << std::endl;
};

int32_t minCount = 2;
int32_t maxCount = 4;
string query = "+properties.region:europe +properties.rank:5";
NStringMap stringProperties;
NStringDoubleMap numericProperties;

stringProperties.emplace("region", "europe");
numericProperties.emplace("rank", 8.0);

rtClient->addMatchmaker(minCount, maxCount, query, stringProperties, numericProperties, successCallback);

// ... if no match is found within a certain time, request a new ticket with looser criteria
query = "+properties.region:europe +properties.rank:>=3 +properties.rank:<=7";
rtClient->addMatchmaker(minCount, maxCount, query, stringProperties, numericProperties, successCallback);
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
String query = "+properties.region:europe +properties.rank:5";
int minCount = 2;
int maxCount = 4;

Map<String, String> stringProperties = new HashMap<String, String>() {{
    put("region", "europe");
}};

Map<String, Double> numericProperties = new HashMap<String, Double>() {{
    put("rank", 8.0);
}};

MatchmakerTicket matchmakerTicket = socket.addMatchmaker(query, minCount, maxCount, stringProperties, numericProperties).get();

// ... if no match is found within a certain time, request a new ticket with looser criteria
query = "+properties.region:europe +properties.rank:>=3 +properties.rank:<=7";
MatchmakerTicket newMatchmakerTicket = socket.addMatchmaker(query, minCount, maxCount, stringProperties, numericProperties).get();
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
var query = "+properties.region:europe +properties.rank:>=5 +properties.rank:<=10"
var string_properties = { "region": "europe"}
var numeric_properties = { "rank": 8 }

var matchmaker_ticket : NakamaRTAPI.MatchmakerTicket = yield(
    socket.add_matchmaker_async(query, 2, 4, string_properties, numeric_properties),
    "completed"
)

if matchmaker_ticket.is_exception():
    print("An error occurred: %s" % matchmaker_ticket)
    return

print("Got ticket: %s" % [matchmaker_ticket])

// ... if no match is found within a certain time, request a new ticket with looser criteria
query = "+properties.region:europe +properties.rank:>=3 +properties.rank:<=7";
var new_matchmaker_ticket : NakamaRTAPI.MatchmakerTicket = yield(
    socket.add_matchmaker_async(query, 2, 4, string_properties, numeric_properties),
    "completed"
)

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.

매치메이커 티켓 #

매치메이커 풀에 추가되는 사용자에게는 매치메이커에서 자신의 상태를 나타내는 고유 식별자인 티켓이 제공됩니다.

사용자는 지정된 시간에 각각 다른 기준 세트를 나타내는 여러 매치메이커 티켓을 가질 수 있습니다. 예를 들어, 모두 무료 대결에서 플레이할 수 있는 상대방을 찾는 티켓과 자신의 지역에서 깃발 뺏기 대결에서 플레이할 플레이어를 찾는 티켓을 들 수 있습니다.

이 티켓은 서버가 일치 성공을 클라이언트에 알릴 때 사용됩니다. 이를 통해 동일한 사용자에 대해 가능한 여러 매치메이커 작업이 구분됩니다. 한 티켓에 대한 매치가 성공해도 해당 사용자가 연 다른 티켓이 자동으로 취소되는 것은 아닙니다.

해당 사용자는 티켓이 처리되기 전이라면 언제든지 티켓을 취소할 수 있습니다.

티켓 제거 #

이전에 제출한 매치메이커 요청을 더 이상 원하지 않는 경우 사용자는 해당 매치메이커 티켓을 취소할 수 있습니다:

Client
1
socket.removeMatchmaker(ticket);
Client
1
2
// "matchmakerTicket" is returned by the matchmaker.
await socket.RemoveMatchmakerAsync(matchmakerTicket);
Client
1
2
3
4
5
// "ticket" is returned by the matchmaker.
rtClient->removeMatchmaker(ticket, []()
{
    std::cout << "removed from Matchmaker" << std::endl;
});
Client
1
2
// "matchmakerTicket" is returned by the matchmaker.
socket.removeMatchmaker(matchmakerTicket.getTicket()).get();
Client
1
2
3
4
5
6
7
var removed : NakamaAsyncResult = yield(socket.remove_matchmaker_async(matchmaker_ticket.ticket), "completed")

if removed.is_exception():
    print("An error occurred: %s" % removed)
    return

print("Removed from matchmaking %s" % [matchmaker_ticket.ticket])

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.

이렇게 하면 지정된 티켓만 취소되고 매치메이킹 풀에 있을 수 있는 사용자의 다른 요청은 영향을 받지 않습니다.

매치메이킹 중에 사용자가 연결을 끊으면 열려 있는 티켓은 자동으로 취소됩니다. 사용자가 즉시 재접속해도 이전 티켓은 복원되지 않습니다.

매치메이커 결과 #

매치메이커가 항상 즉시적인 과정은 아닙니다. 현재 연결된 사용자에 따라 매치메이커가 완료하는 데 시간이 걸릴 수 있으며 결과적으로 상대방 목록을 비동기적으로 반환합니다.

클라이언트는 서버가 매치메이커 결과를 보낼 때 트리거되는 이벤트 핸들러를 등록해야 합니다.

Client
1
2
3
4
socket.onmatchmakermatched = (matched) => {
  console.info("Received MatchmakerMatched message: ", matched);
  console.info("Matched opponents: ", matched.users);
};
Client
1
2
3
4
5
6
socket.ReceivedMatchmakerMatched += matched =>
{
    Console.WriteLine("Received: {0}", matched);
    var opponents = string.Join(",\n  ", matched.Users); // printable list.
    Console.WriteLine("Matched opponents: [{0}]", opponents);
};
Client
1
2
3
4
rtListener->setMatchmakerMatchedCallback([](NMatchmakerMatchedPtr matched)
{
    std::cout << "Matched! matchId: " << matched->matchId << std::endl;
});
Client
1
2
3
4
5
6
7
SocketListener listener = new AbstractSocketListener() {
    @Override
    public void onMatchmakerMatched(final MatchmakerMatched matched) {
        System.out.format("Received MatchmakerMatched message: %s", matched.toString());
        System.out.format("Matched opponents: %s", opponents.toString());
    }
};
Client
1
2
3
4
5
6
7
func _ready():
    # First, setup the socket as explained in the authentication section.
    socket.connect("received_matchmaker_matched", self, "_on_matchmaker_matched")

func _on_matchmaker_matched(p_matched : NakamaRTAPI.MatchmakerMatched):
    print("Received MatchmakerMatched message: %s" % [p_matched])
    print("Matched opponents: %s" % [p_matched.users])

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.

매치메이커 결과에는 매치된 모든 사용자와 각각의 [properties]이(가) 포함됩니다.

결과에는 이 일치하는 플레이어 그룹의 새 대결에 가입하는 데 사용할 수 있는 토큰 또는 대결 ID도 포함됩니다. 포함되는 항목은 대결 유형에 따라 다릅니다. 클라이언트 중계 대결의 경우 토큰이 제공되고 서버 정식 대결의 경우 대결 ID가 제공됩니다.

정식 대결의 경우 매치메이커 결과가 반환될 때 서버 후크를 사용하여 서버에서 새 대결을 생성할 수 있습니다.

Server
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
var (
	errUnableToCreateMatch = runtime.NewError("unable to create match", 13)
)

func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
	if err := initializer.RegisterMatchmakerMatched(func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, entries []runtime.MatchmakerEntry) (string, error) {
		matchId, err := nk.MatchCreate(ctx, "lobby", map[string]interface{}{"invited": entries})
		if err != nil {
			return "", errUnableToCreateMatch
		}

		return matchId, nil
	}); err != nil {
		logger.Error("unable to register matchmaker matched hook: %v", err)
		return err
	}

	return nil
}
Server
1
2
3
4
5
6
7
8
function InitModule(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
  initializer.registerMatchmakerMatched(onMatchmakerMatched);
}

const onMatchmakerMatched : nkruntime.MatchmakerMatchedFunction = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, matches: nkruntime.MatchmakerResult[]): string | void {
  const matchId = nk.matchCreate("lobby", { "invited": matches })
  return matchId;
};
Server
1
2
3
4
nk.register_matchmaker_matched(function(context, matched_users)
    local match_id = nk.match_create("lobby", { invited = matched_users })
    return match_id
end)

대결 가입 #

클라이언트 중계 멀티플레이어에서는 매치메이커 결과 이벤트를 매치된 상대방과 새로운 대결에 가입하는 방법으로 사용하는 것이 일반적입니다. 매치된 사용자는 할당된 대결에 자동으로 가입하지 않습니다.

각 매치메이커 결과 이벤트는 클라이언트 중계 대결에 가입하는 데 사용되는 토큰이나 권위 부여 대결에 가입하는 데 사용되는 대결 ID를 전달합니다. 매치된 상대방과 함께 대결에 가입하는 데 사용할 수 있습니다.

클라이언트 중계 멀티플레이어의 경우 토큰을 통해 서버는 이러한 사용자가 함께 플레이하기를 원하고 동적으로 대결을 생성할 것임을 알 수 있습니다.

토큰은 수명이 짧으므로 가능한 한 빨리 대결에 가입하는 데 사용해야 합니다. 대결 토큰은 원치 않는 사용자가 일치하지 않은 대결에 가입하는 것을 방지하는 데에도 사용됩니다. 만료된 토큰은 더 이상 사용하거나 새로 고칠 수 없습니다.

표준 클라이언트 측 “대결 가입” 작업을 사용하여 새 대결에 가입할 수 있습니다:

Client
1
2
3
4
5
socket.onmatchmakermatched = (matched) => {
  console.info("Received MatchmakerMatched message: ", matched);
  const matchId = null;
  socket.joinMatch(matchId, matched.token);
};
Client
1
2
3
4
5
socket.ReceivedMatchmakerMatched += async matched =>
{
    Console.WriteLine("Received: {0}", matched);
    await socket.JoinMatchAsync(matched);
};
Client
1
2
3
4
5
6
7
8
9
rtListener->setMatchmakerMatchedCallback([this](NMatchmakerMatchedPtr matched)
{
    std::cout << "Matched! token: " << matched->token << std::endl;

    rtClient->joinMatchByToken(matched->token, [](const NMatch& match)
    {
        std::cout << "Joined Match!" << std::endl;
    });
});
Client
1
2
3
4
5
6
SocketListener listener = new AbstractSocketListener() {
    @Override
    public void onMatchmakerMatched(final MatchmakerMatched matched) {
        socket.joinMatchToken(matched.getToken()).get();
    }
};
Client
1
2
3
4
5
6
7
8
9
func _on_matchmaker_matched(p_matched : NakamaRTAPI.MatchmakerMatched):
    print("Received MatchmakerMatched message: %s" % [p_matched])
    var joined_match : NakamaRTAPI.Match = yield(socket.join_match_async(p_matched), "completed")

    if joined_match.is_exception():
        print("An error occurred: %s" % joined_match)
        return

    print("Joined match: %s" % [joined_match])

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.

파티 매치메이킹 #

Nakama의 실시간 파티를 통해 사용자는 지정된 세션 동안만 유지되는 단기 팀으로 뭉쳐서 함께 플레이할 수 있습니다. 일단 파티로 그룹화되면 이 플레이어들은 함께 매치메이킹을 할 수 있어 궁극적으로 같은 대결에 배정됩니다.

각 파티에는 지정된 리더가 있으며 일반적으로 해당 파티를 만든 사용자입니다. 이 리더는 매치메이킹에 사용할 기준을 설정하고 해당 파티를 매치메이커 풀에 추가합니다:

Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Register the matchmaker matched handler (the party leader and party members should all do this)
socket.onmatchmakermatched = (matched) => {
  socket.joinMatch(null, matched.token);
};

// Create a party as the party leader
const party = await socket.createParty(true, 2);

// Accept any incoming party requests
socket.onpartyjoinrequest = (request) => {
  request.presences.forEach(presence => {
    await socket.acceptPartyMember(request.party_id, presence);
  });
};

// As the leader of the party, add the entire party to the matchmaker
const ticket = await socket1.addMatchmakerParty(party.party_id, "*", 3, 4, null, null);
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Register the matchmaker matched handler (the party leader and party members should all do this)
socket.ReceivedMatchmakerMatched += async matched => await socket.JoinMatchAsync(matched);

// Create a party as the party leader
var party = await socket.CreatePartyAsync(true, 2);

// Accept any incoming party requests
socket.ReceivedPartyJoinRequest += async request =>
{
    foreach (var presence in request.Presences)
    {
        await socket.AcceptPartyMemberAsync(request.PartyId, presence);
    }
};

// As the leader of the party, add the entire party to the matchmaker
var ticket = await socket.AddMatchmakerPartyAsync(party.Id, "*", 3, 4);
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
func _ready():
  // Register the matchmaker matched handler (the party leader and party members should all do this)
  socket.connect("received_matchmaker_matched", self, "_on_matchmaker_matched")

  // Create a party as the party leader
  var party = yield(socket.create_party_async(true, 2), "completed")

  // Accept any incoming party requests
  socket.connect("received_party_join_request", self, "_on_party_join_request")

  // As the leader of the party, add the entire party to the matchmaker
  var ticket = yield(socket.add_matchmaker_party_async(party.id, "*", 3, 4), "completed");

func _on_matchmaker_matched(p_matched : NakamaRTAPI.MatchmakerMatched):
  var joined_match : NakamaRTAPI.Match = yield(socket.join_match_async(p_matched), "completed")

func _on_party_join_request(party_join_request: NakamaRTAPI.PartyJoinRequest):
  for presence in party_join_request.presences:
    yield(socket.accept_party_member_async(party_join_request.party_id, presence), "completed")

Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.

매치메이킹 프로세스의 일부로 파티 구성원은 반환된 결과에서 항상 함께 유지됩니다. 파티는 다른 파티 및 개별 사용자와 모두 매칭되어 결과적으로 대결을 형성할 수 있으며, 매치메이커에서는 둘 다 선호되지 않습니다.

예를 들어, 최대 수가 10인 경우 5명의 파티가 3명의 다른 파티와 매치된 다음 두 명의 개별 사용자를 추가하여 완전한 대결을 형성할 수 있습니다.

매치메이킹에 성공하면 파티 리더 뿐만 아니라 모든 파티 구성원들이 매치메이커 결과 콜백을 받습니다.

Related Pages