This is an old revision of the document!
Realizarea unui joc de pick-up folosind serviciile PlayFab
Bonus:
Gasiti pe MS Teams inregistrat
PlayFab este unul dintre serviicile web dedicate jocurilor video, oferit de Microsoft, similar cu alte servicii de la AWS, Epic Online Services, Unity, Steam si altele. Este un backend as a service (BaaS) care oferă funcționalități esențiale pentru jocuri: autentificare, date persistente pentru playeri, leaderboard-uri, matchmaking, economy, etc.
In general aceste servicii vin deja cu un SDK implementat pentru motoarele grafice cunoscute (Unity, Unreal etc), dar se pot utiliza si direct prin cereri HTTP intr-o arhitectura REST. SDK-ul este in general un wrapper peste un REST API.
Resurse oficiale:
Primul pas pentru folosirea oricarui serviciu dedicat este autentificare. Procesul este simplificat prin folosirea SDK-ului.
PlayFab acceptă o varietate de furnizori de autentificare (Microsoft, Facebook, Google etc). Pentru development și testare rapidă, cel mai simplu este LoginWithCustomID care folosește device ID-ul:
void LoginWithCustomId() { var request = new LoginWithCustomIDRequest { CreateAccount = true, // Should be true if account does *not* exist (does a sort of create-and-login). CustomId = PlayFabSettings.DeviceUniqueIdentifier }; PlayFabClientAPI.LoginWithCustomID(request, result => Debug.Log($"Login successful! PlayFabId: {result.PlayFabId}"), error => Debug.LogError($"Error: {error.GenerateErrorReport()}") ); }
PlayFab oferă mai multe tipuri de stocare pentru date asociate playerilor:
User Data (client R/W, server R/W)
User Read-Only Data (client read-only, server R/W)
Internal Data (server R/W only)
server.GetUserInternalData / UpdateUserInternalDataExemplu User Data:
PlayFabClientAPI.
void SavePlayerString(string playerStr) { var request = new UpdateUserDataRequest { Data = new Dictionary<string, string> { { "SomeStringValue", playerStr } } }; PlayFabClientAPI.UpdateUserData(request, result => Debug.Log("Data has been saved remotely!"), error => Debug.LogError(error.GenerateErrorReport()) ); } void LoadPlayerString() { PlayFabClientAPI.GetUserData(new GetUserDataRequest(), result => { if (result.Data.TryGetValue("SomeStringValue", out var data)) { string fetchedStr = data.Value; Debug.Log($"Fetched data: {fetchedStr}"); } }, error => Debug.LogError(error.GenerateErrorReport()) ); }
CloudScript permite executarea de cod pe serverele PlayFab, nu pe client. Esențial pentru validare server-side și prevenirea cheating-ului.
De ce CloudScript? Pentru orice logică critică (quest progress, achievements, currency) avem nevoie de validare server-side.
Exemplu utilizare:
1. Pentru a scrie cod backend in CloudScript:
handlers.IncrementCounter = function(args, context) { var res = server.GetUserReadOnlyData({ PlayFabId: currentPlayerId, Keys: ["Counter"] }); var count = 0; if (res.Data["Counter"] !== undefined) { count = JSON.parse(res.Data["Counter"].Value); } count++; server.UpdateUserReadOnlyData({ PlayFabId: currentPlayerId, Data: { "Counter": count } }); return count; };
2. Apel CloudScript function din Unity:
void CallCloudScript() { PlayFabClientAPI.ExecuteCloudScript(new ExecuteCloudScriptRequest { FunctionName = "IncrementCounter" }, result => Debug.Log($"Counter: {result.FunctionResult}"), error => Debug.LogError(error.GenerateErrorReport()) ); }
log.info(“message”) în CloudScript. Verificați output-ul în Dashboard → Title Overview → PlayStream Monitor.
PlayFab poate declanșa automat CloudScript functions în două moduri:
Rules (PlayStream Events) - se execută când anumite evenimente apar în joc:
Scheduled Tasks - se execută periodic la intervale fixe:
Exemplu de utilizare Rules:
Puteți crea o regulă care să incrementeze automat un counter de login-uri de fiecare dată când un player se autentifică:
player_logged_inupdatePlayerLogins (presupunem că există funcția în CloudScript)Astfel, de fiecare dată când un player face login, PlayFab va apela automat funcția CloudScript fără să fie nevoie de apel explicit din client.
Exemplu de utilizare Scheduled Tasks:
Puteți crea un task care să reseteze daily rewards pentru toți playerii la miezul nopții:
resetDailyRewards (presupunem că există funcția în CloudScript)
Pentru scenarii unde playerul nu este online când un proces se termină (ex: training care dureaza mai mult timp), putem trimite notificări prin email.
Problema: PlayFab nu trimite email-uri direct - metoda oficială necesită configurare SMTP putin mai complexă (Add-on → Email).
Alternativă: Folosim un serviciu extern (ex: Resend) și facem HTTP requests din CloudScript.
makeHTTPRequest din codul deja existent in CloudScriptTrimitere email:
handlers.SendLevelUpEmail = function(args, context) { var playerEmail = args.playerEmail; var headers = { "Authorization": "Bearer YOUR_RESEND_API_KEY", "Content-Type": "application/json" }; var body = { from: "onboarding@resend.dev", to: playerEmail, subject: "Level Up Complete!", html: "<p>Your training is complete!</p>" }; var url = "https://api.resend.com/emails"; var response = http.request(url, "post", JSON.stringify(body), "application/json", headers); return { emailSent: true }; };
PlayFab SMTP Add-on (Add-ons → Email):
makeHTTPRequest în samples.
Resurse:
PlayFab folosește două concepte separate pentru datele jucătorilor ce țin de scor, progres, etc., statistics și leaderboard-uri – dar în backend-ul din PlayFab acestea sunt (destul de) legate și utilitatea acestora constă în sinergia dintre cele două concepte.
Statistics = valori numerice tracked per player (high scores, kills, wins, etc.)
Leaderboards = clasamente bazate pe statistici
Pentru a putea folosi API-ul de stats/leaderboards pe clienți (să le permiteți actualizarea acestora):
Update statistică din Unity:
using PlayFab; using PlayFab.ProgressionModels; void SubmitScore(int score) { var request = new UpdateStatisticsRequest { Entity = new EntityKey { Id = "<Entity_ID>", // Player's entity ID (from login response). Type = "title_player_account" }, Statistics = new List<StatisticUpdate> { new StatisticUpdate { Name = "HighScore", Scores = new List<string> { score.ToString() } } } }; PlayFabProgressionAPI.UpdateStatistics(request, result => Debug.Log("Statistic updated!"), error => Debug.LogError(error.GenerateErrorReport()) ); }
Citire leaderboard:
using PlayFab; using PlayFab.ProgressionModels; private void GetLeaderboard() { var request = new GetEntityLeaderboardRequest { LeaderboardName = "HighScore_Leaderboard", StartingPosition = 1, // 1 = beginning of leaderboard (not 0!). PageSize = 10 // Min 1, max 100. }; PlayFabProgressionAPI.GetLeaderboard(request, result => { foreach (var entry in result.Rankings) { Debug.Log($"{entry.Rank}. {entry.DisplayName}: {entry.Scores[0]}"); } }, error => Debug.LogError(error.GenerateErrorReport()) ); }
Citire leaderboard în jurul playerului:
using PlayFab; using PlayFab.ProgressionModels; void GetLeaderboardAroundPlayer() { var request = new GetLeaderboardAroundEntityRequest { Entity = new EntityKey { Id = "<Entity_ID>", // Player's entity ID (from login response). Type = "title_player_account" }, LeaderboardName = "HighScore_Leaderboard", MaxSurroundingEntries = 10 // Number of entries above and below (these are split ~half/half). }; PlayFabProgressionAPI.GetLeaderboardAroundEntity(request, result => { foreach (var entry in result.Rankings) { Debug.Log($"{entry.Rank}. {entry.DisplayName}: {entry.Scores[0]}"); } }, error => Debug.LogError(error.GenerateErrorReport()) ); }
result.EntityToken.Entity.Id din response-ul de la LoginWithCustomID.
[Statistics]
[Leaderboards]