클라이언트 중계 멀티플레이어 #

실시간 멀티플레이어 엔진을 사용하면 사용자가 상대방과 데이터를 빠르게 교환할 수 있는 대결을 쉽게 설정하고 가입할 수 있습니다. 이 중계된 멀티플레이어(클라이언트 부여라고도 함) 모델은 서버에 대한 권한 부여 제어가 중요하지 않은(예: 부정 행위는 중요하지 않음) 간단한 1대1 또는 협동 게임과 같은 다수의 게임 유형에 적합합니다

중계 멀티플레이어에서 Nakama의 경우 메시지 크기나 내용에 관계없이 데이터 교환이 쉽습니다. 대결을 통해 전송된 모든 데이터는 클라이언트가 요청한 대결 상대방에게 즉시 라우팅됩니다. Nakama가 유지 관리하는 유일한 대결 데이터대결 ID와 해당 대결의 현재 상태 목록입니다.

연결된 다른 클라이언트로 보내지는 클라이언트 메시지는 검사 없이 서버에 의해 전달됩니다. Nakama는 중계된 멀티플레이어 대결에서 전달된 데이터의 양이나 내용을 추적하지 않기 때문에 부정 행위 감지, 오류 수정 또는 기타 사용 가능한 기능이 없습니다. 이 방식의 경우 호스트 역할을 하기 위해 클라이언트 자신이 결정한 각 대결에서 한 클라이언트에 의존합니다. 이 호스트는 피어 간의 상태 변경을 조정하고 잘못된 클라이언트에서 보낸 애매하거나 악의적인 메시지를 중재합니다.

모든 사용자는 다른 사용자와 대결에 참여할 수 있으며, 대결을 암호로 차단하거나 액세스를 제한할 수 없습니다. 대결에서 플레이어 수에 대한 명시적인 제한은 없으며 게임 디자인(메시지 크기 및 빈도)과 사용 가능한 리소스(서버 하드웨어 및 네트워크 기능)에 따른 실제적인 제한만 있습니다.

사용자는 클라이언트에서 보낸 메시지로 대결을 만들고 가입하고 나갈 수 있습니다.

대결은 마지막 참가자가 종료하기 전까지만 서버에 존재합니다. 메모리에 보관되지만 지속되지는 않습니다.

대결 생성 #

모든 사용자는 “대결 생성” 작업을 명시적으로 호출하여 새 대결을 만들 수 있습니다. 대결을 만들 때 생성자는 대결 상태를 제공할 수 없습니다.

서버는 새 대결에 고유 ID를 할당합니다. 이 ID는 다른 사용자가 대결에 가입할 수 있도록 공유할 수 있습니다.

Client
1
2
var response = await socket.createMatch();
console.log("Created match with ID:", response.match.match_id);
Client
1
2
var match = await socket.CreateMatchAsync();
Console.WriteLine("New match with id '{0}'.", match.Id);
Client
1
2
3
4
rtClient->createMatch([](const NMatch& match)
{
    std::cout << "Created Match with ID: " << match.matchId << std::endl;
});
Client
1
2
Match match = socket.createMatch().get();
System.out.format("Created match with ID %s.", match.getId());
Client
1
2
3
4
5
6
7
var created_match : NakamaRTAPI.Match = yield(socket.create_match_async(), "completed")

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

print("New match with id %s.", created_match.match_id)
Client
1
2
3
4
5
6
7
8
local result = socket.match_create()

if result.error then
  print(result.error.message)
  return
end

print("Created match with ID", result.match.match_id)

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.

사용자는 새 대결을 생성할 때 name을(를) 선택적으로 제공할 수 있습니다. 이 이름은 대결 ID를 생성하는 데 사용됩니다. 즉, 동일한 이름으로 대결을 생성하는 두 명의 플레이어는 동일한 대결 ID를 갖게 되며 결과적으로 동일한 대결이 됩니다.

Client
1
2
var matchName = "Heroes";
var match = await socket.CreateMatchAsync(matchName);
Client
1
2
var match_name = "Heroes";
var match : NakamaRTAPI.Match = yield(socket.create_match_async(matchName), "completed");

Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
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 JavaScript/Cocos2d-js 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 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.

사용자는 언제든지 대결을 종료할 수 있으며, 이는 다른 모든 사용자에게 통보됩니다. 모든 사용자가 대결을 종료하면 해당 대결은 사라집니다.

