ruk·si

🕹️ PlayFab

Updated at 2016-11-05 04:03

PlayFab is a game backend-as-a-service.

Download the Unity SDK. Import the downloaded package to your game.

You will use the Client API. to log in. New players get registered and returning players log in. Use a unique GUID which is stored on the device, and use that to login e.g. device ID.

using UnityEngine;
using PlayFab;
using PlayFab.ClientModels;

public class PlayFabManager : MonoBehaviour {
    public string PlayFabId;

    void Start() {
        PlayFabSettings.TitleId = "YOUR_TITLE_ID";
        Login(PlayFabSettings.TitleId);
    }

    void Login(string titleId) {
        LoginWithCustomIDRequest request = new LoginWithCustomIDRequest() {
            TitleId = titleId,
            CreateAccount = true,
            CustomId = SystemInfo.deviceUniqueIdentifier
        };
        PlayFabClientAPI.LoginWithCustomID(request,
            (result) => {
                PlayFabId = result.PlayFabId;
                Debug.Log("Got PlayFabID: " + PlayFabId);
                if (result.NewlyCreated) {
                    Debug.Log("(new account)");
                } else {
                    Debug.Log("(existing account)");
                }
            },
            (error) => {
                Debug.Log("Error on request:");
                Debug.Log(error.ErrorMessage);
            }
        );
  }
}

You can search and view players at Players tab.

You will notice that all players have an user ID. This is hidden information from players. Players can login with a Custom ID, an email address and password, an iPhone or Android device ID, or social accounts like Kongregate, Facebook, or Twitch.

PlayStream shows details when player account changes. Logins, statistic value changes etc. More about PlayStream later.

Note that many fields have pretty strict byte size restrictions. Compressing data will give you some extra room but you should avoid saving really big data to PlayFab fields.

PlayFab doens't have good notification support. You can use e.g. https://onesignal.com/

Advertising and data collection:

  • Privacy policy: disclose any and all third party advertising, tell if you adhere to California's "Do Not Track" disclosure.
  • COPPA: don't have content that CAN BE interpreted as targeted to children under 13, or make sure you are COPPA compliant.
    • Create a separate Children's Private Policy.
    • You must ask parent's consent before allowing the child to play.
  • Comply with Privacy Shield and tell that in your Privacy Policy. Best choice for EU. Don't save IP or anything identifiable.
  • Stuff everything in EULA. Make a link to the EULA and provide an affirmative action link to skip it.

Statistics / Leaderboards

Statistics are player specific variables across titles. You cannot delete or rename statistic data. You can only create one or edit one. Clients can only read player statistics, updating must be done server-side, but you can allow clients to write through Settings > API Features Example stats: level score, experience.

Defining a statistic inherently defines a leaderboard as well. It doesn't have to be visible for the player but it will always be created to Players > Leaderboards.

GetLeaderboardAroundCurrentUser

Statistics are commonly used for:

  • Player and character-centric statistics
  • Segmentation based on player level
  • Achievement system
  • Leaderboards
  • Community forum metadata

Statistics are versioned. Useful for wiping inactive players from leaderboards or high scores caused by bugs. You can read and write any version of a statistic.

Statistics can be configured to reset on a pre-determined interval. Configured with VersionChangeInterval parameter or through leaderboards web interface: Never, Hour, Day, Week, Month. You can manually reset a statistic with IncrementPlayerStatisticVersion.

Statistics usage example:

using UnityEngine;
using PlayFab;
using PlayFab.ClientModels;

public class MyScript : MonoBehaviour {

    public void GetPlayerStatistics() {
        var request = new GetPlayerStatisticsRequest() {};
        PlayFabClientAPI.GetPlayerStatistics(request,
            (result) => {
                var stats = result.Statistics;
                Debug.Log(stats[0].Value);
            },
            (error) => {
                Debug.Log("Error on request:");
                Debug.Log(error.ErrorMessage);
            }
        );
    }

}

Economy

Bulk edit of items. Coupons for items. Bundles are collections of items that turn into the real items when bought. Containers are collections of items that remain as a single item until opened. Drop tables define rules for randomizing item rewards. Grant and revoke items through the web interface.

You can create virtual currencies e.g. diamonds or gold. Economy > Currencies > New Currency For example add a new currency GO, Gold. You can also configure initial amount, max and recharge rate.

You can add currency to a player. Players > Player* > Virtual Currency > Currency*

Catalogs are collections of items players can buy. Avoid creating a huge amount of catalogs as items can easily be filtered using classes, tags and stores. Note that a catalog must always have at least one item or it is removed. Catalog name usually is used as catalog version between game verions and regions.

