Table of Contents

4. Servicii web dedicate

Cerinte

Realizarea unui joc de pick-up folosind serviciile PlayFab

Bonus:

Documentatie video

Gasiti pe MS Teams inregistrat

Documentatie text

Introducere în PlayFab

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:

Instalarea și configurarea SDK-ului

1. Crearea unui cont PlayFab

2. Import PlayFab SDK

Găsiți mai multe detalii, dacă aveți nevoie, în Quickstart: PlayFab Client library for C# in Unity

Concepte PlayFab

Autentificare

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()}")
    );
}

Pentru alte metode de autentificare (Facebook, Google, etc), consultați documentația oficială.

Player Data

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)

Exemplu User Data:

Pentru interacțiunea cu sistemul User Data ne vom folosi de API-ul clientului, 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())
    );
}

Datele stocate pot fi vizualizate pe dashboard: Mergeți la secțiunea Players, găsiți player-ul pentru care ați scris datele, accesați tab-ul Player Data.

Mai multe detalii: Player Data

CloudScript

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.

În producție, recomandăm folosirea Azure Functions (C#) în loc de CloudScript (JavaScript) pentru logică backend complexă. Acestea oferă mai multă flexibilitate, debugging mai bun și support pentru limbaje moderne. Setup-ul pentru Azure Functions este însă mai complex și depășește scope-ul acestui laborator, astfel că vom folosi CloudScript clasic (JavaScript) pentru simplitate.

CloudScript nu este exclusiv pentru a scrie/citi din date ale player-ului. Puteți efectua multe alte operațiuni, de exemplu grant de item sau virtual currency, schimbare date generale ale proiectului PlayFab (Title Data), etc.

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:

private void CallCloudScript()
{
    var request = new ExecuteCloudScriptRequest
    {
        FunctionName = "incrementCounter",
        GeneratePlayStreamEvent = true
    };
    PlayFabClientAPI.ExecuteCloudScript(request,
    result =>
    {
        var counterValue = Convert.ToInt32(result.FunctionResult);
        Debug.Log($"Returned counter value: {counterValue}");
    },
    error =>
    {            
        Debug.LogError($"Cloud Script error: {error.GenerateErrorReport()}");
    });
}

Pentru debugging, folosiți log.info(“message”) în CloudScript. Verificați output-ul în Dashboard → Title Overview → PlayStream Monitor.

Mai multe detalii: CloudScript Quickstart

Event Automation

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ă:

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:

Verificați execuția task-urilor în Automation → Scheduled Tasks (scroll în josul paginii) pentru debugging.

Notificări Email Asincrone

Context

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.

Procesul General

  1. Configurare serviciu email - Cont gratuit pe Resend, obțineți API key
  2. CloudScript face HTTP request - Adaptăm exemplul existent makeHTTPRequest din codul deja existent in CloudScript
  3. Scheduled Task verifică periodic - Rulează la 10-15 min, verifică playeri cu training complet

Implementare CloudScript

Trimitere 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 };
};

SMTP

PlayFab SMTP Add-on (Add-ons → Email):

Pentru testare rapidă, API extern (Resend) este mai simplu. Pentru producție, folosiți Add-on-ul SMTP din PlayFab.

CloudScript include deja un exemplu de HTTP request - vezi funcția makeHTTPRequest în samples.

Resurse:

Statistics și Leaderboards

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):

În mod ideal această setare este destul de sensibilă, întrucât permite jucătorilor actualizarea acestor valori - în scop didactic este OK.

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())
    );
}

Pentru a obține Entity ID-ul playerului după login, folosiți result.EntityToken.Entity.Id din response-ul de la LoginWithCustomID.

Vizualizați statisticile în Leaderboards → Statistics și leaderboard-ul în Leaderboards → Leaderboards.

Resurse utile

Documentație:

Exemple:

Support: