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.
- 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.
- 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.
- Join. JoinTournament(leaderboardId, ...) enrolls the player and automatically collects any entry fee, failing if they can't afford it.
- Play. PostTournamentScoreUTC/PostTournamentScoreWithResultsUTC post to the tournament's leaderboard, same as any leaderboard score post.
- 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.
- 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
- 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.
- 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.
- 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.
- 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
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;
}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);
}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.