이 클라이언트 라이브러리 가이드에서는 Among Us(외부)에서 영감을 받은 Sagi-shi(“사기꾼"을 뜻하는 일본어)라는 게임에서 Nakama 고유의 부품을 개발하는 방법(전체 게임 로직이나 UI를 사용하지 않음)을 통해 **C++**에서 Nakama의 핵심 기능을 사용하는 방법에 대해서 설명합니다.
booldone=false;autologinFailedCallback=[&done](constNError&error){cout<<"Failed to login"<<endl;cout<<error.message<<endl;done=true;};autologinSucceededCallback=[&done,&rtClient](NSessionPtrsession){cout<<"Login successful"<<endl;cout<<session->getAuthToken()<<endl;rtClient->connect(session,true);};stringdeviceId="e872f976-34c1-4c41-88fe-fd6aef118782";client->authenticateDevice(deviceId,opt::nullopt,opt::nullopt,{},loginSucceededCallback,loginFailedCallback);
autoerrorCallback=[](constNError&error){cout<<"An error occurred: "<<error.message<<endl;if(error.code==ErrorCode::ConnectionError){cout<<"The server is currently unavailable. Check internet connection."<<endl;}};client->getAccount(session,successCallback,errorCallback);
// Typically you would get the system's unique device identifier here.
stringdeviceId="e872f976-34c1-4c41-88fe-fd6aef118782";autologinFailedCallback=[&done](constNError&error){cout<<"An error occurred: "<<error.message<<endl;};autologinSucceededCallback=[&done,&rtClient](NSessionPtrsession){cout<<"Successfully authenticated: "<<session->getAuthToken()<<endl;};// Authenticate with the Nakama server using Device Authentication.
client->authenticateDevice(deviceId,opt::nullopt,opt::nullopt,{},loginSucceededCallback,loginFailedCallback);
Nakama Facebook 인증은 사용자의 Facebook 친구를 선택하여 Nakama 친구 목록에 추가할 수 있는 인증 방법입니다.
1
2
3
4
5
6
7
8
9
10
11
12
// Authenticate with the Nakama server using Facebook Authentication.
stringaccessToken="<Token>";boolimportFriends=true;client->authenticateFacebook(accessToken,"mycustomusername",true,importFriends,{},loginSucceededCallback,loginFailedCallback);
사용자 인증을 완료한 경우, 사용자는 계정에서 Nakama 연결 인증 방법을 사용할 수 있습니다.
장치 ID 인증 연결
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
autolinkSuccessCallback=[](){cout<<"Successfully linked Device ID authentication to existing player account"<<endl;};autolinkErrorCallback=[](constNError&error){cout<<"Error linking Device ID: "<<error.message<<endl;};// Link Device Authentication to existing player account.
client->linkDevice(session,deviceId,linkSuccessCallback,linkErrorCallback);
Facebook 인증 연결
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
autolinkSuccessCallback=[](){cout<<"Successfully linked Facebook authentication to existing player account"<<endl;};autolinkErrorCallback=[](constNError&error){cout<<"Error linking Facebook: "<<error.message<<endl;};client->linkFacebook(session,accessToken,importFriends,linkSuccessCallback,linkErrorCallback);
// Check whether a session has expired or is close to expiry.
if(session->isExpired()||session->isExpired(time(0)+24\*60\*60)){autorefreshSuccessCallback=[](NSessionPtrsession){cout<<"Session successfully refreshed"<<endl;};autorefreshErrorCallback=[](constNError&error){// Couldn't refresh the session so reauthenticate.
// client->authenticateDevice(...)
};// Refresh the existing session
client->authenticateRefresh(session,refreshSuccessCallback,refreshErrorCallback);}
많은 Nakama 기능은 사용자 계정 가져오기와 같은 인증 세션을 통해서 액세스할 수 있습니다.
기본 사용자 정보 및 사용자 ID를 통해 Sagi-shi 플레이어의 전체 사용자 계정을 가져옵니다:
1
2
3
4
5
6
7
8
9
10
11
12
13
autosuccessCallback=[](constNAccount&account){stringusername=account.user.username;stringavatarUrl=account.user.avatarUrl;stringuserId=account.user.id;};autoerrorCallback=[](constNError&error){cout<<"Failed to get user account: "<<error.message<<endl;};client->getAccount(session,successCallback,errorCallback);
autosuccessCallback=[](constNStorageObjectAcks&storageObjectAcks){cout<<"Success writing storage object"<<endl;};autoerrorCallback=[](constNError&error){cout<<"Error writing storage object: "<<error.message<<endl;};hatsStorageObjectfavoriteHats{{"cowboy","alien"}};jsonj;j["hats"]=favoriteHats.hats;NStorageObjectWritewriteObject{collection:"favorites",key:"hats",value:j.dump(),permissionRead:NStoragePermissionRead::OWNER_READ,// Only the server and owner can read
permissionWrite:NStoragePermissionWrite::OWNER_WRITE// The server and owner can write
};client->writeStorageObjects(session,{writeObject},successCallback,errorCallback);
저장소 엔진 조건부 작성은 저장소 엔진에 액세스한 후에 개체가 변경되지 않은 경우에만 발생합니다.
이렇게 하면 데이터 덮어쓰기를 방지할 수 있습니다. 예를 들어, 플레이어가 마지막으로 액세스한 이후에 Sagi-shi 서버가 개체를 업데이트 했을 수도 있습니다.
조건부 작성을 실행하려면 버전을 추가하여 가장 최신의 개체 버전에서 저장소 개체를 작성합니다:
1
2
3
4
5
6
7
8
9
10
11
// Assuming we already have a storage object (storageObject).
NStorageObjectWritewriteObject{collection:"favorites",key:"hats",value:"<NewJsonValue>",permissionRead:NStoragePermissionRead::OWNER_READ,// Only the server and owner can read
permissionWrite:NStoragePermissionWrite::OWNER_WRITE,// The server and owner can write
version:storageObject.version};client->writeStorageObjects(session,{writeObject},successCallback,errorCallback);
// Subscribe to the Status event.
NRtDefaultClientListenerlistener;rtClient->setListener(&listener);listener.setStatusPresenceCallback([](constNStatusPresenceEvent&statusPresenceEvent){for(NUserPresencepresence:statusPresenceEvent.joins){cout<<presence.username<<" is online with status: "<<presence.status<<endl;}for(NUserPresencepresence:statusPresenceEvent.leaves){cout<<presence.username<<"went offline"<<endl;}});// Follow mutual friends and get the initial Status of any that are currently online.
autosuccessCallback=[&rtClient](NFriendListPtrfriendList){autofollowSuccessCallback=[](constNStatus&status){for(NUserPresencepresence:status.presences){cout<<presence.username<<" is online with status: "<<presence.status<<endl;}};autofollowErrorCallback=[](constNRtError&error){cout<<"Error following friends: "<<error.message<<endl;};for(NFriendf:friendList->friends){rtClient->followUsers({f.user.id},followSuccessCallback,followErrorCallback);}};autoerrorCallback=[](constNError&error){cout<<"Error listing friends: "<<error.message<<endl;};// Follow mutual friends and get the initial Status of any that are currently online.
client->listFriends(session,1000,NFriend::State::FRIEND,"",successCallback,errorCallback);
autosuccessCallback=[](){cout<<"Successfully updated status"<<endl;};autoerrorCallback=[](constNRtError&error){cout<<"Error updating status: "<<error.message<<endl;};rtClient->updateStatus("Viewing the Main Menu",successCallback,errorCallback);
그룹에는 공용 또는 개인 “공개” 표시가 있습니다. 누구나 공용 그룹에 가입할 수 있지만, 가입을 요청하고 비공개 그룹의 최고 관리자/관리자가 수락해야 합니다.
Sagi-shi 플레이어는 공통의 관심사를 기반으로 그룹을 생성할 수 있습니다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
autosuccessCallback=[](constNGroup&group){cout<<"Successfully created group: "<<group.id<<endl;};autoerrorCallback=[](constNError&error){cout<<"Error creating group: "<<error.message<<endl;};stringname="Imposters R Us";stringdescription="A group for people who love playing the imposter.";stringavatarUrl="";stringlangTag="";boolopen=true;// public group
intmaxSize=100;client->createGroup(session,name,description,avatarUrl,langTag,open,maxSize,successCallback,errorCallback);
그룹은 다른 Nakama 리소스와 같이 목록을 만들고 와일드카드 그룹 이름으로 필터링할 수 있습니다.
Sagi-shi 플레이어는 그룹 나열과 필터링을 통해 기존 그룹을 검색할 수 있습니다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
autosuccessCallback=[&session](NGroupListPtrgroupList){for(NGroupgroup:groupList->groups){cout<<group.name<<": "<<(group.open?"Public":"Private")<<endl;}// Get the next page of results using groupList->cursor.
};autoerrorCallback=[](constNError&error){cout<<"Error listing groups: "<<error.message<<endl;};intlimit=20;stringcursor="";client->listGroups(session,"imposter%",limit,cursor,successCallback,errorCallback);
개인 그룹 관리자 또는 총괄 관리자는 사용자를 그룹에 추가하여 가입 요청을 수락할 수 있습니다.
Sagi-shi는 먼저 가입 요청 상태의 모든 사용자를 나열한 다음 루프를 통해 그룹에 추가합니다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
autosuccessCallback=[&client](NGroupUserListPtrgroupUserList){for(NGroupUsergroupUser:groupUserList->groupUsers){client->addGroupUsers(session,"<GroupId>",{groupUser.user.id},nullptr,nullptr);}};autoerrorCallback=[](constNError&error){cout<<"Error listing group users: "<<error.message<<endl;};autolimit=opt::nullopt;NUserGroupStatestate=NUserGroupState::JOIN_REQUEST;stringcursor="";client->listGroupUsers(session,"",limit,state,cursor,successCallback,errorCallback);
Nakama 그룹 구성원은 관리자 또는 총괄 관리자 역할로 승격되어 성장하는 그룹을 관리하거나 구성원이 탈퇴할 경우 인수할 수 있습니다.
관리자는 다른 구성원을 관리자로 승격시킬 수 있고, 총괄 관리자는 다른 구성원을 총괄 관리자로 승격시킬 수 있습니다.
구성원은 한 단계 승격됩니다. 예:
구성원이 승격되면 관리자가 됩니다.
관리자가 승격되면 총괄 관리자가 됩니다.
1
2
3
4
5
6
7
8
9
10
11
autosuccessCallback=[](){cout<<"Successfully promoted group users"<<endl;};autoerrorCallback=[](constNError&error){cout<<"Error promoting group users: "<<error.message<<endl;};client->promoteGroupUsers(session,"<GroupId>",{"<UserId>"},successCallback,errorCallback);
autosuccessCallback=[](){cout<<"Successfully demoted group users"<<endl;};autoerrorCallback=[](constNError&error){cout<<"Error demoting group users: "<<error.message<<endl;};client->demoteGroupUsers(session,"<GroupId>",{"<UserId>"},successCallback,errorCallback);
autosuccessCallback=[](){cout<<"Successfully kicked group users"<<endl;};autoerrorCallback=[](constNError&error){cout<<"Error kicking group users: "<<error.message<<endl;};client->kickGroupUsers(session,"<GroupId>",{"<UserId>"},successCallback,errorCallback);
autosuccessCallback=[](NChannelPtrchannel){cout<<"Connected to dynamic room channel: "<<channel->id<<endl;};autoerrorCallback=[](constNRtError&error){cout<<"Error connecting to channel: "<<error.message<<endl;};stringroomName="<MatchId>";boolpersistence=false;boolhidden=false;rtClient->joinChat(roomName,NChannelType::ROOM,persistence,hidden,successCallback,errorCallback);
Sagi-shi 그룹 구성원은 지속적인 그룹 채팅 채널에서 플레이 세션 동안 대화할 수 있습니다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
autosuccessCallback=[](NChannelPtrchannel){cout<<"Connected to group channel: "<<channel->id<<endl;};autoerrorCallback=[](constNRtError&error){cout<<"Error connecting to channel: "<<error.message<<endl;};stringgroupId="<GroupId>";boolpersistence=false;boolhidden=false;rtClient->joinChat(groupId,NChannelType::GROUP,persistence,hidden,successCallback,errorCallback);
Sagi-shi 플레이어는 대결 중 또는 대결 후에 개인적으로 1:1 채팅을 하고 이전 메시지를 볼 수도 있습니다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
autosuccessCallback=[](NChannelPtrchannel){cout<<"Connected to direct message channel: "<<channel->id<<endl;};autoerrorCallback=[](constNRtError&error){cout<<"Error connecting to channel: "<<error.message<<endl;};stringuserId="<UserId>";boolpersistence=true;boolhidden=false;rtClient->joinChat(userId,NChannelType::DIRECT_MESSAGE,persistence,hidden,successCallback,errorCallback);
autosuccessCallback=[](constNChannelMessageAck&messageAck){cout<<"Successfully sent message: "<<messageAck.messageId<<endl;};autoerrorCallback=[](constNRtError&error){cout<<"Error sending message: "<<error.message<<endl;};stringchannelId="<ChannelId>";jsonj;j["message"]="I think Red is the imposter!";rtClient->writeChatMessage(channelId,j.dump(),successCallback,errorCallback);jsonj2;j2["emote"]="point";j2["emoteTarget"]="<RedPlayerUserId>";rtClient->writeChatMessage(channelId,j2.dump(),successCallback,errorCallback);
Nakama는 메시지 업데이트 기능도 지원합니다. 이 기능은 원하는 경우 사용할 수 있지만 Sagi-shi와 같은 속임수 게임에서는 속임수가 추가될 수 있습니다.
예를 들어, 플레이어는 다음의 메시지를 전송합니다:
1
2
3
4
5
stringchannelId="<ChannelId>";jsonj;j["message"]="I think Red is the imposter!";rtClient->writeChatMessage(channelId,j.dump(),successCallback,errorCallback);
다른 플레이어에게 혼동을 주기 위해서 메시지를 빠르게 편집할 수 있습니다:
1
2
3
4
5
6
autosuccessCallback=[&rtClient](constNChannelMessageAck&messageAck){jsonj;j["message"]="I think BLUE is the imposter!";rtClient->updateChatMessage(messageAck.channelId,messageAck.messageId,j.dump(),nullptr,nullptr);};
autoerrorRtCallback=[](constNRtError&error){cout<<"Error: "<<error.message<<endl;};autoerrorCallback=[](constNError&error){cout<<"Error: "<<error.message<<endl;};autolistFriendsSuccessCallback=[&rtClient,&errorRtCallback,&match](NFriendListPtrfriendList){for(NFriendf:friendList->friends){if(!f.user.online){continue;}autojoinChannelSuccessCallback=[&rtClient,&f,&match](NChannelPtrchannel){jsonj;j["message"]="Hey "+f.user.username+", join me for a match!";j["matchId"]=match.matchId;rtClient->writeChatMessage(channel->id,j.dump(),nullptr,nullptr);};boolpersistence=false;boolhidden=false;rtClient->joinChat(f.user.id,NChannelType::DIRECT_MESSAGE,persistence,hidden,joinChannelSuccessCallback,errorRtCallback);}};intlimit=100;NFriend::Statestate=NFriend::State::FRIEND;stringcursor="";client->listFriends(session,limit,state,cursor,listFriendsSuccessCallback,errorCallback);
autosuccessCallback=[](constNMatch&match){cout<<"Successfully joined match: "<<match.matchId<<endl;};autoerrorCallback=[](constNRtError&error){cout<<"Error: "<<error.message<<endl;};stringmatchId="<MatchId>";NStringMapmetadata={{"Region","EU"}};rtClient->joinMatch(matchId,metadata,successCallback,errorCallback);
listener.setMatchmakerMatchedCallback([&rtClient](NMatchmakerMatchedPtrmatchmakerMatched){autosuccessCallback=[](constNMatchmatch){cout<<"Successfully joined match: "<<match.matchId<<endl;};autoerrorCallback=[](constNRtError&error){cout<<"Error: "<<error.message<<endl;};rtClient->joinMatch(matchmakerMatched->matchId,{},successCallback,errorCallback);});autosuccessCallback=[](constNMatchmakerTicket&matchmakerTicket){cout<<"Successfully joined matchmaker: "<<matchmakerTicket.ticket<<endl;};autoerrorCallback=[](constNRtError&error){cout<<"Error: "<<error.message<<endl;};intminPlayers=2;intmaxPlayers=10;stringquery="";NStringMapstringProperties={};NStringDoubleMapnumericProperties={};autocountMultiple=opt::nullopt;rtClient->addMatchmaker(minPlayers,maxPlayers,query,stringProperties,numericProperties,countMultiple,successCallback,errorCallback);
플레이어 상태로 대결 참여하기
Sagi-shi 플레이어는 새로운 대결에 참여할 경우 상태를 업데이트할 수 있습니다:
1
2
3
4
5
jsonj;j["status"]="Playing a match";j["matchId"]="<MatchID>";rtClient->updateStatus(j.dump(),successCallback,errorCallback);
listener.setStatusPresenceCallback([&rtClient](constNStatusPresenceEvent&statusPresence){for(NUserPresencepresence:statusPresence.joins){autosuccessCallback=[](constNMatch&match){cout<<"Successfully joined match: "<<match.matchId<<endl;};autoerrorCallback=[](constNRtError&error){cout<<"Error: "<<error.message<<endl;};jsonj=json::parse(presence.status);if(j.contains("matchId")){NStringMapmetadata={};rtClient->joinMatch(j["matchId"],metadata,successCallback,errorCallback);}}});
// Assuming a GameObject type
//class GameObject { };
map<string,GameObject*>players={};for(NUserPresenceuserPresence:match.presences){GameObject*gameObject=spawnPlayer();// Instantiate player object
players.insert({userPresence.sessionId,gameObject});}
Sagi-shi는 대결 현재 상태 수신 이벤트를 사용하여 생성된 플레이어를 최신 상태로 유지합니다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
listener.setMatchPresenceCallback([&players](constNMatchPresenceEventmatchPresence){// For each player that has joined in this event...
for(NUserPresencepresence:matchPresence.joins){// Spawn a player for this presence and store it in the dictionary by session id.
GameObject*gameObject=spawnPlayer();players.insert({presence.sessionId,gameObject});}// For each player that has left in this event...
for(NUserPresencepresence:matchPresence.leaves){// Remove the player from the game if they've been spawned
if(players.count(presence.sessionId)>0){players.erase(presence.sessionId);}}});
Nakama는 실시간 네트워킹을 통해 플레이어가 게임 세계에서 이동하고 상호 작용할 때 대결 상태를 보내고받습니다.
대결이 진행되는 동안 Sagi-shi 클라이언트는 다른 클라이언트와 연결되는 서버로 대결 상태를 전송합니다.
대결 상태에는 수신 중인 데이터를 수신인에게 알려주는 연산 코드가 포함되어 있어 수신 중인 데이터를 역직렬화하고 게임 보기를 업데이트할 수 있습니다.
Sagi-shi에서 사용되는 연산 코드 예시:
1: 플레이어 위치
2: 플레이어 호출 투표
플레이어 위치 전송
Sagi-shi 플레이어 위치 상태를 표시하는 클래스를 정의합니다:
1
2
3
4
5
structpositionState{floatx;floaty;floatz;};
플레이어의 변환에서 인스턴스를 만들고 연산 코드를 설정하고 JSON 인코딩 상태를 보냅니다:
1
2
3
4
5
6
7
8
9
// Assuming a position variable
jsonj;j["x"]=position.x;j["y"]=position.y;j["z"]=position.z;intopCode=1;rtClient->sendMatchData(match.matchId,opCode,j.dump());
정적 클래스로서의 연산 코드
Sagi-shi에는 네트워크 게임 액션이 많습니다. 연산 코드에 대해 정적 상수 클래스를 사용하면 코드를 더 쉽게 준수하고 유지 관리할 수 있습니다:
listener.setMatchDataCallback([&players](constNMatchData&matchData){switch(matchData.opCode){caseOpCodes::POSITION:{// Get the updated position data
jsonj=json::parse(matchData.data);positionStateposition{j["x"].get<float>(),j["y"].get<float>(),j["z"].get<float>()};// Update the GameObject associated with that player.
if(players.count(matchData.presence.sessionId)>0){// Here we would normally do something like smoothly interpolate to the new position, but for this example let's just set the position directly.
players[matchData.presence.sessionId].position=newVector3(position.x,position.y,position.z);}}default:cout<<"Unsupported opcode";break;}});
개발자는 대결 목록 또는 Nakama Matchmaker를 사용하여 플레이어의 대결을 찾을 수 있습니다. 이를 통해 플레이어는 실시간 매치메이킹 풀에 참여하고 지정된 기준과 일치하는 다른 플레이어와 대결이 성사될 때 알림을 받을 수 있습니다.
매치메이킹을 통해 플레이어는 서로를 찾을 수 있지만 대결을 생성하지는 않습니다. 이러한 분리는 의도된 것이므로 게임 대결을 찾는 것 이상으로 매치메이킹을 사용할 수 있습니다. 예를 들어, 소셜 경험을 만들고 있는 경우 매치메이킹을 사용하여 채팅할 다른 사람을 찾을 수 있습니다.
파티를 생성하는 플레이어가 파티의 리더가 됩니다. 파티에는 최대 플레이어 수가 지정되며 공개된 경우 자동으로 플레이어가 수락되며 비공개인 경우 파티 리더가 들어오는 참가 요청을 수락해야 합니다.
Sagi-shi는 최대 4명의 플레이어가 있는 비공개 파티를 사용합니다:
1
2
3
4
5
6
7
8
9
10
11
12
13
autosuccessCallback=[](constNParty&party){cout<<"Successfully created party: "<<party.id<<endl;};autoerrorCallback=[](constNRtError&error){cout<<"Error: "<<error.message<<endl;};boolopen=false;intmaxPlayers=4;rtClient->createParty(open,maxPlayers,successCallback,errorCallback);
Safi-shi 플레이어는 메시지에서 파티 ID를 확인하여 채팅 메시지에 있는 파티에 참여할 수 있습니다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
listener.setChannelMessageCallback([&rtClient](constNChannelMessage&channelMessage){autosuccessCallback=[](){cout<<"Successfully joined party"<<endl;};autoerrorCallback=[](constNRtError&error){cout<<"Error: "<<error.message<<endl;};jsonj=json::parse(channelMessage.content);if(j.contains("partyId")){rtClient->joinParty(j["partyId"],successCallback,errorCallback);}});
autosuccessCallback=[](){cout<<"Successfully promoted party member"<<endl;};autoerrorCallback=[](constNRtError&error){cout<<"Error: "<<error.message<<endl;};for(NUserPresencepresence:party.presences){if(presence.sessionId!=party.leader.sessionId){rtClient->promotePartyMember(party.id,presence,successCallback,errorCallback);}}
autosuccessCallback=[](){cout<<"Successfully left party"<<endl;};autoerrorCallback=[](constNRtError&error){cout<<"Error: "<<error.message<<endl;};rtClient->leaveParty(party.id,successCallback,errorCallback);
Sagi-shi 플레이어는 매치메이커 대결 이벤트를 들은 후 발견하면 해당 대결에 참여할 수 있습니다:
1
2
3
4
5
6
7
8
9
10
11
12
13
listener.setMatchmakerMatchedCallback([&rtClient](NMatchmakerMatchedPtrmatchmakerMatched){autosuccessCallback=[](constNMatch&match){cout<<"Successfully joined match: "<<match.matchId<<endl;};autoerrorCallback=[](constNRtError&error){cout<<"Error: "<<error.message<<endl;};rtClient->joinMatch(matchmakerMatched->matchId,{},successCallback,errorCallback);});
파티 리더는 파티 상대 찾기를 시작합니다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
autosuccessCallback=[](constNPartyMatchmakerTicket&partyMatchmakerTicket){cout<<"Successfully joined matchmaker as party: "<<partyMatchmakerTicket.ticket<<endl;};autoerrorCallback=[](constNRtError&error){cout<<"Error: "<<error.message<<endl;};intminPlayers=2;intmaxPlayers=10;stringquery="";NStringMapstringProperties={};NStringDoubleMapnumericProperties={};autocountMultiple=opt::nullopt;rtClient->addMatchmakerParty(party.id,query,minPlayers,maxPlayers,stringProperties,numericProperties,countMultiple,successCallback,errorCallback);
기본적으로, Nakama 플레이어는 점수를 제출하기 전에 토너먼트에 참여하지 않아도 되지만 Sagi-shi에서는 점수를 의무적으로 제출해야 합니다:
1
2
3
4
5
6
7
8
9
10
11
autosuccessCallback=[](){cout<<"Successfully joined tournament"<<endl;};autoerrorCallback=[](constNError&error){cout<<"Error: "<<error.message<<endl;};client->joinTournament(session,"<TournamentId>",successCallback,errorCallback);
Nakama는 알림을 구별하기 위해서 코드를 사용합니다. 0 이하의 코드는 Nakama 내부 직원을 위해 예약된 시스템입니다.
Sagi-shi 플레이어는 알림 수신 이벤트를 구독할 수 있습니다. Sagi-shi는 토너먼트 우승 100 코드를 사용합니다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
listener.setNotificationsCallback([](constNNotificationList¬ificationList){constintrewardCode=100;for(NNotificationn:notificationList.notifications){switch(n.code){caserewardCode:cout<<"Congratulations, you won the tournament!"<<endl<<n.subject<<endl<<n.content<<endl;break;default:cout<<"Other notification:"<<endl<<n.subject<<endl<<n.content<<endl;break;}}});