varretryConfiguration=newNakama.RetryConfiguration(baseDelay:1,maxRetries:5,delegate{System.Console.Writeline("about to retry.");});// Configure the retry configuration globally.client.GlobalRetryConfiguration=retryConfiguration;varaccount=awaitclient.GetAccountAsync(session);// Alternatively, pass the retry configuration to an individual request.varaccount=awaitclient.GetAccountAsync(session,retryConfiguration);
// Retry configuration with random jitter intervalsvarretryConfiguration=newRetryConfiguration(500,5,RetryListener,RetryJitter.FullJitter);
如果需要对请求之间的延迟有更多的控制,您可以提供自己的抖动委托。
1
2
3
4
5
6
7
8
9
// Retry configuration with custom retry jittervarretryConfigurationCustomJitter=newRetryConfiguration(500,5,RetryListener,(history,baseDelay,random)=>{// Use the "Decorrelated Jitter" algorithm (https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/)constintdelayCap=20000;varlastAttempt=history.Last();varjitter=Mathf.Min(delayCap,random.Next(baseDelay,lastAttempt.JitterBackoff*3));returnjitter;});
Nakama API可以使用可选CancellationTokenSource对象来取消请求:
1
2
3
4
5
// Part of System.Threading namespacevarcanceller=newCancellationTokenSource();varaccount=awaitclient.GetAccountAsync(session,retryConfiguration:null,canceller);canceller.Cancel();
publicasyncvoidAuthenticateWithDevice(){// If the user's device ID is already stored, grab that - alternatively get the System's unique device identifier.vardeviceId=PlayerPrefs.GetString("deviceId",SystemInfo.deviceUniqueIdentifier);// If the device identifier is invalid then let's generate a unique one.if(deviceId==SystemInfo.unsupportedIdentifier){deviceId=System.Guid.NewGuid().ToString();}// Save the user's device ID to PlayerPrefs so it can be retrieved during a later play session for re-authenticating.PlayerPrefs.SetString("deviceId",deviceId);// Authenticate with the Nakama server using Device Authentication.try{Session=awaitclient.AuthenticateDeviceAsync(deviceId);Debug.Log("Authenticated with Device ID");}catch(ApiResponseExceptionex){Debug.LogFormat("Error authenticating with Device ID: {0}",ex.Message);}}
publicvoidAuthenticateWithFacebook(){FB.LogInWithReadPermissions(new[]{"public_profile","email"},asyncresult=>{if(FB.IsLoggedIn){try{varimportFriends=true;Session=awaitclient.AuthenticateFacebookAsync(AccessToken.CurrentAccessToken.TokenString,importFriends);Debug.Log("Authenticated with Facebook");}catch(ApiResponseExceptionex){Debug.LogFormat("Error authenticating with Facebook: {0}",ex.Message);}}});}
publicasyncvoidAuthenticationWithCustom(){// Authenticate using Custom ID (using itch.io authentication).try{varitchioApiKey=Environment.GetEnvironmentVariable("ITCHIO_API_KEY");Session=awaitClient.AuthenticateCustomAsync(itchioApiKey);Debug.Log("Authenticated with Custom ID");}catch(ApiResponseExceptionex){Debug.LogFormat("Failed authentication: {0}",ex.Message);}}
publicasyncvoidLinkDeviceAuthentication(){// Acquiring the unique device ID has been shortened for brevity, see previous example.vardeviceId="<UniqueDeviceId>";// Link Device Authentication to existing player account.try{awaitclient.LinkDeviceAsync(Session,deviceId);Debug.Log("Successfully linked Device ID authentication to existing player account");}catch(ApiResponseExceptionex){Debug.LogFormat("Error linking Device ID: {0}",ex.Message);}}
链接Facebook身份验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
publicvoidLinkFacebookAuthentication(boolimportFriends=true){FB.LogInWithReadPermissions(new[]{"public_profile","email"},asyncresult=>{if(FB.IsLoggedIn){try{varimportFriends=true;awaitclient.LinkFacebookAsync(Session,AccessToken.CurrentAccessToken.TokenString,importFriends);Debug.Log("Successfully linked Facebook authentication to existing player account");}catch(ApiResponseExceptionex){Debug.LogFormat("Error authenticating with Facebook: {0}",ex.Message);}}});}
// Check whether a session has expired or is close to expiry.if(session.IsExpired||session.HasExpired(DateTime.UtcNow.AddDays(1))){try{// Attempt to refresh the existing session.session=awaitclient.SessionRefreshAsync(session);}catch(ApiResponseException){// Couldn't refresh the session so reauthenticate.session=awaitclient.AuthenticateDeviceAsync(deviceId);PlayerPrefs.SetString("nakama.refreshToken",session.RefreshToken);}PlayerPrefs.SetString("nakama.authToken",session.AuthToken);}
publicclassMetadata{publicstringTitle;publicstringHat;publicstringSkin;}// Get the updated account object.varaccount=awaitclient.GetAccountAsync(session);// Parse the account user metadata.varmetadata=Nakama.TinyJson.JsonParser.FromJson<Metadata>(account.User.Metadata);Debug.LogFormat("Title: {0}",metadata.Title);Debug.LogFormat("Hat: {0}",metadata.Hat);Debug.LogFormat("Skin: {0}",metadata.Skin);
varfavoriteHats=newHatsStorageObject{Hats=newstring[]{"cowboy","alien"}};varwriteObject=newWriteStorageObject{Collection="favorites",Key="Hats",Value=JsonWriter.ToJson(favoriteHats),PermissionRead=1,// Only the server and owner can readPermissionWrite=1,// The server and owner can write};awaitclient.WriteStorageObjectsAsync(session,new[]{writeObject});
// Assuming we already have a storage object (storageObject)varwriteObject=newWriteStorageObject{Collection=storageObject.Collection,Key=storageObject.Key,Value="<NewJSONValue>",PermissionWrite=0,PermissionRead=1,Version=storageObject.Version};try{awaitclient.WriteStorageObjectsAsync(session,writeObjects);}catch(ApiResponseExceptionex){Debug.Log(ex.Message);}
varlimit=3;varunlocksObjectList=awaitclient.ListUsersStorageObjectsAsync(session,"Unlocks",session.UserId,limit,cursor:null);foreach(varunlockStorageObjectinunlocksObjectList.Objects){switch(unlockStorageObject.Key){case"Titles":varunlockedTitles=JsonParser.FromJson<TitlesStorageObject>(unlockStorageObject.Value);// Display the unlocked titlesbreak;case"Hats":varunlockedHats=JsonParser.FromJson<HatsStorageObject>(unlockStorageObject.Value);// Display the unlocked hatsbreak;case"Skins":varunlockedSkins=JsonParser.FromJson<SkinsStorageObject>(unlockStorageObject.Value);// Display the unlocked skinsbreak;}}
try{varpayload=newDictionary<string,string>{{"item","cowboy"}};varresponse=awaitclient.RpcAsync(session,"EquipHat",payload.ToJson());Debug.Log("New hat equipped successfully",response);}catch(ApiResponseExceptionex){Debug.LogFormat("Error: {0}",ex.Message);}
// Add friends by Username.awaitclient.AddFriendsAsync(session,null,new[]{"AlwaysTheImposter21","SneakyBoi"});// Add friends by User ID.awaitclient.AddFriendsAsync(session,new[]{"<SomeUserId>","<AnotherUserId>"});
varlimit=20;// Limit is capped at 1000varfrienshipState=0;varresult=awaitclient.ListFriendsAsync(session,frienshipState,limit,cursor:null);foreach(varfriendinresult.Friends){Debug.LogFormat("ID: {0}",friend.User.Id);}
// Delete friends by User ID.awaitclient.DeleteFriendsAsync(session,new[]{"<SomeUserId>","<AnotherUserId>"});// Delete friends by Username.awaitclient.DeleteFriendsAsync(session,null,new[]{"<SomeUsername>","<AnotherUsername>"});
// Block friends by User ID.awaitclient.BlockFriendsAsync(session,new[]{"<SomeUserId>","<AnotherUserId>"});// Block friends by Username.awaitclient.BlockFriendsAsync(session,null,new[]{"<SomeUsername>","<AnotherUsername>"});
// Subscribe to the Status event.socket.ReceivedStatusPresence+=e=>{foreach(varpresenceine.Joins){Debug.LogFormat("{0} is online with status: '{1}'",presence.Username,presence.Status);}foreach(varpresenceine.Leaves){Debug.LogFormat("{0} went offline",presence.Username);}};// Follow mutual friends and get the initial Status of any that are currently online.varfriendsResult=awaitclient.ListFriendsAsync(session,0);varfriendIds=friendsResult.Friends.Select(f=>f.User.Id);varresult=awaitsocket.FollowUsersAsync(friendIds);foreach(varpresenceinresult.Presences){Debug.LogFormat("{0} is online with status: {1}",presence.Username,presence.Status);}
varname="Imposters R Us";vardescription="A group for people who love playing the imposter.";varopen=true;// public groupvarmaxSize=100;vargroup=awaitclient.CreateGroupAsync(session,name,description,avatarUrl:null,langTag:null,open,maxSize);
varlimit=20;varresult=awaitclient.ListGroupsAsync(session,"imposter%",limit);foreach(vargroupinresult.Groups){Debug.LogFormat("{0} [{1}]",group.Name,group.Open?"Public":"Private")}// Get the next page of results.varnextResults=awaitclient.ListGroupsAsync(session,name:"imposter%",limit,result.Cursor);
vargroupId="<GroupId>";varpersistence=true;varhidden=false;varchannel=awaitsocket.JoinChatAsync(groupId,ChannelType.Group,persistence,hidden);Debug.LogFormat("Connected to group channel: {0}",channel.Id);
varuserId="<UserId>";varpersistence=true;varhidden=false;varchannel=awaitsocket.JoinChatAsync(userId,ChannelType.DirectMessage,persistence,hidden);Debug.LogFormat("Connected to direct message channel: {0}",channel.Id);
varchannelId="<ChannelId>"varmessageContent=newDictionary<string,string>{{"message","I think Red is the imposter!"}};varmessageSendAck=awaitsocket.WriteChatMessageAsync(channelId,JsonWriter.ToJson(messageContent));varemoteContent=newDictionary<string,string>{{"emote","point"},{"emoteTarget","<RedPlayerUserId>"}};varemoteSendAck=awaitsocket.WriteChatMessageAsync(channelId,JsonWriter.ToJson(emoteContent));
varchannelId="<ChannelId>"varmessageContent=newDictionary<string,string>{{"message","I think Red is the imposter!"}};varmessageSendAck=awaitsocket.WriteChatMessageAsync(channelId,JsonWriter.ToJson(messageContent));
然后他们迅速编辑他们的消息,迷惑他人:
1
2
3
4
varnewMessageContent=newDictionary<string,string>{{"message","I think BLUE is the imposter!"}};varmessageUpdateAck=awaitsocket.UpdateChatMessageAsync(channelId,messageSendAck.MessageId,JsonWriter.ToJson(newMessageContent));
varmatch=awaitsocket.CreateMatchAsync();varfriendsList=awaitclient.ListFriendsAsync(session,0,100);varonlineFriends=friendsList.Friends.Where(f=>f.User.Online).Select(f=>f.User);foreach(varfriendinonlineFriends){varcontent=new{message=string.Format("Hey {0}, join me for a match!",friend.Username),matchId=match.Id};varchannel=awaitsocket.JoinChatAsync(friend.Id,ChannelType.DirectMessage);varmessageAck=awaitsocket.WriteChatMessageAsync(channel,JsonWriter.ToJson(content));}
varmatchName="NoImpostersAllowed";// When joining by match name, you use the CreateMatchAsync function instead of the JoinMatchAsync functionvarmatch=awaitsocket.CreateMatchAsync(matchName);
按玩家状态加入比赛
Sagi-shi玩家可以在加入新比赛时更新状态:
1
2
3
4
5
6
7
varstatus=newDictionary<string,string>{{"Status","Playing a match"},{"MatchId","<MatchId>"}};awaitsocket.UpdateStatusAsync(JsonWriter.ToJson(status));
关注玩家的用户可以接收实时状态事件,并尝试加入比赛:
1
2
3
4
5
6
7
8
9
10
11
12
13
socket.ReceivedStatusPresence+=asynce=>{// Join the first match found in a friend's statusforeach(varpresenceine.Joins){varstatus=JsonParser.FromJson<Dictionary<string,string>>(presence.Status);if(status.ContainsKey("MatchId")){awaitsocket.JoinMatchAsync(status["MatchId"]);break;}}};
varmatch=awaitsocket.JoinMatchAsync(matchId);varplayers=newDictionary<string,GameObject>();foreach(varpresenceinmatch.Presences){// Spawn a player for this presence and store it in a dictionary by session id.vargo=Instantiate(playerPrefab);players.Add(presence.SessionId,go);}
socket.ReceivedMatchPresence+=matchPresenceEvent=>{// For each player that has joined in this event...foreach(varpresenceinmatchPresenceEvent.Joins){// Spawn a player for this presence and store it in a dictionary by session id.vargo=Instantiate(playerPrefab);players.Add(presence.SessionId,go);}// For each player that has left in this event...foreach(varpresenceinmatchPresenceEvent.Leaves){// Remove the player from the game if they've been spawnedif(players.ContainsKey(presence.SessionId)){Destroy(players[presence.SessionId]);players.Remove(presence.SessionId);}}};
socket.ReceivedMatchState+=matchState=>{switch(matchState.OpCode){caseOpCodes.Position:// Get the updated position datavarstateJson=Encoding.UTF8.GetString(matchState.State);varpositionState=JsonParser.FromJson<PositionState>(stateJson);// Update the GameObject associated with that playerif(players.ContainsKey(matchState.UserPresence.SessionId)){// 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[matchState.UserPresence.SessionId].transform.position=newVector3(positionState.X,positionState.Y,positionState.Z);}break;default:Debug.Log("Unsupported op code");break;}};
socket.ReceivedNotification+=notification=>{constintrewardCode=100;switch(notification.Code){caserewardCode:Debug.LogFormat("Congratulations, you won the tournament!\n{0}\n{1}",notification.Subject,notification.Content);break;default:Debug.LogFormat("Other notification: {0}:{1}\n{2}",notification.Code,notification.Subject,notification.Content);break;}};