⌘K
↑↓ Navigate ↵ Open Esc Close
Tier 2Global App11 / 29

Global Properties: tunable config without a client build

Authentication: this call fires in the same post-login burst as everything else in this series.

brainCloud provides Global Properties: portal-configured key/value data, read with a single client call. Loot box drop rates, per-rarity move speeds, and child-account limits are all Global Properties in bitBuddies, fetched right after login. A designer can retune drop rates or unlock requirements without a new client build. By this point in the series, a new developer already has auth, stats, currency, and a store wired up. Global Properties covers what's left in Tier 2: live-tunable numbers, without a hand-rolled config system.

How it works

  1. Ask for exactly the properties you use, by name. ReadSelectedProperties(string[]) takes a fixed array of property names. This is not a "fetch everything the portal has" call. Adding a new tunable property server-side also means adding its name to this array client-side.
  2. A property's value is not a ready-to-use primitive. Complex config (a rarity table, a per-type speed map) comes back double-encoded: parsing the JSON once yields the property envelope; a second parse pulls the value string into the real data.
  3. Simple scalar properties still come back as strings. Even a plain integer, like a max-child-count, needs an explicit parse (int.TryParse) rather than a direct cast. The SDK does not hand back a ready-made int.
  4. A property can be server-only, which makes it a place to store a secret. Leave it off ReadSelectedProperties/ReadProperties and only cloud code/S2S can read it, via globalAppServiceProxy; a client never can. Good for a third-party API key a script needs for an outbound call, or a drop rate that shouldn't be exposed.
  5. isSecret goes further: masked even from server-side view unless whitelisted. Masked in the portal UI, masked in API responses, stripped from audit logs. Cloud code/S2S read the real value through a separate accessor (bridge.getGlobalProperty()), and only if their tier is whitelisted for it; off the whitelist gets a 403, not a masked placeholder. Sourcing: step 4 is confirmed against brainCloud's public API reference. isSecret and a related LicensorOnly flag (restricts a property to super users on a specific email domain) are real but documented internally, not exercised in any reference app here. Managed in the portal under Design → Cloud Data → Global Properties.

Code

The call: three named properties, right after loginBrainCloudManager.cs:120-126
string[] propertyNames = new[] { "MysteryBoxInfo", "ChildAccountMaximum", "BuddyMoveSpeedInfo" };
Wrapper.GlobalAppService.ReadSelectedProperties(
    propertyNames,
    HandleSuccess("Get Global Properties Success", OnGetGlobalProperties),
    HandleFailure("Get Mystery Box Info Failed", OnFailureCallback));
Double-decoded JSON configBrainCloudManager.cs:152-176
var mysteryBoxInfo = (Dictionary<string, object>)data["MysteryBoxInfo"];
string innerJson = (string)mysteryBoxInfo["value"];                       // value is itself a JSON string
var lootboxes = (Dictionary<string, object>)JsonReader.Deserialize(innerJson);

foreach (var keyValuePair in lootboxes)
{
    var boxDict = (Dictionary<string, object>)keyValuePair.Value;
    var boxInfo = new MysteryBoxInfo
    {
        Rarity = boxDict.GetString("rarity"),
        BoxName = boxDict.GetString("boxName"),
        currencyType = boxDict.GetValue<CurrencyTypes>("unlockType"),
        UnlockAmount = boxDict.GetValue<int>("unlockAmount"),
        LevelRequirement = boxDict.GetValue<int>("levelRequirement"),
    };
    listOfBoxInfo.Add(boxInfo);
}
A scalar property, still a string on the wireBrainCloudManager.cs:178-182
var childAccountMaxObj = data["ChildAccountMaximum"] as Dictionary<string, object>;
if (int.TryParse((string)childAccountMaxObj["value"], out int value3))
{
    GameManager.Instance.ChildCountMaximum = value3;
}
A second double-decoded property, order-dependent on read-outBrainCloudManager.cs:184-196
var buddyMoveSpeedObj = (Dictionary<string, object>)data["BuddyMoveSpeedInfo"];
string moveSpeedJson = (string)buddyMoveSpeedObj["value"];
// moveSpeedJson = "{\"Starter\":1,\"Basic\":1.1,\"Rare\":1.2,\"SuperRare\":1.3,\"Legendary\":1.4}"

var moveSpeedDict = (Dictionary<string, object>)JsonReader.Deserialize(moveSpeedJson);
List<float> moveSpeeds = new List<float>();
foreach (var kvp in moveSpeedDict)
{
    moveSpeeds.Add(Convert.ToSingle(kvp.Value));
}
GameManager.Instance.BuddyMoveSpeeds = moveSpeeds;   // order = dictionary iteration order, not indexed by rarity key
Creating a server-only property, from brainCloud's own docs (Cloud Code, not exercised in this repo)
var globalAppProxy = bridge.getGlobalAppServiceProxy();

var propertyName = "initialHealth";
var jsonValue = {};
jsonValue.health = 6;
jsonValue.regen = 1;

var postResult = globalAppProxy.sysUpdatePropertyJson(propertyName, jsonValue);
if (postResult.status == 200) {
    // Success! Only cloud code and S2S can call this; a client never can.
}

Gotchas worth knowing up front

  • ReadSelectedProperties won't just hand you everything. It returns only the fixed list of names passed in. A new tunable added in the portal is invisible to the client until its name is added to that array. There is no fallback "unknown property" path.
  • Don't update a property on every request. SysUpdatePropertyJson refreshes an app-wide cache across every API server on write. For anything that changes often during the day, use Global/System Entities or unowned Custom Entities (Persistent State) instead.
  • A missing or unparseable scalar fails silently. int.TryParse failing on ChildAccountMaximum leaves ChildCountMaximum at its default (0), with nothing logged. If child-account creation caps unexpectedly at zero, check this parse first.
  • A 403 Forbidden on a secret property doesn't mean the property doesn't exist. It means the caller isn't on that property's read whitelist for its tier. A masked placeholder value, not an error, is what a whitelisted caller gets back instead. Don't debug a 403 on a known-real property as a naming or typo issue before checking whether it's flagged secret and whether the caller's tier is actually whitelisted for it.