대결 가입 #

사용자는 ID를 사용하여 특정 대결에 가입할 수 있습니다. 대결은 암호로 차단되거나 닫을 수 없습니다. 대결 ID를 갖고 있는 사용자는 가입할 수 있습니다. 마지막 참가자가 종료하기 전이라면 언제든지 대결에 가입할 수 있습니다.

Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var id = "<matchid>";
var match = await socket.joinMatch(id);

var connectedOpponents = match.presences.filter((presence) => {
  // Remove your own user from list.
  return presence.user_id != match.self.user_id;
});

connectedOpponents.forEach((opponent) => {
  console.log("User id %o, username %o.", opponent.user_id, opponent.username);
});
Client
1
2
3
4
5
6
7
var matchId = "<matchid>";
var match = await socket.JoinMatchAsync(matchId);

foreach (var presence in match.Presences)
{
    Console.WriteLine("User id '{0}' name '{1}'.", presence.UserId, presence.Username);
}
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
string matchId = "<matchid>";
rtClient->joinMatch(matchId, {}, [](const NMatch& match)
{
    std::cout << "Joined Match!" << std::endl;

    for (auto& presence : match.presences)
    {
        if (presence.userId != match.self.userId)
        {
            std::cout << "User id " << presence.userId << " username " << presence.username << std::endl;
        }
    }
});
Client
1
2
3
4
5
6
String matchId = "<matchid>";
Match match = socket.joinMatch(matchId).get();

for (UserPresence presence : match.getPresences()) {
    System.out.format("User id %s name %s.", presence.getUserId(), presence.getUsername());
}
Client
1
2
3
4
5
6
7
8
9
var match_id = "<matchid>"
var joined_match = yield(socket.join_match_async(match_id), "completed")

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

for presence in joined_match.presences:
    print("User id %s name %s'." % [presence.user_id, presence.username])
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
local match_id = "<matchid>"
local result = socket.match_join(match_id)

if result.error then
  print(result.error.message)
  return
end

for _,user in  ipairs(result.match.presences) do
  print("User id", user.user_id, "name", user.name)
end

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
14
15
16
17
18
var connectedOpponents = [];
socket.onmatchpresence = (presences) => {
  // Remove all users who left.
  connectedOpponents = connectedOpponents.filter(function(co) {
    var stillConnectedOpponent = true;

    presences.leaves.forEach((leftOpponent) => {
      if (leftOpponent.user_id == co.user_id) {
        stillConnectedOpponent = false;
      }
    });

    return stillConnectedOpponent;
  });

  // Add all users who joined.
  connectedOpponents = connectedOpponents.concat(presences.joins);
};
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var connectedOpponents = new List<IUserPresence>(2);

socket.ReceivedMatchPresence += presenceEvent =>
{
    foreach (var presence in presenceEvent.Leaves)
    {
        connectedOpponents.Remove(presence);
    }

    connectedOpponents.AddRange(presenceEvent.Joins);

    // Remove yourself from connected opponents.
    connectedOpponents.Remove(self);
    Console.WriteLine("Connected opponents: [{0}]", string.Join(",\n  ", connectedOpponents));
};
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
rtListener->setMatchPresenceCallback([](const NMatchPresenceEvent& event)
{
    for (auto& presence : event.joins)
    {
        std::cout << "Joined user: " << presence.username << std::endl;
    }

    for (auto& presence : event.leaves)
    {
        std::cout << "Left user: " << presence.username << std::endl;
    }
});
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
List<UserPresence> connectedOpponents = new ArrayList<UserPresence>();

public void onMatchPresence(final MatchPresenceEvent matchPresence) {
    connectedOpponents.addAll(matchPresence.getJoins());

    for (UserPresence leave : matchPresence.getLeaves()) {
        for (int i = 0; i < connectedOpponents.size(); i++) {
            if (connectedOpponents.get(i).getUserId().equals(leave.getUserId())) {
                connectedOpponents.remove(i);
            }
        }
    };
});
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
var connected_opponents = {}

func _ready():
    # First, setup the socket as explained in the authentication section.
    socket.connect("received_match_presence", self, "_on_match_presence")