`Economy > Catalogs > New Catalog`, give it catalog version of `main`.
Click the automatic item called `One`.
Edit item ID: apple
Display Name: Apple
Define price of 5 GO, note special `RM` currency which is "Real Money"
in USD cents e.g. 499 would be $4.99.
using UnityEngine;
using PlayFab;
using PlayFab.ClientModels;

public class MyScript : MonoBehaviour {

    public void GetCatalogItems() {
        var request = new GetCatalogItemsRequest() { CatalogVersion = "main" };
        PlayFabClientAPI.GetCatalogItems(request,
            (result) => {
                var catalog = result.Catalog;
                Debug.Log(catalog);
            },
            (error) => {
                Debug.Log("Error on request:");
                Debug.Log(error.ErrorMessage);
            }
        );
    }

    public void PurchaseItem() {
        var request = new PurchaseItemRequest() {
            CatalogVersion = "main",
            ItemId = "apple",
            VirtualCurrency = "GO",
            Price = 5
        };
        PlayFabClientAPI.PurchaseItem(request,
            (result) => {
                var items = result.Items;
                Debug.Log(items);
            },
            (error) => {
                Debug.Log("Error on request:");
                Debug.Log(error.ErrorMessage);
            }
        );
    }

    public void GetUserInventory() {
        var request = new GetUserInventoryRequest() {};
        PlayFabClientAPI.GetUserInventory(request,
            (result) => {
                var inventory = result.Inventory;
                Debug.Log(inventory);
            },
            (error) => {
                Debug.Log("Error on request:");
                Debug.Log(error.ErrorMessage);
            }
        );
    }

}

You have to specify the price when buying an item. This way the server will respond WrongPrice if client and server think the price is not in sync.

Stores are a subsets of your catalog with prices you can override.

`Economy > Catalogs > main > New Item`
Add a few more fruits: `pear:3`, `orange:4`, `grape:no_cost`
Add a non-fruit: `potato:1`
`Economy > Catalogs > main > Stores > New Store`
Store ID: `fruits`
Add to store: apple, pear, orange, grape
Make all prices 1 less than the catalog price except keep grape at 0
Save the store
using UnityEngine;
using PlayFab;
using PlayFab.ClientModels;

public class MyScript : MonoBehaviour {

    public void GetStoreItems() {
        var request = new GetStoreItemsRequest() {
            CatalogVersion = "main",
            StoreId = "fruits"
        };
        PlayFabClientAPI.GetStoreItems(request,
             (result) => {
                var store = result.Store;
                Debug.Log(store[0].ItemId);
            },
            (error) => {
                Debug.Log("Error on request:");
                Debug.Log(error.ErrorMessage);
            }
        );
    }

    public void PurchaiseStoreItem() {
        var request = new PurchaseItemRequest() {
            CatalogVersion = "main",
            StoreId = "fruits", // Use store price, not the catalog
            ItemId = "pear",
            VirtualCurrency = "GO",
            Price = 2
        };
        PlayFabClientAPI.PurchaseItem(request,
              (result) => {
                var items = result.Items;
                Debug.Log(items[0].ItemId);
            },
            (error) => {
                Debug.Log("Error on request:");
                Debug.Log(error.ErrorMessage);
            }
        );
    }

}

CloudScript

CloudScript allows running custom JavaScript on PlayFab's servers. No client updates needed but don't do anything long running in there. You can think them as a replacement for a dedicated game server with less restricted access to PlayFab API.

`Servers > CloudScript`
Add the following JavaScript on top of the CloudScript
Then press "Save as revision 2" and "Deploy revision 2"
handlers.bushelOnYourFirstDay = function(args) {
    // The server API can add virtual currency safely
    var addGoldResult = server.AddUserVirtualCurrency({
        PlayFabId: currentPlayerId,
        VirtualCurrency: "GO",
        Amount: 500
    });

    // When the server grants items, there's no cost to the player
    var appleBounty = server.GrantItemsToUser({
        PlayFabId: currentPlayerId,
        CatalogVersion: "main",
        ItemIds: ["apple", "apple"]
    });

    // Like AddUserVirtualCurrency, it's safer to call UpdatePlayerStatistics from CloudScript
    var updateStatistics = server.UpdatePlayerStatistics({
        PlayFabId: currentPlayerId,
        Statistics: [{
            "StatisticName": "xp",
            "Value": 10
        }]
    });

    log.info("I have 500 gold, two apples and 10 XP!");
}

You can execute the CloudScript in your code. You can use it to grant players XP after a match ends, randomize starting statistics, or even run referral codes.

using UnityEngine;
using PlayFab;
using PlayFab.ClientModels;

