Cloud Save SDK 3.1 - Queries and Access Classes

Last month we released version 3.1 of the Unity Cloud Save SDK which added support for running Queries directly from a game client and for reading and writing to data in different Access Classes.

We just expanded the Unity SDK tutorial for Cloud Save to add examples of how to use Queries and Access Classes with both Player Data and Game Data (using Custom Items).

Queries and Indexes

Cloud Save Queries allows you to define indexes on Player Data and Game Data stored in Cloud Save so you can search / query data using Cloud Code. You must create an index before you save data that can be queried.

You can create and manage indexes in the Unity Cloud Dashboard.

Player Data: Access Classes

There are 3 different Access Classes for Cloud Save Player Data:

  • Default: Readable and writable by the player the data corresponds to
  • Public: Readable by any player, writable by the player the data corresponds to
  • Protected: Readable by the player the data corresponds to, only writeable from a server

Game Data: Access Classes

Cloud Save supports 2 different Access Class levels for Custom Items - these can be for Game Data that does not have to be bound to a player:

  • Default: Readable by any player, writeable only from a server
  • Private: Readable and writeable only from a server

Example

If you want to create a system to allow players to find other players - for example for async matchmaking - you could first create an index for the names of the properties you want to be able to match on (e.g. language, location, etc).

Player 1:

In your game, you could then write data needed for matching to the Public Access Class for the Player, where it can be read (and queried) by other players. In this case, language and a location.

using SaveOptions = Unity.Services.CloudSave.Models.Data.Player.SaveOptions;

var data = new Dictionary<string, object> { { "language", "FR" }, { "location", "Paris" } };
await CloudSaveService.Instance.Data.Player.SaveAsync(data, new SaveOptions(new PublicWriteAccessClassOptions()));

Player 2:

You can then query from another client to get back a list of Player IDs for players that match a query:

var query = new Query(
  new List<FieldFilter> {
    new FieldFilter("language", "FR", FieldFilter.OpOptions.EQ, true),
    new FieldFilter("location", "Paris", FieldFilter.OpOptions.EQ, true)
  },
  { "location", "name", "avatar" } // List of Public data keys to return along with results
);
var results = await CloudSaveService.Instance.Data.Player.QueryAsync(query);

Debug.Log($"Number of players returned {results.Count}");
results.ForEach(r => {
  Debug.Log($"Player ID: {r.Id}");
  r.Data.ForEach(d => Log($"Key: {d.Key}, Value: {d.Value.GetAsString()}"));
});

You can also use Custom Items to Query Game Data in the same way, or use Unity Cloud Code (or a game server) to query across data in any Access Class, and for any Player or Custom Item.

You can use client side methods to quickly and easily to unlock new functionality without adding any server side logic and you can combine the functionality in Cloud Save with Cloud Code to create full featured systems for guilds, quests, community goals, NPCs, auction houses, inventory management, item trading, mailboxes, loot tables, etc

1 Like

Hi lainUnity3D,
Iā€™m currently facing a challenge as my game has almost reached the limit of 20 Indexes in Unity Cloud Save. Iā€™m exploring ways to optimize the usage of Indexes, but Iā€™m wondering if thereā€™s a possibility to increase the maximum number of Indexes beyond 20. Is there any guidance or solution available for managing a higher number of Indexes efficiently?

Hi @tranpd ,

Apologies for the delay in following up.

I donā€™t have much of an answer to this yet but we appreciate the feedback and that the currently limit might be overly restrictive for some use cases - particularly games with complex behaviours - and are reviewing what we can do to provide guidance and on our end to either increase the limit or provide ways to work with it that make running into it less likely.

Having generic names for indexes (e.g. ā€˜Typeā€™, ā€˜IDā€™, ā€˜Nameā€™) and then using them across multiple objects of a different type can help reduce the number of indexes required when working with Game Data (Custom Items).

If you would like to share any details about the sorts of things you are modelling (Game Data, Player Data, etc) Iā€™d be happy to take a look to either make suggestions or to inform our own thinking about how we can better support the sorts of things you would like to be able to do.

Best regards,

Iain

1 Like

It might be handy to have an option beyond EQ, NE etc for ā€œcontainsā€ or ā€œdoesnā€™t containā€ for strings. Then I can Serialize a more complex class under a key ā€œGameDataā€ for example and query contains ā€œopponent: nullā€ to find empty games for a lobby.

At the moment, it seems I need to separate our an ā€œopponentā€ key and have its value as empty to do the same job.

@IainUnity3D

Do you have any examples anywhere for querying from within CloudCode? i am having troubles trying to piece it together without documentation. I was trying

_gameApiClient.CloudSaveData.QueryDefaultPlayerDataAsync(context, context.ServiceToken, context.ProjectId, queryIndexBody, default);

But I canā€™t work out the queryIndexBody.

EDIT -------
I managed to have some success, even if it can be improved. Sharing for other newbies.
Annoyance 1: A limit of 1000 breaks the query. I canā€™t find documentation to the max limit, just default.
Annoyance2: If your return keys havenā€™t been updates since you indexed fields, you get a bad request.

public async void QueryPlayerData(IExecutionContext context)
{
    // Set the query (if Tier == Standard)

    FieldFilter filter1 = new FieldFilter("Tier", "Standard", FieldFilter.OpEnum.EQ, true);
    var fields = new List<FieldFilter> { filter1 };
    var returnKeys = new List<string> { "ShortPlayerName" };
    var queryIndexBody = new QueryIndexBody(fields, returnKeys, 0, 100);  

    // Conduct the query
    var  results = await _gameApiClient.CloudSaveData.QueryDefaultPlayerDataAsync(context, context.ServiceToken, context.ProjectId, queryIndexBody, default);

    // Log the number of results
    _logger.LogInformation($"QueryPlayerDataTest results: {results.Data.Results.Count}");

    // Debug each result for checking
    foreach (var result in results.Data.Results)
    {
        _logger.LogInformation($"Player ID: {result.Id}");

        foreach (var item in result.Data)
        {
            _logger.LogInformation($"Key: {item.Key}, Value: {item.Value}");
        }
    }
}

Thanks for the feedback! I donā€™t have an example of using Queries and Access Classes from Cloud Code that I can link to off hand, but I think one is being worked on at the moment.

  1. Annoyance 1: A limit of 1000 breaks the query. I canā€™t find documentation to the max limit, just default.

Thanks for flagging that, Iā€™ll make a note to expand on that in the documentation - I appreciate there is some other guidance around limits that are also not covered anywhere (or in some cases, only in the REST API docs).

  1. Annoyance2: If your return keys havenā€™t been updates since you indexed fields, you get a bad request.

Thanks again for feedback on this, it helps with prioritisation. This is definitely a limitation of the platform we would like to address.

Thatā€™s really interesting feedback, we have been considering supporting something like this (for example JSONPath style queries for key values, if you are familiar with that). Do you think that would be helpful?