func _on_match_presence(p_presence : NakamaRTAPI.MatchPresenceEvent):
    for p in p_presence.joins:
        connected_opponents[p.user_id] = p

    for p in p_presence.leaves:
        connected_opponents.erase(p.user_id)

    print("Connected opponents: %s" % [connected_opponents])
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
local connected_opponents = {}
socket.on_matchpresence(function(message)
  for _,p in ipairs(message.match_presence_event.leaves) do
    connected_opponents[p.user_id] = nil
  end

  for _,p in ipairs(message.match_presence_event.joins) do
    connected_opponents[p.user_id] = p
  end
end)

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에서는 메시지를 전송된 순서가 아닌 받은 순서대로 브로드캐스트합니다.

각 데이터 메시지 내의 바이너리 컨텐츠는 1500 바이트라는 최대 전송 단위(MTU) 내에서 최대한 작아야 합니다. JSON을 사용하는 것이 일반적이며 프로토콜 버퍼 또는 FlatBuffers와 같은 컴팩트 바이너리 형식을 사용하는 것이 좋습니다.

메시지 크기 및/또는 빈도를 더 줄일 수 없는 경우 메시지를 더 적게 보내는 게 가장 좋습니다. 예를 들어, 초당 1000 바이트 메시지 1개가 초당 200 바이트 메시지 5개보다 낫습니다.

각 메시지를 특정 “명령"으로 식별하기 위해 메시지에는 작업 코드와 페이로드가 포함되어 있습니다.

Client
1
2
3
4
var id = "<matchid>";
var opCode = 1;
var data = { "move": {"dir": "left", "steps": 4} };
socket.sendMatchState(id, opCode, data);
Client
1
2
3
4
5
// using Nakama.TinyJson;
var matchId = "<matchid>";
var opCode = 1;
var newState = new Dictionary<string, string> {{"hello", "world"}}.ToJson();
socket.SendMatchStateAsync(matchId, opCode, newState);
Client
1
2
3
4
string id = "<matchid>";
int64_t opCode = 1;
NBytes data = "{ "move": {"dir": "left", "steps" : 4} }";
rtClient->sendMatchData(id, opCode, data);
Client
1
2
3
4
String id = "<matchid>";
int opCode = 1;
String data = "{"message":"Hello world"}";
socket.sendMatchData(id, opCode, data);
Client
1
2
3
4
var match_id = "<matchid>"
var op_code = 1
var new_state = {"hello": "world"}
socket.send_match_state_async(match_id, op_code, JSON.print(new_state))
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
local match_id = "<matchid>"
local op_code = 1

local data = json.encode({
  move = {
    dir = "left",
    steps = 4
  }
})

local result = socket.match_data_send(match_id, op_code, data)

if result.error then
  print(result.error.message)
  return
end

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
// Only send data to the first presence in the match presences array
socket.sendMatchState(id, opCode, data, [match.presences[0]]);
Client
1
2
// Only send data to the first presence in the match presences array
await socket.SendMatchStateAsync(matchId, opCode, newState, new [] { match.presences.First() });
Client
1
2
// Only send data to the first presence in the match presences array
rtClient->sendMatchData(id, opCode, data, { match.presences[0] });
Client
1
2
// Only send data to the first presence in the match presences array
socket.sendMatchData(id, opCode, data, match.getPresences().get(0));
Client
1
2
// Only send data to the first presence in the match presences array
socket.send_match_state_async(match_id, op_code, JSON.print(new_state), [current_match.presences[0]])
Client
1
2
-- Only send data to the first presence in the match presences array
local result = socket.match_data_send(match_id, op_code, data, { match.presences[0] })

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.

작업 코드 #

작업 코드는 전송된 메시지 유형의 숫자 식별자입니다. 메시지를 디코딩하기 전에 작업 코드를 통해 메시지의 목적과 내용을 쉽게 확인할 수 있습니다.

작업 코드를 사용하여 다음과 같은 특정 사용자 작업에 속하는 게임플레이 내에서 명령을 정의할 수 있습니다:

  • 초기 상태 동기화
  • 준비 상태
  • Ping / Pong
  • 게임 상태 업데이트
  • 이모티콘

구현에 관한 예는 Fish Game 튜토리얼을 참조하세요.

데이터 메시지 수신 #

서버는 클라이언트에서 보내는 데이터 메시지를 처리하는 순서대로 데이터를 전달합니다. 클라이언트는 수신되는 대결 데이터 메시지에 대한 콜백을 추가할 수 있습니다. 이것은 사용자가 대결을 생성(또는 가입)하고 종료하기 전에 수행되어야 합니다.

Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
socket.onmatchdata = (result) => {
  var content = result.data;

  switch (result.op_code) {
    case 101:
      console.log("A custom opcode.");
      break;
    default:
      console.log("User %o sent %o", result.presence.user_id, content);
  }
};
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Use whatever decoder for your message contents.
var enc = System.Text.Encoding.UTF8;
socket.ReceivedMatchState += newState =>
{
    var content = enc.GetString(newState.State);

    switch (newState.OpCode)
    {
        case 101:
            Console.WriteLine("A custom opcode.");
            break;
        default:
            Console.WriteLine("User '{0}'' sent '{1}'", newState.UserPresence.Username, content);
    }
};
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
rtListener->setMatchDataCallback([](const NMatchData& data)
{
    switch (data.opCode)
    {
        case 101:
            std::cout << "A custom opcode." << std::endl;
            break;
        default:
            std::cout << "User " << data.presence.userId << " sent " << data.data << std::endl;
            break;
    }
});
Client
1
2
3
4
5
6
SocketListener listener = new AbstractSocketListener() {
    @Override
    public void onMatchData(final MatchData matchData) {
        System.out.format("Received match data %s with opcode %d", matchData.getData(), matchData.getOpCode());
    }
};
Client
1
2
3
4
5
6
func _ready():
    # First, setup the socket as explained in the authentication section.
    socket.connect("received_match_state", self, "_on_match_state")

func _on_match_state(p_state : NakamaRTAPI.MatchData):
    print("Received match state with opcode %s, data %s" % [p_state.op_code, parse_json(p_state.data)])
Client
1
2
3
4
socket.on_matchdata(function(message)
  local data = json.decode(message.match_data.data)
  local op_code = tonumber(message.match_data.op_code)
end)

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
var id = "<matchid>";
socket.leaveMatch(id);
Client
1
2
var matchId = "<matchid>";
await socket.LeaveMatchAsync(matchId);
Client
1
2
string matchId = "<matchid>";
rtClient->leaveMatch(matchId);
Client
1
2
String matchId = "<matchid>";
socket.leaveMatch(matchId).get();
Client
1
2
3
4
5
6
var match_id = "<matchid>"
var leave : NakamaAsyncResult = yield(socket.leave_match_async(match_id), "completed")
if leave.is_exception():
    print("An error occurred: %s" % leave)
    return
print("Match left")
Client
1
2
3
4
5
6
local match_id = "<matchid>"
local result = socket.match_leave(match_id)
if result.error then
  print(result.error.message)
  return
end

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.

모든 사용자가 종료하면 대결은 끝납니다. 그러면 해당 ID가 무효가 되며 이를 다시 사용하여 재가입할 수 없습니다.

#

대결 호스트 순환 #

연결된 클라이언트 중에서 대결 호스트를 선택하는 방법을 결정해야 합니다. 이는 모든 클라이언트가 동일한 호스트를 확인하도록 하면서도 대결 참가자 간에 “협상"이 필요하지 않으므로 가장 잘 구현됩니다.

대결 현재 상태를 결정적으로 정렬하고 원하는 요소를 기반으로 호스트를 선택하는 방식으로 이룰 수 있습니다. 아래 예에서는 현재 상태 목록을 정렬하고 가장 낮게 인덱싱된 세션 ID를 호스트로 선택합니다.

Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Declare a variable to store which presence is the host
var hostPresence;

// Upon receiving a matchmaker matched event, deterministically calculate the host by sorting the session Ids
socket.onmatchmakermatched = (matchmakerMatched) => {
  var hostSessionId = matchmakerMatched.users.map(user => user.presence.session_id).sort();
  hostPresence = matchmakerMatched.users.filter(user => user.presence.session_id == hostSessionId)[0];
};

// When receiving a match presence event, check if the host left and if so recalculate the host presence
socket.onmatchpresence = (matchPresence) => {
  if (matchPresence.leaves.find(presence => presence.user_id === hostPresence.user_id))
  {
    var hostSessionId = match.presences.map(presence => presence.session_id).sort();
    hostPresence = match.presences.filter(presence => presence.session_id === hostSessionId)[0];
  }
};
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Declare a variable to store which presence is the host
IUserPresence hostPresence;