public class MyScript : MonoBehaviour {
    public void ExecuteCloudScript() {
        var request = new ExecuteCloudScriptRequest() {
            FunctionName = "bushelOnYourFirstDay",
            GeneratePlayStreamEvent = true
        };
        PlayFabClientAPI.ExecuteCloudScript(request,
            (result) => {
                Debug.Log(result.FunctionResult);
                Debug.Log(result.Logs[0].Message);
            },
            (error) => {
                Debug.Log("Error on request:");
                Debug.Log(error.ErrorMessage);
            }
        );
    }
}

You can run any CloudScript against a player through the web interface. Players > Player* > Overview > Run CloudScript The CloudScript doesn't even have to be in live revision.

You should create a pipeline for more complex cloud code. Using Atom plugin to concat JS files, add comments and push to PlayFab.

Shared Data

There are 2 types of shared data across all players:

  1. Publisher Data: Key/value-pairs that can be read by all clients connected to titles by the publisher. Publisher data can only be written by servers. Publisher internal data is only available to servers, not to clients even for reading.
  2. Title Data: Key/value-pairs that can be read by all clients connected to that title. Title data can only be written by servers. Title internal data is only available to servers, not to clients even for reading.

Go to Content > Title Data to edit the title data.

You can find the Publisher ID in Settings > Credentials. Publisher is dubbed as "Studio" in some parts of the web interface. All titles inside the same studio have the same publisher ID.

Title data is commonly used for:

  • Shared game configuration.
  • Add quests post-launch.
  • Tune existing configuration, e.g. tune power of stuff.
  • Validate client actions.

Title data usage example:

using UnityEngine;
using PlayFab;
using PlayFab.ClientModels;

public class MyScript : MonoBehaviour {

    public void GetTitleData() {
        var request = new GetTitleDataRequest() {};
        PlayFabClientAPI.GetTitleData(request,
            (result) => {
                var data = result.Data;
                if (data.ContainsKey("Key1")) { Debug.Log(data["Key1"]); }
                if (data.ContainsKey("Key2")) { Debug.Log(data["Key2"]); }
            },
            (error) => {
                Debug.Log("Error on request:");
                Debug.Log(error.ErrorMessage);
            }
        );
    }

}

Player Data

Player data applies to players or a group of players. They are simple key value pairs.

There are 3 different types of player data:

  1. Player Data: Title and player specific key/value-pairs that both clients and servers can write and read. Used with UpdateUserData and GetUserData
  2. Read-only Player Data: Title and player specific key/value-pairs that clients can only read, but servers can write and read. Used with UpdateUserReadOnlyData and GetUserReadOnlyData.
  3. Internal Player Data: Title and player specific key/value-pairs that only servers can write and read. Used with UpdateUserInternalData and GetUserInternalData.

You can also define publisher player data. Publisher player data is the same 3 types of player data but shared across all titles with the same publisher. Each title can have a publisher.

UpdateUserPublisherData, GetUserPublisherData, etc.

All player data can be public or private. Public data can be accessed by other players, private data only by the player.

Player data usage example:

using UnityEngine;
using System.Collections.Generic;
using PlayFab;
using PlayFab.ClientModels;

public class MyScript : MonoBehaviour {

    public void SetUserData() {
        UpdateUserDataRequest request = new UpdateUserDataRequest() {
            Data = new Dictionary<string, string>() {
                {"Ancestor", "Arthur"},
                {"Successor", "Fred"}
            }
        };
        PlayFabClientAPI.UpdateUserData(request,
            (result) => {
                Debug.Log("Successfully updated user data");
            },
            (error) => {
                Debug.Log("Error on request:");
                Debug.Log(error.ErrorDetails);
            }
        );
    }

    public void GetUserData() {
        GetUserDataRequest request = new GetUserDataRequest() { Keys = null };
        PlayFabClientAPI.GetUserData(request,
            (result) => {
                Debug.Log("Got user data:");
                if ((result.Data == null) || (result.Data.Count == 0)) {
                    Debug.Log("No user data available");
                } else {
                    foreach (var item in result.Data) {
                        Debug.Log("    " + item.Key + " == " + item.Value.Value);
                    }
                }
            }, (error) => {
                Debug.Log("Error on reques:");
                Debug.Log(error.ErrorMessage);
            }
        );
    }

}

Item Data

Each item can have custom key/value-pairs associated with it. This can be used for anything e.g. item type, weapon speed or mana cost. Economy > Catalogs > Item* > Custom Data. Catalog item data is shared between all instances of the item.

