⌘K
↑↓ Navigate ↵ Open Esc Close
Tier 3TournamentEvent16 / 29

Tournament: the reward arrives as an event, not something you poll for

Leaderboards: a tournament is a leaderboard with a lifecycle wrapped around it; Player-to-Player Events for the generic event mechanism a tournament's completion rides on.

brainCloud's Tournament service builds a competitive season on top of a leaderboard: join, post scores, and the server tracks standings and payouts. The join and post calls aren't the interesting part; the interesting part is how a client learns a tournament has ended and a reward is waiting. BombersRTT (repositories/unity-examples/BombersRTT) never polls for that. A SYSTEM_TOURNAMENT_COMPLETE Event (the same generic Event mechanism from Player-to-Player Events) arrives in the normal event queue, and the client reacts to it the same way it reacts to a player-sent invite.

The full lifecycle

A tournament run passes through six stages. BombersRTT, the reference app for this article, only touches the last three (join, play, claim); the first three are configured once, server-side, and apply to every run.

  1. Create. SysCreateTournamentTemplate(tournamentCode, configJson) (cloud-code/S2S only) defines what a tournament is: entryType ("PLAYER" or "GROUP"), an entry fee, payoutRules (reward by rank, e.g. rank 1 gets 100 coins, top 10% gets 5), and a postScript, a Cloud Code script that runs once rankings are calculated and is what actually enqueues each participant's SYSTEM_TOURNAMENT_COMPLETE event.
  2. Schedule. The template alone has no dates. Scheduling lives on the underlying leaderboard: GetTournamentStatus returns a tournamentTimetable with tRegistrationStart/tRegistrationEnd/tPlayStart/tPlayEnd and a tState (ACTIVE, etc). Each recurrence of a recurring tournament is a new versionId on that same leaderboardId, which is exactly the pair GetTournamentStatus/ ClaimTournamentReward take.
  3. Join. JoinTournament(leaderboardId, ...) enrolls the player and automatically collects any entry fee, failing if they can't afford it.
  4. Play. PostTournamentScoreUTC/PostTournamentScoreWithResultsUTC post to the tournament's leaderboard, same as any leaderboard score post.
  5. Reward computed. Once play ends, the postScript resolves each participant's rank against payoutRules. ViewCurrentReward previews the projected reward mid-tournament from current standing; ViewReward reads the final reward after the run has ended. Neither call pays out.
  6. Claim. ClaimTournamentReward is what actually pays out. This is the step BombersRTT demonstrates, triggered by the SYSTEM_TOURNAMENT_COMPLETE event from step 1's postScript, covered below.

Tournaments and divisions

A division set is the same machinery, used to shrink the competitive pool, not to rank players by skill. SysCreateDivisionSetConfig(divSetId, configJson) (cloud-code/S2S only) sets a templateLeaderboardId and a cap (maxPlayers or maxGroups); brainCloud fills one division instance up to that cap, then opens a new one, and repeats for as long as players keep joining. Which instance a player lands in is random, based on enrollment order, not their skill or rank. The point is engagement: competing against 10 people for a top spot feels winnable in a way competing against a worldwide leaderboard of thousands doesn't, so divisions exist to make that top spot reachable, not to sort players into Bronze/Silver/Gold. A skill-based ladder is a real, related, but separate pattern brainCloud calls Tiers: a series of ordinary leaderboards and tournaments a design team sets up one per tier, with cloud code moving a player up or down a tier based on how the previous tournament went. Divisions don't do that on their own. Client-side, the calls mirror the plain-tournament calls one-to-one: JoinDivision(divSetId, tournamentCode, initialScore, ...) plays the role of JoinTournament, returning the specific leaderboardId for whichever instance the player landed in, and GetDivisionInfo/GetGroupDivisionInfo play the role of GetTournamentStatus. Everything downstream, posting scores, viewing rewards, claiming, uses that returned leaderboardId exactly the way a plain tournament does.

How it works

  1. A tournament is identified by a leaderboard id and a version number. There's no separate tournament id: GetTournamentStatus(leaderboardId, versionId, ...) and ClaimTournamentReward both take that same pair.
  2. Completion arrives as a system-generated Event; it isn't fetched separately. The client's normal GetEvents() poll (see Player-to-Player Events) returns SYSTEM_TOURNAMENT_COMPLETE alongside any player-sent events. brainCloud enqueues it the same way.
  3. The event handler routes straight into the claim call. There's no separate "check if I have a reward" step. The event is the signal to claim.
  4. Claiming and checking status are separate calls. ClaimTournamentReward processes whatever's outstanding; GetTournamentStatus is a read-only lookup a UI can call anytime to show current standing without triggering a payout.

Code

System event routes directly to a claim, alongside player-sent event typesGPlayerMgr.cs:268-288
public bool ProcessBCEvents()
{
    bool bNewItemProcessed = false;
    // Do we have any events to process?
    if (m_baseBrainCloudEventDataList.Count > 0)
    {
        BaseBrainCloudEventData baseBrainCloudEventData = GetNextAvailableBrainCloudEvent();
        if (baseBrainCloudEventData != null)
        {
            if (baseBrainCloudEventData.eventType.Contains(BrainCloudConsts.JSON_EVENT_SYSTEM_TOURNAMENT_COMPLETE))
            {
                // SYSTEM_TOURNAMENT_COMPLETE Event received
                // claim the reward
                TournamentClaimReward(baseBrainCloudEventData, null, null, baseBrainCloudEventData);
                bNewItemProcessed = true;
            }
            // Process other event types here
        }
    }
    return bNewItemProcessed;
}
The claim call: leaderboard id + version, not a separate tournament idGPlayerMgr.cs:290-297
public void TournamentClaimReward(BaseBrainCloudEventData in_brainCloudEventData, SuccessCallback in_success = null, FailureCallback in_failure = null, object cbObject = null)
{
    GCore.Wrapper.Client.TournamentService.ClaimTournamentReward(in_brainCloudEventData.leaderboardId,
        in_brainCloudEventData.versionId,
        onClaimRewardSuccess + in_success,
        onClaimRewardFailed + in_failure,
        cbObject);
}
A read-only status check, independent of claimingGPlayerMgr.cs:299-306
public void TournamentGetTournamentStatus(BaseBrainCloudEventData in_brainCloudEventData, SuccessCallback in_success = null, FailureCallback in_failure = null)
{
    GCore.Wrapper.Client.TournamentService.GetTournamentStatus(in_brainCloudEventData.leaderboardId,
         in_brainCloudEventData.versionId,
        in_success,
        in_failure,
        null);
}

Gotchas worth knowing up front

  • SYSTEM_TOURNAMENT_COMPLETE arrives through the same event queue as everything else. This reference app's ProcessBCEvents is the exact queue Player-to-Player Events's player-invite events flow through; it's one more eventType value to branch on, not a separate delivery mechanism.
  • The leaderboard/version pair is load-bearing. leaderboardId and versionId together identify which run of a recurring tournament the reward belongs to. Pairing the current leaderboard id with a stale version, or vice versa, targets the wrong tournament period.
  • This is genuinely one SDK's worth of evidence. No other reference app in this repo exercises Tournament, and none exercises the create/schedule/divisions surface described above at all; those steps are confirmed against brainCloud's public API reference, not reference-app source.