// Upon receiving a matchmaker matched event, deterministically calculate the host by sorting the session Ids
socket.ReceivedMatchmakerMatched += matched =>
{
  hostPresence = matched.Users.OrderBy(x => x.Presence.SessionId).First().Presence;
};

// When receiving a match presence event, check if the host left and if so recalculate the host presence
socket.ReceivedMatchPresence += matchPresenceEvent =>
{
  if (matchPresenceEvent.Leaves.Any(x => x.UserId == hostPresence.UserId))
  {
    hostPresence = match.Presences.OrderBy(x => x.SessionId).First();
  }
};
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Declare a variable to store which presence is the host
NUserPresence hostPresence;

// Upon receiving a matchmaker matched event, deterministically calculate the host by sorting the session Ids
listener.setMatchmakerMatchedCallback([&done, &hostPresence](NMatchmakerMatchedPtr matchmakerMatched) {
    std::sort(matchmakerMatched->users.begin(), matchmakerMatched->users.end(), [](const NMatchmakerUser lhs, const NMatchmakerUser rhs) {
        return lhs.presence.sessionId < rhs.presence.sessionId;
    });
    hostPresence = matchmakerMatched->users[0].presence;
});

// When receiving a match presence event, check if the host left and if so recalculate the host presence
listener.setMatchPresenceCallback([&match, &hostPresence](NMatchPresenceEvent matchPresenceEvent) {
    for (int i = 0; i < matchPresenceEvent.leaves.size(); i++) {
        if (matchPresenceEvent.leaves[i].sessionId == hostPresence.sessionId) {
            std::sort(match.presences.begin(), match.presences.end(), [](const NUserPresence lhs, const NUserPresence rhs) {
                return lhs.sessionId < rhs.sessionId;
            });
            hostPresence = match.presences[0];
        }
    }
});
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
// Declare a variable to store which presence is the host (as a final 1 length array so we can access it correctly)
final UserPresence[] hostPresence = new UserPresence[1];

SocketListener socketListener = new SocketListener() {
  // Upon receiving a matchmaker matched event, deterministically calculate the host by sorting the session Ids
  @Override
  public void onMatchmakerMatched(MatchmakerMatched matchmakerMatched) {
    List<MatchmakerUser> users = matchmakerMatched.getUsers();
    users.sort((a, b) -> String.CASE_INSENSITIVE_ORDER.compare(a.getPresence().getSessionId(), b.getPresence().getSessionId()));
    hostPresence[0] = users.get(0).getPresence();
  }

  // When receiving a match presence event, check if the host left and if so recalculate the host presence
  @Override
  public void onMatchPresence(MatchPresenceEvent e) {
    if (e.getLeaves() != null) {
      e.getLeaves().forEach(presence -> {
        if (presence.getSessionId() == hostPresence[0].getSessionId()) {
          List<UserPresence> matchPresences = match.getPresences();
          matchPresences.sort((a, b) -> String.CASE_INSENSITIVE_ORDER.compare(a.getSessionId(), b.getSessionId()));
          hostPresence[0] = matchPresences.get(0);
        }
      });
    }
  }
}
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
# Declare a variable to store which presence is the host
var host_presence : NakamaRTAPI.UserPresence

# Define comparer functions
func _presence_comparer(a : NakamaRTAPI.UserPresence, b : NakamaRTAPI.UserPresence):
	return a.session_id < b.session_id

func _user_comparer(a : NakamaRTAPI.MatchmakerUser, b : NakamaRTAPI.MatchmakerUser):
	return a.presence.session_id < b.presence.session_id

# Upon receiving a matchmaker matched event, deterministically calculate the host by sorting the session Ids
func _on_matchmaker_matched(matchmaker_matched : NakamaRTAPI.MatchmakerMatched):
	matchmaker_matched.users.sort_custom(self, "_user_comparer")
	host_presence = matchmaker_matched.users[0].presence
	current_match = yield(socket.join_match_async(matchmaker_matched.match_id), "completed")

# When receiving a match presence event, check if the host left and if so recalculate the host presence
func _on_match_presence(match_presence_event : NakamaRTAPI.MatchPresenceEvent):
	for presence in match_presence_event.leaves:
		if presence.session_id == host_presence.session_id:
			current_match.presences.sort_custom(self, "_presence_comparer")
			if len(current_match.presences) < 1:
				host_presence = current_match.self_user
			else:
				host_presence = current_match.presences[0]

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.

Related Pages