Each instance of an item can also have custom key/value-pairs. Players > Player* > Inventory > (i) symbol next to an item Used with UpdateUserInventoryItemCustomData through API.

PlayStream

PlayStream is PlayFab's proprietary real-time segmentation engine. It allows you to instantly run actions against players who match criteria you specify.

`PlayStream > Segments > New Segment`
Segment name: Adventurers
Edit Filter: `Statistic value xp is greater than or equal to 500`
Add Filter:  `Statistic value xp is less than 600`
Entered Segment, Add Action: Grant item, potato, 1.
Entered Segment, Add Action: Grant virtual currency, GO, 1000.
Save Segment
Open:
1. PlayStream > Segments
2. Dashboard
3. Players > Player* > Statistics

Change player xp stat to 550 and save player statistics
Switch to Segments window and you'll see that there is now 1 Adventurer.
Switch to Dashboard and you see the chain of events on the right debugger.

You can show special stores to players in a specific segment. Segment overrides allow e.g. holiday discounts.

`Economy > Catalogs > main > Stores > [x] fruits > Duplicate`
Rename the "fruits (1)" to "fruits-adventurers"
Modify prices > Decrease prices by 50%
Save Store
Open the original "fruits"
Change Segment Overrides > 1. Adventurers to fruit-adventurers
Save Store
Now all players in "Adventurers" segment get the "fruit-adventurers"
store when they ask for "fruits" store.

Binary Data

You can upload files for clients to download.

`Content > File Management > New Folder`
Folder name: v001
Press "Upload files" and select some image.

Updated files take up to 24 hours to refresh. New files are available instantly. This is because of caching. You can use title data to tell clients which asset version you are on e.g. v001 etc. so downloads update right away.

using UnityEngine;
using PlayFab;
using PlayFab.ClientModels;

public class MyScript : MonoBehaviour {
    public void GetContentDownloadUrl() {
        var request = new GetContentDownloadUrlRequest() {
            Key = "v001/tutorial.jpg"
        };
        PlayFabClientAPI.GetContentDownloadUrl(request,
            (result) => {
                Debug.Log(result.URL);
            },
            (error) => {
                Debug.Log("Error on request:");
                Debug.Log(error.ErrorMessage);
            }
        );
    }
}

Custom Server

You can spin up custom servers through PlayFab. Stateful servers running your code, meant for matchmaking/multiplayer logic. For stateless server-side logic, use CloudScripts.

Server build has to be compatible with Windows Server 2012 R2. The executable will receive a lot of information as command line parameters. Basically a zip file with an gameserver.exe and optional required files.

Registration

# Auth experience should start with the following + CreateAccount:true
LoginWithAndroidDeviceID
LoginWithIOSDeviceID
# The following are acceptable + CreateAccount:true
LoginWithCustomID
LoginWithFacebook
LoginWithGameCenter
LoginWithGoogleAccount
LoginWithKongregate
LoginWithSteam
LoginWithTwitch
# All of there previous "Login" generated accounts can do the following:
AddUsernamePassword
# Rarely you want to create a PlayFab account straight away...
RegisterPlayFabUser

# These can only be used to login, not to create an account:
LoginWithPlayFab / LoginWithEmailAddress

# You will always end up with atleast a PlayFab account:
# If you want to control the access to the same PlayFab account
# using multiple login providers, use the following:
LinkAndroidDeviceID     / UnlinkAndroidDeviceID
LinkCustomID            / UnlinkCustomID
LinkFacebookAccount     / UnlinkFacebookAccount
LinkGameCenterAccount   / UnlinkGameCenterAccount
LinkGoogleAccount       / UnlinkGoogleAccount
LinkIOSDeviceID         / UnlinkIOSDeviceID
LinkKongregate          / UnlinkKongregate
LinkSteamAccount        / UnlinkSteamAccount
LinkTwitch              / UnlinkTwitch

# Examples:
LoginWithAndroidDeviceID
    -> UpdateUserTitleDisplayName (allows specifying display name)
        -> LinkFacebookAccount
LoginWithIOSDeviceID
    -> LinkFacebookAccount (error, already linked to another PF account)
        -> LoginWithFacebook (recover the old account)
            -> LinkIOSDeviceID
            # OR ???
            -> UnlinkFacebookAccount.
                -> LoginWithIOSDeviceID
                    -> LinkFacebookAccount
LoginWithIOSDeviceID
    -> AddUsernamePassword (enables username / password login)
        -> now both LoginWithIOSDeviceID and LoginWithPlayFab work

Scalability

Everything is automatically scaled. Forum post says they have tested with 1,000,000 concurrently connected players, requires further testing. The test was for a major publisher. They peaked at 22,000 requests per second.

Sources