private static void StartMatchmaking(Session session, Session.EncodingScheme encoding) { // 로그인 한 Id 를 가져옵니다. string id; if (!session.GetFromContext("id", out id)) { Log.Warning("Failed to request matchmaking. Not logged in."); if (encoding == Session.EncodingScheme.kJsonEncoding) { session.SendMessage("error", Utility.MakeResponse("fail", "not logged in")); } else { Log.Assert(encoding == Session.EncodingScheme.kProtobufEncoding); FunMessage funmsg = new FunMessage(); PongErrorMessage msg = new PongErrorMessage(); msg.result = "fail"; msg.msg = "not logged in"; funmsg.AppendExtension_pong_error(msg); session.SendMessage("error", funmsg); } return; } funapi.Matchmaking.Client.MatchCallback match_cb = (string player_id, funapi.Matchmaking.Match match, funapi.Matchmaking.MatchResult result) => { JObject response = new JObject(); LobbyMatchReply reply = new LobbyMatchReply(); if (result == funapi.Matchmaking.MatchResult.kSuccess) { // Matchmaking 에 성공했습니다. Log.Info("Succeed in matchmaking: id={0}", player_id); string player_a_id = Utility.ReadStringFromJsonObject(match.Context, "A"); string player_b_id = Utility.ReadStringFromJsonObject(match.Context, "B"); Log.Assert(player_a_id != null && player_b_id != null); string opponent_id; if (player_a_id == player_id) { opponent_id = player_b_id; } else { opponent_id = player_a_id; } session.AddToContext("opponent", opponent_id); session.AddToContext("matching", "done"); session.AddToContext("ready", 0); if (encoding == Session.EncodingScheme.kJsonEncoding) { response = Utility.MakeResponse("Success"); response["A"] = player_a_id; response["B"] = player_b_id; } else { Log.Assert(encoding == Session.EncodingScheme.kProtobufEncoding); reply.result = "Success"; reply.player1 = player_a_id; reply.player2 = player_b_id; } // 유저를 Game 서버로 보냅니다. Common.Redirect(session, "game"); } else if (result == funapi.Matchmaking.MatchResult.kAlreadyRequested) { // Matchmaking 요청을 중복으로 보냈습니다. Log.Info("Failed in matchmaking. Already requested: id={0}", player_id); session.AddToContext("matching", "failed"); if (encoding == Session.EncodingScheme.kJsonEncoding) { response = Utility.MakeResponse("AlreadyRequested"); } else { Log.Assert(encoding == Session.EncodingScheme.kProtobufEncoding); reply.result = "AlreadyRequested"; } } else if (result == funapi.Matchmaking.MatchResult.kTimeout) { // Matchmaking 처리가 시간 초과되었습니다. Log.Info("Failed in matchmaking. Timeout: id={0}", player_id); session.AddToContext("matching", "failed"); if (encoding == Session.EncodingScheme.kJsonEncoding) { response = Utility.MakeResponse("Timeout"); } else { Log.Assert(encoding == Session.EncodingScheme.kProtobufEncoding); reply.result = "Timeout"; } } else { // Matchmaking 에 오류가 발생했습니다. Log.Error("Failed in matchmaking. Error: id={0}", player_id); session.AddToContext("matching", "failed"); if (encoding == Session.EncodingScheme.kJsonEncoding) { response = Utility.MakeResponse("Error"); } else { Log.Assert(encoding == Session.EncodingScheme.kProtobufEncoding); reply.result = "Error"; } } if (encoding == Session.EncodingScheme.kJsonEncoding) { session.SendMessage("match", response, Session.Encryption.kDefault); } else { FunMessage funmsg = new FunMessage(); funmsg.AppendExtension_lobby_match_repl(reply); session.SendMessage("match", funmsg, Session.Encryption.kDefault); } }; // 빈 Player Context 를 만듭니다. 지금 구현에서는 Matchmaking 서버가 // 조건 없이 Matching 합니다. Level 등의 조건으로 Matching 하려면 // 여기에 Level 등의 Matching 에 필요한 정보를 넣습니다. JObject player_ctxt = new JObject(); // Matchmaking 을 요청합니다. funapi.Matchmaking.Client.Start( (int)MatchmakingType.kMatch1vs1, id, player_ctxt, new funapi.Matchmaking.Client.MatchCallback(match_cb), funapi.Matchmaking.Client.TargetServerSelection.kMostNumberOfPlayers, null, kMatchmakingTimeout); }
public static void ProcessSpawnOrMatchmaking( Session session, JObject message, SessionResponse.SessionResponseHandler handler) { Log.Assert(session != null); // 메시지 핸들러 함수는 세션 ID 를 이벤트 태그로 하는 이벤트 위에서 실행합니다. // 아래는 이 함수를 메시지 핸들러 위에서 실행하게 강제하게 검사하는 식입니다. // 세션 ID 별로 이 함수를 직렬화하지 않으면 동시에 서로 다른 곳에서 // 이 세션에 접근할 수 있습니다. Log.Assert(Event.GetCurrentEventTag() == session.Id); // // 매치메이킹 + 데디케이티드 서버 스폰 요청 예제 // // 유저 2명, 4명, 6명을 대상으로 새로운 데디케이티드 서버 프로세스를 생성합니다. // // 클라이언트는 다음 메시지 형태로 매치메이킹을 요청합니다. // { // // Facebook ID 또는 구글+ ID 등 고유한 ID 를 사용해야 합니다. // "account_id": "id", // // 매치 타입, 이 파일에 있는 MatchType 정의 참조 // "match_type": 1 // "user_data": { // "level": 70, // "ranking_score": 1500, // ... // }, // } if (message[kAccountId] == null || message[kAccountId].Type != JTokenType.String || message[kMatchType] == null || message[kMatchType].Type != JTokenType.Integer || message[kUserData] == null || message[kUserData].Type != JTokenType.Object) { Log.Error("Missing required fields: '{0}' / '{1}' / '{2}': session_id={3}, message={4}", kAccountId, kMatchType, kUserData, session.Id, message.ToString()); handler(SessionResponse.ResponseResult.FAILED, new SessionResponse(session, 400, "Missing required fields.", new JObject())); return; } // // 매치메이킹 요청 인자 설정 가이드 // // 1. 매치 타입 // 매치 타입을 식별하는 용도로 사용합니다. 정해진 값이 없으므로 // 서버에서 정의한 매치 타입과 일치하는지 확인할 필요가 있습니다. long match_type = message[kMatchType].Value <long>(); if (!MatchmakingType.IsValidMatchType(match_type)) { Log.Error("Invalid match_type: session_id={0}, message={1}", session.Id, message.ToString()); handler(SessionResponse.ResponseResult.FAILED, new SessionResponse(session, 400, "Invalid arguments.", new JObject())); return; } // 2. 계정(account) ID // AccountManager 로 로그인한 계정 ID 를 입력합니다. string account_id = message[kAccountId].Value <string>(); // 요청한 계정과 로그인 중인 세션이 일치하는 지 검사합니다. // 이 검사를 통해 다른 유저 ID 로 매칭 요청하는 것을 방지할 수 있습니다. if (AccountManager.FindLocalSession(account_id).Id != session.Id) { Log.Info("ProcessMatchmaking denied: bad request: session_id={0}, message={1}", session.Id, message.ToString()); handler(SessionResponse.ResponseResult.FAILED, new SessionResponse(session, 400, "Access denied for this account.", new JObject())); return; } // 3. user_data, 매치메이킹 및 생성한 데디케이티드 서버 안에서 사용할 // 플레이어의 데이터 입니다. 서버는 클라이언트에서 보낸 user_data 를 복사한 후 // 요청 시간을 추가합니다. 따라서 매치메이킹 시 사용할 데이터는 최종적으로 // 다음과 같습니다. // "user_data": { // "level": 70, // "mmr_score": 1500, // "req_time": 1544167339, // ... // }, JObject user_data = message[kUserData].ToJObject(); if (user_data[MatchmakingType.kMatchLevel] == null || user_data[MatchmakingType.kMatchLevel].Type != JTokenType.Integer || user_data[MatchmakingType.kMMRScore] == null || user_data[MatchmakingType.kMMRScore].Type != JTokenType.Integer) { // 매치메이킹 요청에 필요한 인자가 부족합니다. Log.Error("Missing required fields: session_id={0}, message={1}", session.Id, message.ToString()); handler(SessionResponse.ResponseResult.FAILED, new SessionResponse(session, 400, "Missing required fields.", new JObject())); return; } // 매치메이킹 없이 스폰을 진행하는 타입이면, 즉시 스폰을 시작합니다. // 매치메이킹은 2명 이상인 경우에만 동작한다는 점에 주의해주세요. // 1명이 매치메이킹 큐에 들어가면 매치메이킹 콜백을 호출하지 않습니다. // 연습 게임 또는 데디케이티드 서버 내부에서 사람을 채운 후 게임을 시작할 때 // 이러한 방식을 사용할 수 있습니다. if (match_type == (long)MatchmakingType.MatchType.kNoMatching) { SpawnOrSendUser(account_id, user_data, match_type); return; } // 4. OnMatchCompleted 콜백 // 매칭을 종료했을 때 결과를 받을 콜백 함수를 지정합니다. // 매치 결과는 콜백 함수의 funapi.Matchmaking.Client.MatchResult 타입으로 // 확인할 수 있습니다. // 5. 서버 선택 방법 // - kRandom: 서버를 랜덤하게 선택합니다. // - kMostNumberOfPlayers; 사람이 가장 많은 서버에서 매치를 수행합니다. // - kLeastNumberOfPlayers; 사람이 가장 적은 서버에서 매치를 수행합니다. funapi.Matchmaking.Client.TargetServerSelection target_selection = funapi.Matchmaking.Client.TargetServerSelection.kRandom; // 6. OnMatchProgressUpdated 콜백 // 매치 상태를 업데이트 할 때마다 결과를 받을 콜백 함수를 지정합니다. // 타임 아웃 (기본 값: default(TimeSpan)) TimeSpan timeout = default(TimeSpan); // 이 세션에서 요청한 매치 타입을 기록해둡니다. LogMatchHistory(session, account_id, match_type); Log.Info("Requesting a matchmaking: session_id={0}, account_id={1}, match_type={2}, user_data={3}", session.Id, account_id, match_type, user_data.ToString()); funapi.Matchmaking.Client.MatchCallback match_cb = new funapi.Matchmaking.Client.MatchCallback( (string player_id, funapi.Matchmaking.Match match, funapi.Matchmaking.MatchResult result) => { OnMatchCompleted(player_id, match, result, session, handler); }); funapi.Matchmaking.Client.Start2( match_type, account_id, user_data, match_cb, target_selection, OnMatchProgressUpdated, timeout); }