Overview
What is Tournament-SDK
Tournament SDK is solution for orchestrating real-time tournaments for your game. It provides a Unity plugin that allows your playerbase to interact, enter and compete in tournaments. All within your game client witout a players need to leave the game.
It also allows you to schedule and reccure specific tournaments, hook onto your backed for deducting entry fees and delivering prizes, and much more.
Implementation examples
Solution parts
Dashboard
Web based dashboard allows you to handle all neccesary live-ops tasks around tournaments. Creating tournament templates, scheduling tournaments, analyzing and optimizing tournament performance.
Unity plugin
Easy to use Unity plug-in gives you access to all tournament metadata and neccesary orchestration tools for executing tournaments within your game client. By default it comes with example UI. These can be rebuild or not used at all in favour of custom UX/UI integration.
Code example of loading tournament data:
private IEnumerator LoadCoroutine()
{
//load/refresh tournament list
yield return BackboneManager.Client.LoadTournamentList();
//get first tournament in the list
var tournament = BackboneManager.Client.Tournaments.TournamentList[0];
//load/refresh all tournament data
yield return BackboneManager.Client.LoadTournament(tournament);
//access and visualize tournament metadata
var name = tournament.Name;
var startTime = tournament.Time;
var status = tournament.Status;
var userInvite = tournament.Invite;
//...
}
Best with Quantum
Quatum makes it easier to achieve best tournament experience for players as good implementaion of tournaments in your game should include:
- Reconnect - players do not want to lose important matches just because they had unexpected disconnection from game server.
- Replays - let players rewatch competitive matches so they can analyze where they made a mistake that lead to loss.
- Spectator - give tournament admins ability to late join any match and spectate allowing to create stream coverages of the most premium tournaments in your game.
SDK & Release Notes
v1.3.x
Latest release date: 17.12.2022
Latest version v1.3.1 - Download SDK
1.3.1
Release date: 1.12.2022
-Added new merging tags for tournament webhooks.
-Added option to submit results from game server.
-Added option to search user by external id in dashboard.
-Added option to create SOLO tournament matches (solo leaderboards).
-Added option to query match data from game server.
-Added option to modify match points from predefined stats.
-Added ability to query tournament data for custom analytics.
-Added new invite option for tournament via private CODE.
-Added various QoL improvements in dashboard.
-Added webGL support.
-Added "public website" with customizable player leaderboards.
-Fixed issue where phase would get stuck in certain situations.
-Fixed issue where optimized SE bracket would not propagate corret round settings to clients after tournament start.
-Fixed issue when calling game session create while results are being processed.
-Fixed incorrect final placement for 'dynamic brackets'.
-Fixed dashboard user search failing in certain situations.
-Fixed issue when inviting from previous tournaments where no rewards were given.
-Fixed issue when inciting by template would include canceled tournaments.
-Fixed issue caused by negative tiebreaker stats.
-Fixed issue with brackets where checked in user would not be moved forward.
-Fixed issue with brackets where user would not get points for autowin in certain situations.
-Changed sorting order on tournament list.
1.3.0
Release date: 1.6.2021
-Added new phase type 'Dynamic brackets'.
-Added default UIs for dynamic brackets.
-Added seed value from previous phase to score.
-Added methods to get phase score by party id or user id.
-Added methods to help calculate phase start and finish timestamps.
-Added methods to help calculate round start and finish timestamps.
-Added method to get start and finish deadline of next game in active match.
-Added option to vertically fill groups in round robin phase.
-Added new dashboard tournament detail view with option to search for specific user. Improved performance and usability for BIG tournaments.
-Added option to batch prize delivery into multiple server calls.
-Added merging tag for server callbacks that provide user names on specific placement in tournament (e.g. #TOURNAMENT_PARTY_1_USERNAMES# for 1st placement).
-Added option to accept replay when specific amount of replay submissions are received.
-Added dashboard callback option for discord.
-Fixed warning messages not correctly triggered on default UIs in certain situations.
-Fixed issue where in some rare cases tournament hub could get stuck in WaitingForUserReadyConfirmation state.
-Fixed issue where in some rare cases submitted results did not close match immediately.
-Fixed issue in arena matchmaking where players were not correctly matched based on their intermediate score.
-Fixed issue in arena matchmaking causing autowin streak in certain situations.
-Fixed sponsor name and image fields returning incorrect values.
-Changed 'GetMatchNextGameDeadline'. This method is now obsolete. Use 'GetMatchNextGameStartDeadline' or 'GetMatchNextGameFinishDeadline' instead.
-Changed tournament player count to be recalculated when tournament starts to reflect valid users only.
-Changed seeding for single and double elimination brackets making it more fair.
-Changed tiebreaker resolution in phase/match to use seed value from previous phase.
-Changed dashboard round time settings making it easier to edit and understand.
v1.2.x
SDK download no longer available for v1.2.x. Please update to newer version.
1.2.3
Release date: 11.11.2020
-Added setting to allow skipping phase if less players sign up.
-Added API for browsing tournament phase score.
-Added party code to be returned with tournament data.
-Added automatic round optimization for SE bracket (skipping rounds in case of less players).
-Added automatic captain flag passing in case of inactivity.
-Added setting for custom phase tiebreaker stats.
-Added game and match point distribution data on round object.
-Added default UI for browsing phase score table.
-Added dashboard time localization.
-Added dahsboard analytics view for total daily unique attendees.
-Added dashboard tournament filter on schedule tab.
-Fixed late result submission reopening match in certain situations.
-Fixed "create game session" api causing race condition issues under high load.
-Fixed dashboard analytics overview, it no loger shows deleted templates.
-Fixed Playfab integration version setting for cloud scripts. Can be now set to "0" or "Live" to point to latest deployment.
-Fixed generating invalid JSON payload for Playfab if user id contains only numbers.
-Fixed race condition that updated player score to invalid values in certain situations.
-Fixed sign up issue via external providers where last player would be rejected.
-Fixed issue where auto wins and loses would be given earlier than round deadline in some cases.
-Fixed issue where spectators would not refresh match data and not trigger match start in certain situations.
-Fixed issue allowing to create tournament without any phase.
-Fixed issue allowing misconfiguration of tournament time deadlines.
-Changed tournament list default update interval to be less agressive.
-Changed tournament list default window to be +- 3 days.
1.2.2
Release date: 7.4.2020
-Added API for creating/joing tournament party with sharable code.
-Added default UI scripts to handle parties with sharable code.
-Added support for partially checked in team to start a match.
-Added property 'PartiallyCheckedInTeamCount' on tournament match.
-Added phase timer on default UI screens.
-Fixed misspelled text in default UI spcripts.
-Fixed stack overlow error when calling ReinitializeComponent method.
-Fixed tournament position being incorrectly shown in parties bigger than 1.
-Fixed tournament hub not setting correct state when user is knocked out early.
-Changed property 'CheckedInTeamCount' to 'FullyCheckedInTeamCount' on match.
1.2.1
Release date: 24.3.2020
-Added get season stats method accepting any platform user id (E.g. PlayFab user id).
-Added API to load specific tournament matches by their ids.
-Added reinitialization method for default UI scripts.
-Added tournament match detail dialog in default UI screens.
-Fixed single elimination bracket being incorrectly rendered with default UI screens.
-Fixed tournament hub not informing correctly about state in certain bracket situations.
-Fixed default UI scripts throwing error when reactivated without reinitialization.
-Fixed testing tournaments being included in template statistics.
-Fixed Playfab prize delivery payload to no longer use arbitrary user id.
-Fixed Playfab store import to fail when some item fields were missing/empty.
-Changed default UI script to no longer show user specific points when no party is set.
1.2.0
Release date: 12.12.2019
-Added new phase type 'Round robin'.
-Added new phase type 'Double elimination bracket'.
-Added pre-build default UI screens for new formats.
-Added new status to indicate if tournament is in progress.
-Added party invite method accepting any platform user id (E.g. PlayFab user id).
-Added API to browse all past or future tournaments on the client.
-Changed pre-build default UI screen prefabs for easier integration.
v1.1.x
SDK download no longer available for v1.1.x. Please update to newer version.
v1.1.2
Release date: 31.8.2019
-Added pre-build default UI screens.
-Added PlayFab integration.
-Added change nickname option for user.
-Added new tournament type for testing.
-Added API to browse all tournament matches on the client.
-Added 'UserMatches' property in tournament.
-Added 'AllMatches' property in tournament.
-Added decline party invite option for user.
-Added tournament hub match interface for easier hookup to room/lobby API.
-Added property to see match points rewarded for each user after match finished.
-Fixed error when parsing decimal numbers on certain localizations.
-Fixed and improved re-throwing of exceptions in nested AsyncOperation API calls.
-Fixed updating of 'CurrentGameCount' property in tournament match.
-Fixed issue where 'UserActiveMatch' had different object reference.
-Fixed tournament match having not set values of 'WinScore' and 'MaxGameCount' in certain situations.
-Changed plugin folder structure.
-Removed 'BracketMatches' property in tournament which was replaced with new API for browsing tournament matches that covers this functionality.
v1.1.1
Release date: 17.5.2019
-Added compression option to resource cache.
-Added new tournament hub status 'KickedOutByAdmin'.
-Added interface to inject different file storage APIs for caching (E.g. Nintendo Switch).
-Fixed incorrect serialization of certain tournament data on the client.
-Fixed various small non critical issues.
v1.1.0
Release date: 29.3.2019
-Added optional resource cache script for loading url images for tournamens.
-Added checkin team count in tournament match.
-Added checkin user count in tournament match.
-Added user season profile with tournament statistics.
-Added methods to more easily get stats from game session.
-Added global custom properties for game title.
-Added language setting for user.
-Added Steam login provider.
-Fixed 'GetMatchStartDeadline' returning invalid time in certain cases.
-Fixed exception thrown sometimes during compression and decompression of replays.
-Fixed client not reporting errors during login.
-Fixed icorrect sorting of users in tournament match.
v1.0.x
SDK download no longer available for v1.0.x. Please update to newer version.
v1.0.1
Release date: 16.2.2019
-Added user reporting from the client (E.g. for suspected cheating).
-Added replay submission.
-Added custom game and match point system.
-Added FFA format for phases.
-Added max game count cap for round match.
-Added 'UserPlayedRoundCount' property informing how many rounds user already passed.
-Added 'UserIsParticipating' property for detection if user has moved to next phase.
-Added new tournament hub status 'ResolvingPartiallyFilledMatch'.
-Added idication of silent login on subsequent client initializations.
-Fixed user active match not refreshing after result submission.
-Fixed user not automatically moved to next round after overdue match.
-Fixed tournament data not being sometimes automatically refreshed after finished match.
-Fixed incorrect indication of user being knocked out in certain situations.
-Fixed pre-start check in not being applied.
-Fixed party invite receiving invalid party id.
-Changed accepting tournament party invite, it now requires tournament id parameter.
v1.0.0
Release date: 21.1.2019
-Initial release.
Example using pre-built UI
This is simple tutorial guide to demonstrate how to implement core tournament loop into your game. It uses pre-built UI screen available in tournament-sdk unity plugin.
Importing demo scene
Import folder TournamentSDK_Demo
from unity plugin package.
This folder contains relevant UI scripts, prefabs and scene that will help you to visualize tournament metadata.
After successful import open Demo
scene located in TournamentSDK_Demo/Scenes/Demo.unity
.
You can run the scene.
Fill in your game client id and initialize tournament-sdk client.
Fill in your nickname and login using anonymous provider (make sure that anonymous login provider is enabled in your dashboard).
You should be able at this point to browse recent tournaments and interact with them.
Manual client initialization
In this example we want to initialize tournament-sdk only after user chose nickname and Photon has successfully connected to servers.
Add the BackboneManager
script into your scene object.
Untick the box which says Initialize on start.
Also add ResourceCache
script into same scene object.
Create new script called BackboneIntegration
.
Open this script and implement client initialization flow.
using Gimmebreak.Backbone.User;
using System.Collections;
using UnityEngine;
public class BackboneIntegration : MonoBehaviour {
private WaitForSeconds waitOneSecond = new WaitForSeconds(1);
private IEnumerator Start()
{
// wait until player nick was set (this happens on initial screen)
while (string.IsNullOrEmpty(PhotonNetwork.player.NickName))
{
yield return this.waitOneSecond;
}
// keep trying to initialize client
while (!BackboneManager.IsInitialized)
{
yield return BackboneManager.Initialize();
yield return this.waitOneSecond;
}
// create arbitrary user id (minimum 64 chars) based on nickname
string arbitraryId = "1000000000000000000000000000000000000000000000000000000000000001" + PhotonNetwork.player.NickName;
// log out user if ids do not match
if (BackboneManager.IsUserLoggedIn &&
BackboneManager.Client.User.GetLoginId(LoginProvider.Platform.Anonym) != arbitraryId)
{
Debug.LogFormat("Backbone user({0}) logged out.", BackboneManager.Client.User.UserId);
yield return BackboneManager.Client.Logout();
}
// log in user
if (!BackboneManager.IsUserLoggedIn)
{
yield return BackboneManager.Client.Login(LoginProvider.Anonym(true, PhotonNetwork.player.NickName, arbitraryId));
if (BackboneManager.IsUserLoggedIn)
{
Debug.LogFormat("Backbone user({0}) logged in.", BackboneManager.Client.User.UserId);
}
else
{
Debug.LogFormat("Backbone user failed to log in.");
}
}
}
}
Show upcoming tournament
Once user is successfully logged in you can refresh tournament list.
// refresh tournament list
BackboneManager.Client.LoadTournamentList()
// add finish callback
.FinishCallback(() => { /* List is loaded */ })
// run on 'this' MonoBehaviour
.Run(this);
You can use BackboneManager.Client.Tournaments.UpcomingTournament
property to get next tournament for user.
var tournament = BackboneManager.Client.Tournaments.UpcomingTournament;
this.tournamentName.text = tournament.TournamentName;
this.tournamentDate.text = tournament.Time.ToLocalTime().ToString("'<b>'dd. MMM'</b>' HH:mm");
this.tournamentTicket.text = string.Format("{0}/{1} | {2}",
tournament.CurrentInvites,
tournament.MaxInvites,
GetSignupStatus());
Copying tournament hub screen
In this example we want to use some of the default UI screens provided in tournament-sdk.
The most essential screens to implement are TournamentListScreen
and TournamentHubScreen
.
Copy TournamentHubScreen
into your UI hierarchy.
This screen shows all tournament details to user and acts as a tournament play hub.
To initialize TournamentHubScreen
a correct tournament id has to be set before the UI object is enabled.
Create a reference to GUITournamentHubScreen
that can be found on TournamentHubScreen
object and call Initialize(long tournamentId)
.
// This is inside UI container script that controls and shows UI panel (not part of tournament-sdk)
// Reference to imported tournament hub screen script
[SerializeField]
private GUITournamentHubScreen tournamentHubScreen;
//...
// Show tournament hub for specific tournament id
public static void ShowScreen(long tournamentId)
{
// Check if tournament is present
var tournament = BackboneManager.Client.Tournaments.GetTournamentById(tournamentId);
if (tournament != null)
{
initializedTournamentId = tournamentId;
// Initialize tournament hub screen for correct tournament id
Instance.tournamentHubScreen.Initialize(tournamentId);
// Enable UI object
ShowScreen();
}
}
This method can be now called from upcoming tournament widget we created in previous step.
// This is inside UI container script that controls and shows UI panel (not part of tournament-sdk)
// Open tournament hub for upcoming tournament
public void OpenTournamentHub()
{
if (BackboneManager.IsUserLoggedIn)
{
// Check if upcomning tournament is present
var upcomingTournament = BackboneManager.Client.Tournaments.UpcomingTournament;
if (upcomingTournament != null)
{
// Hide main screen
LobbyMain.HideScreen();
// Show tournament hub
// Note: UITournamentHub is not part of tournament-sdk, it is a wrapper
// around TournamentHubScreen
UITournamentHub.ShowScreen(upcomingTournament.Id);
LobbyAudio.Instance.OnClick();
}
}
}
Note: This example will not use TournamentListScreen
as it shows only upcoming tournament for simplicity.
Implementing tournament match handler
Now an interface between tournament hub and games lobby/room creation api has to be made.
Create new script called TournamentMatchHandler
.
Open this script and derive it from TournamentMatchCallbackHandler
.
It provides simple set of methods to communicate your lobby/room state to tournament hub.
public bool IsConnectedToGameServerNetwork()
{
//Check if client is successfully connected to your networking backend.
//Return true if user is connected and ready to join given match.
}
public bool IsUserConnectedToMatch(long userId)
{
//Check if specific user is already connected to lobby/room.
//Return true if user is connected.
}
public void OnJoinTournamentMatch(Tournament tournament, TournamentMatch match, ITournamentMatchController controller)
{
//Callback from tournament hub passing tournament, match and controller object.
//Use match data to join correct lobby/room.
//Use controller to inform tournament hub about changes in your lobby/room.
}
public bool IsUserReadyForMatch(long userId)
{
//Check if specific user is ready (e.g. moved to correct slot)
//Return true if user is ready to start.
}
public bool IsGameSessionInProgress()
{
//Check if game session is already in progress for given tournament match.
//Return true if game session is in progress.
}
public void OnLeaveTournamentMatch()
{
//Callback from tournament hub informing user should leave joined lobby/room.
}
public void StartGameSession(IEnumerable<TournamentMatch.User> checkedInUsers)
{
//Callback from tournament hub requesting game session to start immediately. Also
//passing users that successfully checked in for current match.
//Create tournament game session, and start your game.
//This might be called multiple times until IsGameSessionInProgress returns true.
}
Methods above are executed in following order:
- OnJoinTournamentMatch()
- IsConnectedToGameServerNetwork()
- IsUserConnectedToMatch()
- IsGameSessionInProgress()
- IsUserReadyForMatch()
- StartGameSession()
OnJoinTournamentMatch
Callback from tournament hub passing tournament, match and controller object. Use match data to join correct lobby/room. Use controller to inform tournament hub about changes in your lobby/room.
public override void OnJoinTournamentMatch(Tournament tournament, TournamentMatch match, ITournamentMatchController controller)
{
// User is requesting to join a tournament match, create or join appropriate room.
// You can use match.Secret as room id.
this.tournament = tournament;
this.tournamentMatch = match;
this.tournamentMatchController = controller;
this.sessionStarted = false;
this.creatingSession = false;
// Forward UserId & TeamId to Quantum player
PlayerData.Instance.BackboneUserId = BackboneManager.Client.User.UserId;
PlayerData.Instance.BackboneTeamId = match.GetMatchUserById(BackboneManager.Client.User.UserId).TeamId;
// Join Photon room
StartCoroutine(JoinRoomRoutine());
}
private IEnumerator JoinRoomRoutine()
{
while (this.tournamentMatch != null)
{
// If you require specific region for tournament, you can use
// tournament custom properties providing the info about required region.
// string cloudRegion = this.tournament.CustomProperties.Properties["cloud-region"];
// ...
// PhotonNetwork.ConnectToRegion(region, gameVersion);
// ...
// wait until connected to proper region
// ...
// continue connecting to room
// If tournament match is finished then leave.
if (this.tournamentMatch.Status == TournamentMatchStatus.MatchFinished ||
this.tournamentMatch.Status == TournamentMatchStatus.Closed)
{
// Check if connected room is for finished match
if (PhotonNetwork.inRoom &&
PhotonNetwork.room.Name == this.tournamentMatch.Secret)
{
PhotonNetwork.LeaveRoom(false);
}
}
// Try to connect to tournament match room
else if (PhotonNetwork.connectedAndReady &&
!PhotonNetwork.inRoom &&
!this.connectingToRoom)
{
this.connectingToRoom = true;
// Set player propery with UserId so we can identify users in room
SetPlayerProperty("BBUID", BackboneManager.Client.User.UserId);
RoomOptions roomOptions = new RoomOptions();
roomOptions.IsVisible = false;
// Set max players for room based on tournament phase setting
roomOptions.MaxPlayers = (byte)(this.tournament.GetTournamentPhaseById(this.tournamentMatch.PhaseId).MaxTeamsPerMatch * this.tournament.PartySize);
// Join or create Photon room with tournamemnt match secret as room id
PhotonNetwork.JoinOrCreateRoom(this.tournamentMatch.Secret, roomOptions, TypedLobby.Default);
}
// If we are in wrong room then leave
else if (PhotonNetwork.inRoom &&
PhotonNetwork.room.Name != this.tournamentMatch.Secret)
{
PhotonNetwork.LeaveRoom(false);
}
yield return this.waitOneSec;
}
}
This method should initialize your TournamentMatchHandler
and start connection to appropriate lobby/room.
This is only called once after user makes confirmation that he is ready for next match.
IsConnectedToGameServerNetwork
Callback from tournament hub to check if client is successfully connected to your networking backend. Return true if user is connected and ready to join given match.
public override bool IsConnectedToGameServerNetwork()
{
// Check if user is connected to photon and ready to join a room.
return PhotonNetwork.connectedAndReady;
}
IsUserConnectedToMatch
Callback from tournament hub to check if specific user is already connected to lobby/room. Return true if user is connected. Notice that player property was set with UserId before joining photon room. This player property is used for identification of connected photon players.
public override bool IsUserConnectedToMatch(long userId)
{
// Check if tournament match user is connected to room.
// Before user joined room, photon player property BBUID was set with users id.
var photonPlayer = GetPhotonPlayerByBackboneUserId(userId);
return photonPlayer != null;
}
private PhotonPlayer GetPhotonPlayerByBackboneUserId(long userId)
{
if (PhotonNetwork.inRoom)
{
for (int i = 0; i < PhotonNetwork.playerList.Length; i++)
{
long playerUserId;
if (TryGetPlayerProperty(PhotonNetwork.playerList[i], "BBUID", out playerUserId) &&
userId == playerUserId)
{
return PhotonNetwork.playerList[i];
}
}
}
return null;
}
This method is called for every user that is expected to be in connected tournament match.
IsGameSessionInProgress
Callback from tournament hub to check if game session is already in progress for given tournament match. Return true if game session is in progress.
public override bool IsGameSessionInProgress()
{
// Determine if tournament match session has started.
return sessionStarted;
}
In this example we set sessionStarted
to true after successful call to BackboneManager.Client.CreateGameSession()
.
IsUserReadyForMatch
Callback from tournament hub to check if specific user is ready (E.g. moved to correct slot). Return true if user is ready to start. Local user that is not checked in for the match yet, will be checked in only after returning true.
public override bool IsUserReadyForMatch(long userId)
{
// Return true when user loaded/set everything neccsary for
// match to start (if user input is required this should be time limited).
return true;
}
In this example we don't need anything to be set for user once it's connected to the room, so we return true by default.
StartGameSession
Callback from tournament hub requesting game session to start immediately.
Also passing users that successfully checked in for current match.
Create tournament game session, and start your game.
This might be called multiple times until IsGameSessionInProgress
returns true.
public override void StartGameSession(IEnumerable<TournamentMatch.User> checkedInUsers)
{
// Start tournament game session with users that checked in.
// Be aware that this callback can be called multiple times until
// IsGameSessionInProgress returns true.
// Check if session has started
if (sessionStarted)
{
return;
}
// Check if Photon is still connected to room and ready
if (!PhotonNetwork.connectedAndReady ||
!PhotonNetwork.inRoom)
{
return;
}
// Check if session is not being requested
if (!this.creatingSession)
{
this.creatingSession = true;
// Create tournament game session
BackboneManager.Client.CreateGameSession(
checkedInUsers,
this.tournamentMatch.Id,
0)
.ResultCallback((gameSession) =>
{
this.creatingSession = false;
// Check if game session was created
if (gameSession != null)
{
// Indicate that session has started
this.sessionStarted = true;
// Set room properties
var ht = new ExitGames.Client.Photon.Hashtable();
ht.Add("SESSIONID", gameSession.Id);
ht.Add("TOURNAMENTID", this.tournament.Id);
ht.Add("TOURNAMENTMATCHID", this.tournamentMatch.Id);
PhotonNetwork.room.SetCustomProperties(ht);
// At this point you can also initiate scene loading
// and game session start
}
})
.Run(this);
}
}
NOTE: it is also important to use reporting methods on tournamentMatchController
that is passed in OnJoinTournamentMatch
.
Call these controller methods when specific events occur in connected lobby/room.
Tournament hub is using these to determine when to refresh metadata.
Failing to do so can lead into inconsistencies where one client start match but others do not (e.g. other client thinks user did not check in yet).
Example of using Photon room callbacks to report changes to tournamentMatchController
:
//Photon callback when new player joined room
public void OnPlayerEnteredRoom(Player newPlayer)
{
long userId;
//extract user id from player custom properties
if (this.tournamentMatchController != null &&
TryGetPlayerBackboneUserId(newPlayer, out userId))
{
//report user who joined room
this.tournamentMatchController.ReportJoinedUser(userId);
}
}
//Photon callback when player disconnected from room
public void OnPlayerLeftRoom(Player otherPlayer)
{
long userId;
//extract user id from player custom properties
if (this.tournamentMatchController != null &&
TryGetPlayerBackboneUserId(otherPlayer, out userId))
{
//report user who disconnected from room
this.tournamentMatchController.ReportDisconnectedUser(userId);
}
}
//Photon callback when room properties are updated
public void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
{
if (this.tournamentMatchController != null)
{
//reporting status change will refresh match metadata
this.tournamentMatchController.ReportStatusChange();
}
}
//Photon callback when player properties are updated
public void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
{
if (this.tournamentMatchController != null)
{
//reporting status change will refresh match metadata
this.tournamentMatchController.ReportStatusChange();
}
}
Attach tournament match handler
Add created TournamentMatchHandler
to the scene object. (E.g. next to TournamentHubScreen
)
Then add object reference of TournamentMatchHandler
to GUITournamentActiveMatch
script, field MatchHandler.
This script can be found on object TournamentHubScreen/Canvas/ActiveMatchContainer
.
Implementing result submission
When a game session is finished final placements has to be set.
Create a new game session object or use the one obtained with BackboneManager.Client.CreateGameSession()
before game session started.
You can also submit a custom stats with game session result.
List<GameSession.User> users = new List<GameSession.User>();
Dictionary<long, int> kills = new Dictionary<long, int>();
Dictionary<long, int> deaths = new Dictionary<long, int>();
// Iterate through game players(robots) and gather placements and stats
for (int i = 0; i < sortedRobots.Count; i++)
{
// Get quantum runtime player
var runtimePlayer = QuantumGame.Instance.Frames.Current.GetPlayerData(((Robot*)sortedRobots[i])->Player);
if (runtimePlayer != null)
{
// Get players BackboneUserId & match TeamId
long userId = (long)runtimePlayer.BackboneUserId;
byte teamId = runtimePlayer.BackboneTeamId;
// Create a new game session user and assign a final placement
// (1-X, one being the best)
users.Add(new Gimmebreak.Backbone.GameSessions.GameSession.User(userId, teamId) { Place = (i + 1) });
// Get players kills stat
kills.Add(userId, ((Robot*)sortedRobots[i])->Score.Kills);
// Get players death stat
deaths.Add(userId, ((Robot*)sortedRobots[i])->Score.Deaths);
}
}
// Create a game session object to be submitted
Gimmebreak.Backbone.GameSessions.GameSession gameSession = new Gimmebreak.Backbone.GameSessions.GameSession(gameSessionId, 0, users, tournamentMatchId);
// Set a play date & session time
gameSession.PlayDate = ServerTime.UtcNow;
gameSession.PlayTime = gameTime;
// Add game session stats
gameSession.Users.ForEach(user =>
{
gameSession.AddStat(1, user.UserId, kills[user.UserId]);
gameSession.AddStat(2, user.UserId, deaths[user.UserId]);
});
Once game session object is populated you can submit it with BackboneManager.Client.SubmitGameSession(gameSession);
private IEnumerator ProcessResult(long tournamentId, GameSession finishedGameSession)
{
var tournament = BackboneManager.Client.Tournaments.GetTournamentById(tournamentId);
var tournamentMatch = tournament.UserActiveMatch;
//report game session
yield return BackboneManager.Client.SubmitGameSession(finishedGameSession);
//refresh tournament data
yield return BackboneManager.Client.LoadTournament(tournamentId);
//check if tournament match was not finished (if not another game should be played)
bool initializeNextGame = tournamentMatch != null &&
tournamentMatch.Status != TournamentMatchStatus.MatchFinished &&
tournamentMatch.Status != TournamentMatchStatus.Closed;
}
You can reload tournament data after result submission to see if UserActiveMatch
is closed or finished.
If user active match is not finished it means another game session should follow. (E.g. best of three match)
Create a new game session with BackboneManager.Client.CreateGameSession()
and repeat the cycle.
Finalizing tournament core loop
When UserActiveMatch
is finished or closed after last result submission it means user should return back to TournamentHubScreen
.
There he can see current stats and progress chage after finished match.
User will not be immediately moved to another match without explicit confirmation action "ready for next match".
When user confirms he is ready for next match system assigns another UserActiveMatch
and cycle repeats until tournament is finished.
QuantumBlueless
Step by step instruction of Tournament-SDK implementation
Set up the project
Set up Quantum
To set up the Quantum App-ID follow the instructions on following link: https://doc.photonengine.com/quantum/current/quantum-100/quantum-101#step_3_create_and_link_a_quantum_appid
May be that instead of App Settings > App Id Realtime
you will have to go App Settings > App Id
.
Set up Tournament-SDK
Import Tournament-SDK.F
To set up the Tournament-SDK Client Game ID follow the steps of Create new game and setup gameId
in instruction on following link: https://www.tournament-sdk.com/docs#Create+new+game+and+setup+gameId
Tournament-SDK UI implementation
To set up UI to direct player among MainMenu
, TournamentListScreen
and TournamentHubScreen
, first we are going to add necessary changes/additions to the game code.
Prepare Scripts to implement Tournament-SDK UI
UITournamentList
-
Create new script called
UITournamentList
. -
Open new script and copy paste following code that is necessary to direct player from
TournamentListScreen
toMainMenu
orTournamentHubScreen
:using Quantum.Demo; public class UITournamentList : UIScreen<UITournamentList> { public void OnGetBackToMainMenu() { HideScreen(); // Hide Tournament List Screen UIConnect.ShowScreen(); // Show Main Menu screen } public void OnPlayOpenTournamentHub() { HideScreen(); // Hide Tournament List Screen UITournamentHub.ShowScreen(); // Show Tournament Hub Screen } }
UITournamentHub
-
Create new script called
UITournamentHub
. -
Open new script and copy paste following code that is necessary to direct player from
TournamentHubScreen
toTournamentListScreen
:using Quantum.Demo; public class UITournamentHub : UIScreen<UITournamentHub> { public void OnGetBackToTournamentList() { HideScreen(); // Hide Tournament Hub Screen UITournamentList.ShowScreen(); // Show Tournament List Screen } }
Prepare code to implement Tournament-SDK UI
-
Open script called
UIConnect
. -
Add variable to store
GUITournamentHubScreen
crucial for tournament initialization:[SerializeField] private GUITournamentHubScreen TournamentHubScreen;
-
Add two following methods:
// Directs player from MainMenu to TournamentListScreen public void OnTournamentClicked() { HideScreen(); // Hide MainMenu UITournamentList.ShowScreen(); // Show Tournament List Screen } // Method that will redirect player back to the TournamentHubScreen after the tournament match has been finished public void ReturnToTournament(long tournamentId) { HideScreen(); // Hide MainMenu TournamentHubScreen.Initialize(tournamentId); // Initialize tournament using tournamentId saved from last played tournament UITournamentHub.ShowScreen(); // Show Tournament Hub Screen }
Add UI
Tournament Button
-
In
Hierarchy
go toUICanvas > Menu > LayoutHorzontal > VerticalLayout > Content > ConnectPanel
. -
Duplicate last element called
ReconnectButton
. -
Rename it to
TournamentButton
. -
Go
TournamentButton > ButtonText
and changeText
component toTOURNAMENTS
.
Tournament List & Tournament Hub
-
Add two new objects to
UICanvas
scene. -
Rename them to
TournamentListUI
andTournamentHubUI
. -
Drag&Drop
TournamentListScreen
andTournamentHubScreen
respectively. -
Make sure
TournamentListUI > TournamentListScreen > SubScreen
is Enabled. -
Find
Rect transform
component and resetLeft
/Right
/Bottom
/Top
to 0. -
Set scale for
X
/Y
/Z
to 1.
Set Up UI
-
Add newly created scripts
UITournamentList
andUITournamentHub
toMenu
object inHierarchy
to make them accessible. -
Drag&Drop
TournamentListUI
intoUITournamentList > Panel
component. -
Drag&Drop
TournamentHubUI
intoUITournamentHub > Panel
component.
TournamentButton
To make TournamentButton
direct player from MainMenu
to TournamentListScreen
:
-
Go to
UICanvas > Menu > LayoutHorzontal > VerticalLayout > Content > ConnectPanel > TournamentButton
. -
In
Button
component change firstOnClick
action fromUIConnect.OnReconnectClicked
toUIConnect.OnTournamentClicked
.
TournamentList to TournamentHub
To allow TournamentList
direct player to TournamentHub
:
-
Drag&Drop
TournamentHubUI > TournamentHubScreen > SubScreen
toTournamentListUI > TournamentListScreen > SubScreen
inGUITournamentListScreen > TournamentHubScreen
component. -
Go to
UICanvas > Menu > TournamentListUI > TournamentListScreen > SubScreen > Canvas > Scroll View > Viewport > Content > TournamentListItem > ButtonUnderline(Open)
findButton
component. -
Add new
OnClick()
action, drag&dropMenu
into empty field underRuntime Only
, changeNo Function
toUITournamentList.OnPlayOpenTournamentHub
-
Repeat this process for
TournamentHubUI
as well.
TournamentList to MainMenu
To allow TournamentList
direct player back to MainMenu
:
-
Go to
UICanvas > Menu > TournamentListUI > TournamentListScreen > SubScreen > Canvas > Title > ButtonUnderline(Back)
, findButton
component. -
Drag&drop
Menu
into field underRuntime Only
and set it toUITournamentList.OnGetBackToMainMenu
.
TournamentHub to TournamentList
-
Drag&Drop
TournamentListUI > TournamentListScreen > SubScreen
toTournamentHubUI > TournamentHubScreen > SubScreen
inGUISubScreen > ReturnScreen
component. -
Go to
UICanvas > Menu > TournamentHubUI > TournamentHubScreen > SubScreen > Canvas > Title > ButtonUnderline(Back)
, findButton
component. -
Add new action, drag&drop
Menu
into empty field underRuntime Only
and set it toUITournamentHub.OnGetBackToTournamentList
.
Backbone Integration
To initialize TournamentList
we need to create an object that will do it on every launch of the game.
-
Add new
GameObject
toUICanvas
and rename it toBackboneIntegration
. -
Add
BackboneManager
andResource Cache
components to it. -
Create new script called
BacboneIntegration
and copy paste following code into it.using Gimmebreak.Backbone.User; using Quantum.Demo; using System.Collections; using UnityEngine; using UnityEngine.UI; public class BackboneIntegration : MonoBehaviour { private WaitForSeconds waitOneSecond = new WaitForSeconds(1); [SerializeField] private Button tournamentButton = default; private IEnumerator Start() { // First way as in previous game would be to use UIConnect.Instance.Username.text // Second option is to check if PlayerPref contains saved name and use it, because what if player didn't input any text as name var localName = string.IsNullOrEmpty(PlayerPrefs.GetString("Quantum.Demo.UIConnect.LastUsername")) ? UIConnect.Instance.Username.text : PlayerPrefs.GetString("Quantum.Demo.UIConnect.LastUsername"); // Disable tournament button until user is logged in tournamentButton.interactable = false; // wait until player nick was set (this happens on initial screen) while (string.IsNullOrEmpty(localName)) { yield return this.waitOneSecond; } // keep trying to initialize client while (!BackboneManager.IsInitialized) { yield return BackboneManager.Initialize(); yield return this.waitOneSecond; } // create arbitrary user id (minimum 64 chars) based on nickname // ClientInfo.Username is the nickname you set on the first launch of the game string arbitraryId = "1000000000000000000000000000000000000000000000000000000000000001" + localName; // log out user if ids do not match if (BackboneManager.IsUserLoggedIn && BackboneManager.Client.User.GetLoginId(LoginProvider.Platform.Anonym) != arbitraryId) { Debug.LogFormat("Backbone user({0}) logged out.", BackboneManager.Client.User.UserId); yield return BackboneManager.Client.Logout(); } // log in user if (!BackboneManager.IsUserLoggedIn) { yield return BackboneManager.Client.Login(LoginProvider.Anonym(true, localName, arbitraryId)); if (BackboneManager.IsUserLoggedIn) { Debug.LogFormat("Backbone user({0}) logged in.", BackboneManager.Client.User.UserId); } else { Debug.LogFormat("Backbone user failed to log in."); } } if (BackboneManager.IsUserLoggedIn) { // Enable tournament button, because if user is logged in, // then all the information needed is already available tournamentButton.interactable = true; } } }
-
Get back to the scene and find
BackboneIntegration
component. AddTournamentButton
to the missing field in component.
Implementing Tournament logic into the Quantum game
Quantum code changes/additions
To succesfully merge Tournament logic into Quantum game, let's first prepare game to handle/carry/mantain tournament information inside the game.
-
Open Quantum part of the project. Go to project location, there must be folder called
quantum_code
open it as a project in an editor. -
Open
RuntimePlayer.User
and copy paste following code into it:using Photon.Deterministic; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Quantum { partial class RuntimePlayer { public AssetRefEntityPrototype PrototypeRef; public string PlayerName; public string BodyId; public long TournamentUserId; public byte TournamentTeamId; partial void SerializeUserData(BitStream stream) { stream.Serialize(ref PrototypeRef.Id.Value); stream.Serialize(ref PlayerName); stream.Serialize(ref BodyId); stream.Serialize(ref TournamentUserId); stream.Serialize(ref TournamentTeamId); } } }
-
Now to apply added changes save them and re-build the quantum project.
Game code changes/additions
UIConnecting
is script that responsible to set up room properties and start lobby, that's why it's important to set custom info about tournament room here.
Find method OnConnectedToMaster()
and change as shown below:
public void OnConnectedToMaster()
{
if
else
if
else
if
// Above is unchanged code
var joinRandomParams = new OpJoinRandomRoomParams();
_enterRoomParams = new EnterRoomParams();
_enterRoomParams.RoomOptions = new RoomOptions();
_enterRoomParams.RoomOptions.IsVisible = true;
_enterRoomParams.RoomOptions.MaxPlayers = TournamentMatchHandler.MaxUserTournament != 0 ? TournamentMatchHandler.MaxUserTournament : Input.MAX_COUNT;
_enterRoomParams.RoomOptions.Plugins = new string[] { "QuantumPlugin" };
_enterRoomParams.RoomOptions.CustomRoomProperties = new Hashtable {
{ "HIDE-ROOM", false },
{ "MAP-GUID", defaultMapGuid },
};
_enterRoomParams.RoomOptions.PlayerTtl = PhotonServerSettings.Instance.PlayerTtlInSeconds * 1000;
_enterRoomParams.RoomOptions.EmptyRoomTtl = PhotonServerSettings.Instance.EmptyRoomTtlInSeconds * 1000;
_enterRoomParams.RoomName = string.IsNullOrEmpty(TournamentMatchHandler.RoomName) ? null : TournamentMatchHandler.RoomName;
// Below is unchanged code
if
}
Result processing
To start result processing we have to find place where game end is being tracked.
-
Go to
ScoreboardHud
script. -
Add variable to track if result processing has started.
private bool processingStarted = false;
-
Find method
OnGameEnded()
and change as follows:private unsafe void OnGameEnded(EventOnGameEnded onGameEnded) { _alwaysShow = true; if (TournamentMatchHandler.TournamentGame && !this.processingStarted) StartCoroutine(ProcessResult(QuantumRunner.Default.Game.Frames.Verified)); }
-
Now copy paste method
ProcessResult()
:private IEnumerator ProcessResult(Frame f) { this.processingStarted = true; List<GameSession.User> users = new List<GameSession.User>(); Dictionary<long, int> kills = new Dictionary<long, int>(); Dictionary<long, int> deaths = new Dictionary<long, int>(); // Iterate through game players and gather placements and stats for (int i = 0; i < _sortedRobots.Count; i++) { // Using EntityRef get PlayerID which contains PlayerRef to obtain RuntimePlayer RuntimePlayer rp = f.GetPlayerData(f.Get<PlayerID>(_sortedRobots[i]).PlayerRef); // Using EntityRef of particular player get Score construct that contains Kills and Deaths Score score = f.Get<Score>(_sortedRobots[i]); long userId = rp.TournamentUserId; byte teamId = rp.TournamentTeamId; Debug.LogWarning($"User with ID -> {userId} # Place -> {i+1} # Kills -> {score.Kills} # Deaths -> {score.Deaths}"); users.Add(new Gimmebreak.Backbone.GameSessions.GameSession.User(userId, teamId) { Place = (i + 1) }); // Get players kills stat kills.Add(userId, score.Kills); // Get players death stat deaths.Add(userId, score.Deaths); } // Create a game session object to be submitted GameSession gameSession = new Gimmebreak.Backbone.GameSessions.GameSession( (long)UIMain.Client.CurrentRoom.CustomProperties["GameSessionId"], 0, users, (long)UIMain.Client.CurrentRoom.CustomProperties["TournamentMatchId"]); // Add game session stats gameSession.Users.ForEach(user => { gameSession.AddStat(1, user.UserId, kills[user.UserId]); gameSession.AddStat(2, user.UserId, deaths[user.UserId]); }); //report game session yield return BackboneManager.Client.SubmitGameSession(gameSession); //refresh tournament data yield return BackboneManager.Client.LoadTournament((long)UIMain.Client.CurrentRoom.CustomProperties["TournamentId"]); //check if tournament match was not finished (if not another game should be played) TournamentMatchHandler.TournamentGame = false; UIConnect.Instance.ReturnToTournament((long)UIMain.Client.CurrentRoom.CustomProperties["TournamentId"]); UIMain.Client.Disconnect(); }
Implementing Tournament Match Handler
-
In
Hierarchy
go toUICanvas > TournamentHubUI > TournamentHubScreen > Canvas
at the very bottom of it, create a new game object and rename it toTournamentMatchHandler
. -
Create new script called
TournamentMatchHandler
and add this script as component toTournamentMatchHandler
object. -
Copy paste following code into it:
using ExitGames.Client.Photon; using Gimmebreak.Backbone.Core; using Gimmebreak.Backbone.Tournaments; using Photon.Realtime; using Quantum; using Quantum.Demo; using System.Collections; using System.Collections.Generic; using System.Data; using System.Linq; using UnityEngine; public class TournamentMatchHandler : TournamentMatchCallbackHandler, IInRoomCallbacks { Tournament tournament; TournamentMatch tournamentMatch; ITournamentMatchController tournamentMatchController; bool sessionStarted; bool creatingSession; private WaitForSeconds waitOneSec = new WaitForSeconds(1); private string tournamentSessionName; public static long TournamentUserId; public static byte TournamentTeamId; public static byte MaxUserTournament; public static string RoomName; public static bool TournamentGame = false; public override void OnJoinTournamentMatch(Tournament tournament, TournamentMatch match, ITournamentMatchController controller) { // User is requesting to join a tournament match, create or join appropriate session this.tournament = tournament; this.tournamentMatch = match; this.tournamentMatchController = controller; this.sessionStarted = false; this.creatingSession = false; this.tournamentSessionName = $"{this.tournamentMatch.Secret}_{this.tournamentMatch.CurrentGameCount}"; // Join Photon session StartCoroutine(JoinRoomRoutine()); } public void OnDisable() { if (UIMain.Client != null) { UIMain.Client.RemoveCallbackTarget(this); } } private IEnumerator JoinRoomRoutine() { while (this.tournamentMatch != null) { // If you require specific region for tournament, you can use // tournament custom properties providing the info about required region. // string cloudRegion = this.tournament.CustomProperties.Properties["cloud-region"]; // If tournament match is finished then leave if (this.tournamentMatch.Status == TournamentMatchStatus.MatchFinished || this.tournamentMatch.Status == TournamentMatchStatus.Closed) { // Check if connected session is for finished match if (UIMain.Client != null && UIMain.Client.State == ClientState.Joined && UIMain.Client.CurrentRoom.Name == this.tournamentSessionName) { UIMain.Client.Disconnect(); } } // Try to connect to tournament match session else if (!(UIMain.Client != null && UIMain.Client.State == ClientState.Joined)) { // Set max players for session based on tournament phase setting MaxUserTournament = (byte)(this.tournament.GetTournamentPhaseById(this.tournamentMatch.PhaseId).MaxTeamsPerMatch * this.tournament.PartySize); // Join or create Photon session with tournamemnt match secret as session id RoomName = this.tournamentSessionName; TournamentUserId = BackboneManager.Client.User.UserId; TournamentTeamId = this.tournamentMatch.GetMatchUserById(BackboneManager.Client.User.UserId).TeamId; UIConnect.Instance.OnConnectClicked(); // Set player propery with UserId so we can identify users in session ExitGames.Client.Photon.Hashtable customProperties = new ExitGames.Client.Photon.Hashtable() { { "TournamentUserId", BackboneManager.Client.User.UserId}, { "TournamentTeamId", this.tournamentMatch.GetMatchUserById(BackboneManager.Client.User.UserId).TeamId} }; UIMain.Client.AddCallbackTarget(this); TournamentGame = true; UIMain.Client.LocalPlayer.SetCustomProperties(customProperties); } // If we are in wrong session then leave else if (UIMain.Client != null && UIMain.Client.State == ClientState.Joined && this.tournamentSessionName != UIMain.Client.CurrentRoom.Name) { UIMain.Client.Disconnect(); } yield return this.waitOneSec; } } public override bool IsConnectedToGameServerNetwork() { // Check if user is connected to photon and ready to join a session if (UIMain.Client != null && UIMain.Client.State == ClientState.Joined) { return true; } return false; } public override bool IsGameSessionInProgress() { // Check if game session has started return sessionStarted; } public override bool IsUserConnectedToMatch(long userId) { // Check if tournament match user is connected to session if (UIMain.Client.CurrentRoom == null) return false; foreach (var player in UIMain.Client.CurrentRoom.Players.Values) { if (player.CustomProperties.ContainsValue(userId)) { return true; } } return false; } public override bool IsUserReadyForMatch(long userId) { // In particular case if player is connected to the match it's considered to be ready return IsUserConnectedToMatch(userId); } public override void OnLeaveTournamentMatch() { this.tournament = null; this.tournamentMatch = null; this.tournamentMatchController = null; TournamentGame = false; UIMain.Client?.RemoveCallbackTarget(this); UIMain.Client?.Disconnect(); } public override void StartGameSession(IEnumerable<TournamentMatch.User> checkedInUsers) { // Start tournament game session with users that checked in. // Be aware that this callback can be called multiple times until // sessionStarted returns true. // Check if session has started if (sessionStarted) { return; } // Check if session is not being requested if (!this.creatingSession) { this.creatingSession = true; // Create tournament game session BackboneManager.Client.CreateGameSession( checkedInUsers, this.tournamentMatch.Id, 0) .ResultCallback((gameSession) => { this.creatingSession = false; // Check if game session was created if (gameSession != null) { // Indicate that session has started this.sessionStarted = true; if (UIMain.Client.LocalPlayer.IsMasterClient) { ExitGames.Client.Photon.Hashtable hashtable = new ExitGames.Client.Photon.Hashtable(); hashtable.Add("GameSessionId", gameSession.Id); hashtable.Add("TournamentMatchId", this.tournamentMatch.Id); hashtable.Add("TournamentId", this.tournament.Id); UIMain.Client.CurrentRoom.SetCustomProperties(hashtable); UIRoom.Instance.OnStartClicked(); } UITournamentHub.HideScreen(); } }) .Run(this); } } public void OnPlayerEnteredRoom(Player newPlayer) { if (this.tournamentMatchController != null) { // Report user who joined room this.tournamentMatchController.ReportJoinedUser((long)newPlayer.CustomProperties["TournamentUserId"]); } } public void OnPlayerLeftRoom(Player otherPlayer) { { // Report user who disconnected from room this.tournamentMatchController.ReportDisconnectedUser((long)otherPlayer.CustomProperties["TournamentUserId"]); } } public void OnPlayerPropertiesUpdate(Player targetPlayer, ExitGames.Client.Photon.Hashtable changedProps) { if (this.tournamentMatchController != null) { // Reporting status change will refresh match metadata this.tournamentMatchController.ReportStatusChange(); } } public void OnRoomPropertiesUpdate(ExitGames.Client.Photon.Hashtable propertiesThatChanged) { if (this.tournamentMatchController != null) { // Reporting status change will refresh match metadata this.tournamentMatchController.ReportStatusChange(); } } public void OnMasterClientSwitched(Player newMasterClient) { } }
-
Get back to Unity project, find
UICanvas > TournamentHubUI > TournamentHubScreen > SubScreen > Canvas > ActiveMatchContainer
, find componentGUITournamentActiveMatch
there will be empty fieldMatchHandler
drag&dropTournamentMatchHandler
into it.
Tournament creation & Final test
Build project
We need at least 2 players to be sure that project is working fine, so build project to create 2nd player.
Don't start it immediately, wait till we create a tournament, otherwise tournament won't be visible on TournamentListScreen
.
Creating tournament template
Creating the template
- Go to https://www.tournament-sdk.com/tournaments
- There you will see
No tournament templates .Create your first template to get started
. -
Press
Create your first template
.
Edit template
-
Tournament template will appear. Select
Edit template
. -
Go to
Description
and setTournament Name
. -
Go to
Registration
set:
Maximum players
- 2Party(team) size
- 1-
Registration rules
->Open to everyone
- Go to
Format/Add Phase
In Format
set:
Teams
- 2Min teams per match
- 2Max Teams per match
- 2
Leave field Max loses
in Scores
empty
In Rounds
set:
Type
- BO3Minimum game time (minutes)
- 2Maximum round time (minutes)
- 8
-
On the bottom of the screen you should see
Careful - you have unsaved changes!
, pressSave Changes
.
Start tournament
- Get back to
Tournament templates
page. -
Press
Schedule
, setTime
toYour current time + 5 minutes
and pressStart tournament
.
Final test
-
Now get back to Unity and start the project.
-
Press
Tournaments
button that was added earlier. You should seeTournamentListScreen
and the tournament that you just added.Tournament may be unavailable to register for some time, wait until
Sign up
button is available to register to tournament. -
Repeat the process in build version
-
When tournament will start, button
Ready to play
will become available. -
Press on both Unity and Build
Ready to play
. -
Finish the match on both players.
-
Get back to: https://www.tournament-sdk.com/schedule
-
You should see tournament.
-
Click on it, to see more information about the tournament and played matches.
-
To check more detailed information about every match played during the tournament, go to
Phase 1
. -
Press
Show matches
to the right from any participant. -
Click
Show details
for more information. -
Click
Game #ID
andStats
for more information.
- Step by step instruction of Tournament-SDK implementation
- Set up the project
- Tournament-SDK UI implementation
- Prepare Scripts to implement Tournament-SDK UI
- UITournamentList
- UITournamentHub
- Prepare code to implement Tournament-SDK UI
- Add UI
- Tournament Button
- Tournament List & Tournament Hub
- Set Up UI
- TournamentButton
- TournamentList to TournamentHub
- TournamentList to MainMenu
- TournamentHub to TournamentList
- Backbone Integration
- Implementing Tournament logic into the Quantum game
- Tournament creation & Final test
QuantumBomber
Step by step instruction of recreation of the project
Set up the project
Set up Quantum
To set up the Quantum App-ID follow the instructions on following link: https://doc.photonengine.com/quantum/current/quantum-100/quantum-101#step_3_create_and_link_a_quantum_appid
May be that instead of App Settings > App Id Realtime
you will have to go App Settings > App Id
.
Set up Tournament-SDK
Import Tournament-SDK.
To set up the Tournament-SDK Client Game ID follow the steps of Create new game and setup gameId
in instruction on following link: https://www.tournament-sdk.com/docs#Create+new+game+and+setup+gameId
Tournament-SDK UI implementation
To set up UI to direct player among MainMenu
, TournamentListScreen
and TournamentHubScreen
, first we are going to add necessary changes/additions to the game code.
Prepare Scripts to implement Tournament-SDK UI
UITournamentList
-
Create new script called
UITournamentList
. -
Open new script and copy paste following code that is necessary to direct player from
TournamentListScreen
toMainMenu
orTournamentHubScreen
:using Quantum.Demo; public class UITournamentList : UIScreen<UITournamentList> { public void OnGetBackToMainMenu() { HideScreen(); // Hide Tournament List Screen UIConnect.ShowScreen(); // Show Main Menu screen } public void OnPlayOpenTournamentHub() { HideScreen(); // Hide Tournament List Screen UITournamentHub.ShowScreen(); // Show Tournament Hub Screen } }
UITournamentHub
-
Create new script called
UITournamentHub
. -
Open new script and copy paste following code that is necessary to direct player from
TournamentHubScreen
toTournamentListScreen
:using Quantum.Demo; public class UITournamentHub : UIScreen<UITournamentHub> { public void OnGetBackToTournamentList() { HideScreen(); // Hide Tournament Hub Screen UITournamentList.ShowScreen(); // Show Tournament List Screen } }
Prepare code to implement Tournament-SDK UI
-
Open script called
UIConnect
. -
Add variable to store
GUITournamentHubScreen
crucial for tournament initialization:[SerializeField] private GUITournamentHubScreen TournamentHubScreen;
-
Add two following methods:
// Directs player from MainMenu to TournamentListScreen public void OnTournamentClicked() { HideScreen(); // Hide MainMenu UITournamentList.ShowScreen(); // Show Tournament List Screen } // Method that will redirect player back to the TournamentHubScreen after the tournament match has been finished public void ReturnToTournament(long tournamentId) { HideScreen(); // Hide MainMenu TournamentHubScreen.Initialize(tournamentId); // Initialize tournament using tournamentId saved from last played tournament UITournamentHub.ShowScreen(); // Show Tournament Hub Screen }
Add UI
Tournament Button
-
In
Hierarchy
go toUICanvas > Menu > LayoutHorzontal > VerticalLayout > Content > ConnectPanel
. -
Duplicate last element called
ReconnectButton
. -
Rename it to
TournamentButton
. -
Go
TournamentButton > ButtonText
and changeText
component toTOURNAMENTS
.
Tournament List & Tournament Hub
-
Add two new objects to
UICanvas
scene. -
Rename them to
TournamentListUI
andTournamentHubUI
. -
Drag&Drop
TournamentListScreen
andTournamentHubScreen
respectively. -
Make sure
TournamentListUI > TournamentListScreen > SubScreen
is Enabled. -
Find
Rect transform
component and resetLeft
/Right
/Bottom
/Top
to 0. -
Set scale for
X
/Y
/Z
to 1.
Set Up UI
-
Add newly created scripts
UITournamentList
andUITournamentHub
toMenu
object inHierarchy
to make them accessible. -
Drag&Drop
TournamentListUI
intoUITournamentList > Panel
component. -
Drag&Drop
TournamentHubUI
intoUITournamentHub > Panel
component.
TournamentButton
To make TournamentButton
direct player from MainMenu
to TournamentListScreen
:
-
Go to
UICanvas > Menu > LayoutHorzontal > VerticalLayout > Content > ConnectPanel > TournamentButton
. -
In
Button
component change firstOnClick
action fromUIConnect.OnReconnectClicked
toUIConnect.OnTournamentClicked
.
TournamentList to TournamentHub
To allow TournamentList
direct player to TournamentHub
:
-
Drag&Drop
TournamentHubUI > TournamentHubScreen > SubScreen
toTournamentListUI > TournamentListScreen > SubScreen
inGUITournamentListScreen > TournamentHubScreen
component. -
Go to
UICanvas > Menu > TournamentListUI > TournamentListScreen > SubScreen > Canvas > Scroll View > Viewport > Content > TournamentListItem > ButtonUnderline(Open)
findButton
component. -
Add new
OnClick()
action, drag&dropMenu
into empty field underRuntime Only
, changeNo Function
toUITournamentList.OnPlayOpenTournamentHub
-
Repeat this process for
TournamentHubUI
as well.
TournamentList to MainMenu
To allow TournamentList
direct player back to MainMenu
:
-
Go to
UICanvas > Menu > TournamentListUI > TournamentListScreen > SubScreen > Canvas > Title > ButtonUnderline(Back)
, findButton
component. -
Remove existing action and add three new actions.
-
For first action, drag&drop
Menu
into empty field underRuntime Only
and set it toUITournamentList.OnGetBackToMainMenu
. -
For second action, drag&drop
Directional Light
into empty field underRuntime Only
and set it toGameObject.SetActive
and make sure to enable it. -
For third action, drag&drop
UICanvasCustom
into empty field underRuntime Only
and set it toGameObject.SetActive
and make sure to enable it.
TournamentHub to TournamentList
-
Drag&Drop
TournamentListUI > TournamentListScreen > SubScreen
toTournamentHubUI > TournamentHubScreen > SubScreen
inGUISubScreen > ReturnScreen
component. -
Go to
UICanvas > Menu > TournamentHubUI > TournamentHubScreen > SubScreen > Canvas > Title > ButtonUnderline(Back)
, findButton
component. -
Add new action, drag&drop
Menu
into empty field underRuntime Only
and set it toUITournamentHub.OnGetBackToTournamentList
.
Backbone Integration
To initialize TournamentList
we need to create an object that will do it on every launch of the game.
-
Add new
GameObject
toUICanvas
and rename it toBackboneIntegration
. -
Add
BackboneManager
andResource Cache
components to it. -
Create new script called
BacboneIntegration
and copy paste following code into it.using Gimmebreak.Backbone.User; using Quantum.Demo; using System.Collections; using UnityEngine; using UnityEngine.UI; public class BackboneIntegration : MonoBehaviour { private WaitForSeconds waitOneSecond = new WaitForSeconds(1); [SerializeField] private Button tournamentButton = default; private IEnumerator Start() { // Disable tournament button until user is logged in tournamentButton.interactable = false; // wait until player nick was set (this happens on initial screen) while (string.IsNullOrEmpty(UIConnect.Instance.Username.text)) { yield return this.waitOneSecond; } // keep trying to initialize client while (!BackboneManager.IsInitialized) { yield return BackboneManager.Initialize(); yield return this.waitOneSecond; } // create arbitrary user id (minimum 64 chars) based on nickname // ClientInfo.Username is the nickname you set on the first launch of the game string arbitraryId = "1000000000000000000000000000000000000000000000000000000000000001" + UIConnect.Instance.Username.text; // log out user if ids do not match if (BackboneManager.IsUserLoggedIn && BackboneManager.Client.User.GetLoginId(LoginProvider.Platform.Anonym) != arbitraryId) { Debug.LogFormat("Backbone user({0}) logged out.", BackboneManager.Client.User.UserId); yield return BackboneManager.Client.Logout(); } // log in user if (!BackboneManager.IsUserLoggedIn) { yield return BackboneManager.Client.Login(LoginProvider.Anonym(true, UIConnect.Instance.Username.text, arbitraryId)); if (BackboneManager.IsUserLoggedIn) { Debug.LogFormat("Backbone user({0}) logged in.", BackboneManager.Client.User.UserId); } else { Debug.LogFormat("Backbone user failed to log in."); } } if (BackboneManager.IsUserLoggedIn) { // Enable tournament button, because if user is logged in, // then all the information needed is already available tournamentButton.interactable = true; } } }
-
Get back to the scene and find
BackboneIntegration
component. AddTournamentButton
to the missing field in component.
Implementing Tournament logic into the Quantum game
Quantum code changes/additions
To succesfully merge Tournament logic into Quantum game, let's first prepare game to handle/carry/mantain tournament information inside the game.
-
Open Quantum part of the project. Go to project location, there must be folder called
quantum_code
open it as a project in an editor. -
Open
RuntimePlayer.User
and copy paste following code into it:using Photon.Deterministic; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Quantum { partial class RuntimePlayer { public ColorRGBA Color; // Variables to store user's identificators for tournament public long TournamentUserId; public byte TournamentTeamId; partial void SerializeUserData(BitStream stream) { // implementation stream.Serialize(ref Color.R); stream.Serialize(ref Color.G); stream.Serialize(ref Color.B); stream.Serialize(ref Color.A); stream.Serialize(ref TournamentUserId); stream.Serialize(ref TournamentTeamId); } } }
-
Go to
gameSession.qtn
and add following variable to component to store list of players who have been destroyed by bomb explosion during the match. -
Set list as
[AllocateOnComponentAdded]
which will allocate it on every simulation.[AllocateOnComponentAdded] list<PlayerRef> Placements;
-
Find
BomberSystem
and copy paste following code into it:using System.Collections.Generic; namespace Quantum { public unsafe class BomberSystem : SystemMainThreadFilter<BomberSystem.BomberFilter> { public struct BomberFilter { public EntityRef Entity; public Bomber* Bomber; public Transform2D* Transform; } public override void Update(Frame f, ref BomberFilter filter) { var gridPosition = filter.Transform->Position.RoundToInt(Axis.Both); var isInvincible = false; #if DEBUG // Used for debugging purposes isInvincible = f.RuntimeConfig.IsInvincible; #endif if (isInvincible == false && f.Grid.GetCellPtr(gridPosition)->IsBurning) { // Death animation is triggered from OnEntityDestroyed // Add to list before destroy in order to get access to players info after match has finished to process tournament results foreach (var (entity, component) in f.GetComponentIterator<GameSession>()) { f.ResolveList(component.Placements).Add(f.Get<PlayerLink>(filter.Entity).Id); } f.Destroy(filter.Entity); } } } }
-
Now to apply added changes re-build the quantum project.
Game code changes/additions
UIConnecting
contains method OnConnectedToMaster()
that starts the game, in order to start a tournament game all players must have same room paramenters.
-
Find
OnConnectedToMaster()
method and change it as shown below, so on every launch it will check if Room name has been set and connect to specific Room.public void OnConnectedToMaster() { if else ... if else if ... // Above we have unchanged part of the code var joinRandomParams = new OpJoinRandomRoomParams(); _enterRoomParams = new EnterRoomParams(); _enterRoomParams.RoomOptions = new RoomOptions(); _enterRoomParams.RoomOptions.IsVisible = true; _enterRoomParams.RoomOptions.MaxPlayers = (byte)(TournamentMatchHandler.MaxUserTournament != 0 ? TournamentMatchHandler.MaxUserTournament : Input.MAX_COUNT); _enterRoomParams.RoomOptions.Plugins = new string[] { "QuantumPlugin" }; _enterRoomParams.RoomOptions.CustomRoomProperties = new Hashtable { { "HIDE-ROOM", false }, { "MAP-GUID", defaultMapGuid }, }; _enterRoomParams.RoomOptions.PlayerTtl = PhotonServerSettings.Instance.PlayerTtlInSeconds * 1000; _enterRoomParams.RoomOptions.EmptyRoomTtl = PhotonServerSettings.Instance.EmptyRoomTtlInSeconds * 1000; Debug.Log("Starting random matchmaking"); _enterRoomParams.RoomName = string.IsNullOrEmpty(TournamentMatchHandler.RoomName) ? null : TournamentMatchHandler.RoomName; // Below we have unchanged part of the code if }
UIGame
is responsible to track game state. -
Add
Disconnect
button to it, to disable it, so players couldn't spoil the process of the tournament game:// Variable that represents Disconnect button [SerializeField] private UnityEngine.UI.Button OnLeaveButton;
-
Change
Update()
method to:public void Update() { if (QuantumRunner.Default != null && QuantumRunner.Default.HasGameStartTimedOut) { UIDialog.Show("Error", "Game start timed out", () => { UIMain.Client.Disconnect(); }); } // Disable Disconnect button if tournament game if (TournamentMatchHandler.TournamentGame) OnLeaveButton.interactable = false; }
UIGameStats
is responsible for the end of the game -
Add variable to track if result procession started:
private bool resultProcessingStarted = false;
-
Then in
UpdateUI
method changeEnding
case to start result processing, right before finishing the game:case GameSessionState.Ending: if (TournamentMatchHandler.TournamentGame && !this.resultProcessingStarted) { StartCoroutine(ProcessResult(frame, gameSession.Winner)); } else if (!TournamentMatchHandler.TournamentGame) { _gameStateMessageTMP.text = gameSession.Winner.IsValid ? $"Player {gameSession.Winner._index} won!" : "DRAW!"; var timeUntilDisconnection = timer.GetRemainingTime(frame).AsFloat; // If more than 60 seconds are left until disconnection, write out counter in min + sec _timerTMP.text = timeUntilDisconnection > 60 ? $"Disconnection in {(int)timeUntilDisconnection / 60} min {(int)timeUntilDisconnection % 60} seconds" : $"Disconnection in {(int)timeUntilDisconnection} seconds"; _gameStateTMP.text = "Game Over"; if (timer.HasExpired(frame)) UIMain.Client.Disconnect(); } break;
-
Add new method called
ProcessResult
:private IEnumerator ProcessResult(Frame frame, PlayerRef winner) { this.resultProcessingStarted = true; List<Gimmebreak.Backbone.GameSessions.GameSession.User> users = new List<Gimmebreak.Backbone.GameSessions.GameSession.User>(); Dictionary<long, byte> results = new Dictionary<long, byte>(); Gimmebreak.Backbone.GameSessions.GameSession gameSession; List<PlayerRef> prs = new List<PlayerRef>(); foreach (var (entity1, component) in frame.GetComponentIterator<GameSession>()) { foreach (PlayerRef pr in frame.ResolveList(component.Placements)) { prs.Add(pr); } } prs.Add(winner); foreach (PlayerRef pr in prs) { RuntimePlayer runtimePlayer = frame.GetPlayerData(pr); users.Add(new Gimmebreak.Backbone.GameSessions.GameSession.User( runtimePlayer.TournamentUserId, runtimePlayer.TournamentTeamId) { Place = runtimePlayer.TournamentUserId == frame.GetPlayerData(winner).TournamentUserId ? 1 : 2 }); if (runtimePlayer.TournamentUserId == frame.GetPlayerData(winner).TournamentUserId) results.Add(runtimePlayer.TournamentUserId, 1); else results.Add(runtimePlayer.TournamentUserId, 0); } // Create new GameSession gameSession = new Gimmebreak.Backbone.GameSessions.GameSession( (long)UIMain.Client.CurrentRoom.CustomProperties["GameSessionId"], 0, users, (long)UIMain.Client.CurrentRoom.CustomProperties["TournamentMatchId"]); // Attach users and their results to current GameSession gameSession.Users.ForEach(user => { gameSession.AddStat(1, user.UserId, results[user.UserId]); }); //report game session yield return BackboneManager.Client.SubmitGameSession(gameSession); //refresh tournament data yield return BackboneManager.Client.LoadTournament((long)UIMain.Client.CurrentRoom.CustomProperties["TournamentId"]); // Leave session after result was submited TournamentMatchHandler.TournamentGame = false; UIConnect.Instance.ReturnToTournament((long)UIMain.Client.CurrentRoom.CustomProperties["TournamentId"]); UIMain.Client.Disconnect(); }
As we want to disable button in
UIGame
, we need to attach button to it. -
Go to
UICanvas > Game
there is empty fieldOnLeaveButton
, drag&dropUICanvas > Game > Panel > DisconnectButton
into empty field.
Implementing Tournament Match Handler
-
In
Hierarchy
go toUICanvas > TournamentHubUI > TournamentHubScreen > Canvas
at the very bottom of it, create a new game object and rename it toTournamentMatchHandler
. -
Create new script called
TournamentMatchHandler
and add this script as component toTournamentMatchHandler
object. -
Copy paste following code into it:
using ExitGames.Client.Photon; using Gimmebreak.Backbone.Core; using Gimmebreak.Backbone.Tournaments; using Photon.Realtime; using Quantum; using Quantum.Demo; using System.Collections; using System.Collections.Generic; using System.Data; using System.Linq; using UnityEngine; public class TournamentMatchHandler : TournamentMatchCallbackHandler, IInRoomCallbacks { Tournament tournament; TournamentMatch tournamentMatch; ITournamentMatchController tournamentMatchController; bool sessionStarted; bool creatingSession; private WaitForSeconds waitOneSec = new WaitForSeconds(1); private string tournamentSessionName; public static byte MaxUserTournament = 0; public static string RoomName = ""; public static bool TournamentGame = false; public override void OnJoinTournamentMatch(Tournament tournament, TournamentMatch match, ITournamentMatchController controller) { // User is requesting to join a tournament match, create or join appropriate session this.tournament = tournament; this.tournamentMatch = match; this.tournamentMatchController = controller; this.sessionStarted = false; this.creatingSession = false; this.tournamentSessionName = $"{this.tournamentMatch.Secret}_{this.tournamentMatch.CurrentGameCount}"; // Join Photon session StartCoroutine(JoinRoomRoutine()); } public void OnDisable() { if (UIMain.Client != null) { UIMain.Client.RemoveCallbackTarget(this); } } private IEnumerator JoinRoomRoutine() { while (this.tournamentMatch != null) { // Use ConnectedStatus instead of UIMain.Client.State, because Client is only created after UIConnect.Instance.OnConnectClicked(); // If you require specific region for tournament, you can use // tournament custom properties providing the info about required region. // string cloudRegion = this.tournament.CustomProperties.Properties["cloud-region"]; // If tournament match is finished then leave if (this.tournamentMatch.Status == TournamentMatchStatus.MatchFinished || this.tournamentMatch.Status == TournamentMatchStatus.Closed) { // Check if connected session is for finished match if (UIMain.Client != null && UIMain.Client.State == ClientState.Joined && UIMain.Client.CurrentRoom.Name == this.tournamentSessionName) { UIMain.Client.Disconnect(); } } // Try to connect to tournament match session else if (!(UIMain.Client != null && UIMain.Client.State == ClientState.Joined)) { // Set player propery with UserId so we can identify users in session // Set max players for session based on tournament phase setting MaxUserTournament = (byte)(this.tournament.GetTournamentPhaseById(this.tournamentMatch.PhaseId).MaxTeamsPerMatch * this.tournament.PartySize); // Join or create Photon session with tournamemnt match secret as session id RoomName = this.tournamentSessionName; PlayerDataContainer.Instance.RuntimePlayer.TournamentUserId = BackboneManager.Client.User.UserId; PlayerDataContainer.Instance.RuntimePlayer.TournamentTeamId = this.tournamentMatch.GetMatchUserById(BackboneManager.Client.User.UserId).TeamId; UIConnect.Instance.OnConnectClicked(); ExitGames.Client.Photon.Hashtable customProperties = new ExitGames.Client.Photon.Hashtable() { { "TournamentUserId", BackboneManager.Client.User.UserId}, { "TournamentTeamId", this.tournamentMatch.GetMatchUserById(BackboneManager.Client.User.UserId).TeamId} }; UIMain.Client.AddCallbackTarget(this); TournamentGame = true; UIMain.Client.LocalPlayer.SetCustomProperties(customProperties); } // If we are in wrong session then leave else if (UIMain.Client != null && UIMain.Client.State == ClientState.Joined && this.tournamentSessionName != UIMain.Client.CurrentRoom.Name) { UIMain.Client.Disconnect(); } yield return this.waitOneSec; } } public override bool IsConnectedToGameServerNetwork() { // Check if user is connected to photon and ready to join a session if (UIMain.Client != null && UIMain.Client.State == ClientState.Joined) { return true; } return false; } public override bool IsGameSessionInProgress() { // Check if game session has started return sessionStarted; } public override bool IsUserConnectedToMatch(long userId) { // Check if tournament match user is connected to session if (UIMain.Client.CurrentRoom == null) return false; foreach (var player in UIMain.Client.CurrentRoom.Players.Values) { if (player.CustomProperties.ContainsValue(userId)) { return true; } } return false; } public override bool IsUserReadyForMatch(long userId) { // In particular case if player is connected to the match it's considered to be ready return IsUserConnectedToMatch(userId); } public override void OnLeaveTournamentMatch() { this.tournament = null; this.tournamentMatch = null; this.tournamentMatchController = null; TournamentGame = false; UIMain.Client?.RemoveCallbackTarget(this); UIMain.Client?.Disconnect(); } public override void StartGameSession(IEnumerable<TournamentMatch.User> checkedInUsers) { // Start tournament game session with users that checked in. // Be aware that this callback can be called multiple times until // sessionStarted returns true. // Check if session has started if (sessionStarted) { return; } // Check if session is not being requested if (!this.creatingSession) { this.creatingSession = true; // Create tournament game session BackboneManager.Client.CreateGameSession( checkedInUsers, this.tournamentMatch.Id, 0) .ResultCallback((gameSession) => { this.creatingSession = false; // Check if game session was created if (gameSession != null) { // Indicate that session has started this.sessionStarted = true; if (UIMain.Client.LocalPlayer.IsMasterClient) { ExitGames.Client.Photon.Hashtable hashtable = new ExitGames.Client.Photon.Hashtable(); hashtable.Add("GameSessionId", gameSession.Id); hashtable.Add("TournamentMatchId", this.tournamentMatch.Id); hashtable.Add("TournamentId", this.tournament.Id); UIMain.Client.CurrentRoom.SetCustomProperties(hashtable); UIRoom.Instance.OnStartClicked(); } UITournamentHub.HideScreen(); } }) .Run(this); } } public void OnPlayerEnteredRoom(Player newPlayer) { if (this.tournamentMatchController != null) { // Report user who joined room this.tournamentMatchController.ReportJoinedUser((long)newPlayer.CustomProperties["TournamentUserId"]); } } public void OnPlayerLeftRoom(Player otherPlayer) { { // Report user who disconnected from room this.tournamentMatchController.ReportDisconnectedUser((long)otherPlayer.CustomProperties["TournamentUserId"]); } } public void OnPlayerPropertiesUpdate(Player targetPlayer, ExitGames.Client.Photon.Hashtable changedProps) { if (this.tournamentMatchController != null) { // Reporting status change will refresh match metadata this.tournamentMatchController.ReportStatusChange(); } } public void OnRoomPropertiesUpdate(ExitGames.Client.Photon.Hashtable propertiesThatChanged) { if (this.tournamentMatchController != null) { // Reporting status change will refresh match metadata this.tournamentMatchController.ReportStatusChange(); } } public void OnMasterClientSwitched(Player newMasterClient) { } }
-
Get back to Unity project, find
UICanvas > TournamentHubUI > TournamentHubScreen > SubScreen > Canvas > ActiveMatchContainer
, find componentGUITournamentActiveMatch
there will be empty fieldMatchHandler
drag&dropTournamentMatchHandler
into it.
Tournament creation & Final test
Build project
We need at least 2 players to be sure that project is working fine, so build project to create 2nd player.
Don't start it immediately, wait till we create a tournament, otherwise tournament won't be visible on TournamentListScreen
.
Creating tournament template
Creating the template
- Go to https://www.tournament-sdk.com/tournaments
- There you will see
No tournament templates .Create your first template to get started
. -
Press
Create your first template
.
Edit template
-
Tournament template will appear. Select
Edit template
. -
Go to
Description
and setTournament Name
. -
Go to
Registration
set:
Maximum players
- 2Party(team) size
- 1-
Registration rules
->Open to everyone
- Go to
Format/Add Phase
In Format
set:
Teams
- 2Min teams per match
- 2Max Teams per match
- 2
Leave field Max loses
in Scores
empty
In Rounds
set:
Type
- BO3Minimum game time (minutes)
- 2Maximum round time (minutes)
- 8
-
On the bottom of the screen you should see
Careful - you have unsaved changes!
, pressSave Changes
.
Start tournament
- Get back to
Tournament templates
page. -
Press
Schedule
, setTime
toYour current time + 5 minutes
and pressStart tournament
.
Final test
-
Now get back to Unity and start the project.
-
Press
Tournaments
button that was added earlier. You should seeTournamentListScreen
and the tournament that you just added.Tournament may be unavailable to register for some time, wait until
Sign up
button is available to register to tournament. -
Repeat the process in build version
-
When tournament will start, button
Ready to play
will become available. -
Press on both Unity and Build
Ready to play
. -
Finish the match on both players.
-
Get back to: https://www.tournament-sdk.com/schedule
-
You should see tournament.
-
Click on it, to see more information about the tournament and played matches.
-
To check more detailed information about every match played during the tournament, go to
Phase 1
. -
Press
Show matches
to the right from any participant. -
Click
Show details
for more information. -
Click
Game #ID
andStats
for more information.
- Step by step instruction of recreation of the project
- Set up the project
- Tournament-SDK UI implementation
- Prepare Scripts to implement Tournament-SDK UI
- UITournamentList
- UITournamentHub
- Prepare code to implement Tournament-SDK UI
- Add UI
- Tournament Button
- Tournament List & Tournament Hub
- Set Up UI
- TournamentButton
- TournamentList to TournamentHub
- TournamentList to MainMenu
- TournamentHub to TournamentList
- Backbone Integration
- Implementing Tournament logic into the Quantum game
- Tournament creation & Final test
FusionKarts
Implementing Tournament-SDK into Fusion based game
This tutorial is based on:
Karts
game sample available on https://doc.photonengine.com/fusion/current/game-samples/fusion-karts#download
To run Karts project you need Editor Version 2020.3.47f1 or any 2020 LTS version.
Tournament-SDK 1.3.1
is available on https://www.tournament-sdk.com/docs#-sdk-release-notes
Set up the Fusion
Open downloaded Karts project in unity.
Follow instruction in given link to set up Fussion AppId
: https://doc.photonengine.com/fusion/current/tutorials/host-mode-basics/1-getting-started#step_6___create_an_app_id
Set up Tournament-SDK
Import Tournament-SDK.
To set up the Tournament-SDK Client Game ID follow the steps of Create new game and setup gameId
in instruction on following link: https://www.tournament-sdk.com/docs#Create+new+game+and+setup+gameId
Setting up the scene
Add necessary objects
-
In
Project
section, openAssets/Scenes
-> double-clickLaunch
. -
In
Hierarchy
add 2 new GameObjects abovePrompts
, rename them toTournamentListUI
andTournamentHubUI
.
Adjust resolution
-
Select
TournamentListUI
. -
In
Rect Transform
component in left top corner change adjustement to stretched. -
Set
Left
,Top
,Right
,Bottom
parameters to 0. -
Do the same for
TournamentHubUI
.
Setting up the objects
-
Add
UIScreen
as component to both new objects. -
Add
TounamentHubScreen
andTounamentListScreen
toTounamentHubUI
andTounamentHubUI
respectively.Tournament(Hub/List)Screen
can be found inAssets/TournamentSDK_Demo/Prefabs/DefaultUIScreens
.Make sure
SubScreen
inside bothTounamentHubScreen
andTounamentListScreen
is enabled! -
Disable
TounamentListUI
andTounamentHubUI
.
Setting up necessary layout
-
In
Hierarchy
go to ->Main Canvas/Main Menu Screen/LayoutGroup/Footer/LayoutGroup
, selectExit Button
and press Ctrl+D to duplicate the object. -
Inspector
will open right after object duplication, change button's name toTournaments
, also findRect Transform
changeWidth
to 470. -
Open newly created object in
Hierarchy
, selectText
and changeText
component toTournaments
.
Setting up the buttons
Moving from Main Menu Screen to Tournament List UI
- Select
Tournaments
object created previosly inHierarchy
. - Find
Button
component inInspector
. - In
On Click()
field, drag ang dropMain Menu Screen
fromHierarchy
into field underRuntime Only
. - Change
No Function
toUIScreen/FocuScreen
. -
In
Hierarchy
findTournamentListUI
, drag and drop it intoOn Click()
field underUIScreen.FocusScreen
whereNone (UI Screen)
is written.
Moving from Tournament List UI to Main Menu Screen
- In
Hierarchy
go toTournamentListUI/TournamentListScreen/SubScreen/Canvas/Title
. - Select
ButtonUnderline(Back)
, in it'sButton
component drag and dropTournamentListUI
into field underRuntime Only
. -
then change
No Function
toUIScreen/Back
.
Moving from Tournament List UI to Tournament Hub UI
-
Go to
TournamentListUI/TournamentListScreen/SubScreen/Canvas/Scroll View/ViewPort/Content/TournamentListItem/ButtonUnderline(Open)/(Play)
. -
Add new action to
On Click()
. -
Drag
TournamentListUI
into field underRuntime Only
. -
Change
No Function
toUIScreen/FocusScreen
. -
Drag
TournamentHubUI
into field underUIScreen.FocusScreen
.Due to we used prefabs of
TournamentListScreen
andTournamentHubScreen
,GUITournamentListScreen
script has unset variableTournamentHubScreen
. -
Go to
TournamentListUI/TournamentListScreen/SubScreen
there will be unsetTournament Hub Screen
. -
From
TournamentHubUI/TournamentHubScreen
dragSubScreen
into unsetTournament Hub Screen
.
Moving from Tournament Hub UI to Tournament List UI
Go to ButtonUnderline(Back)
in TournamentHubUI
same way as for TournamentListUI
.
-
Add another action on button by pressing
+
. -
Drag and drop
TournamentHubUI
into field underRuntime Only
. -
Change
No Function
toUIScreen/Back
. -
Go to
TournamentHubUI/TournamentHubScreen/SubScreen
inGUISubScreen
component will be unsetReturn Screen
. -
From
TournamentListUI/TournamentListScreen
dragSubScreen
into unsetReturn Screen
field.
Implementing Tournament-SDK
Create/Set up BackboneManager
-
Create new
GameObject
on the very bottom ofMain Canvas
, rename it toBackboneManager
. -
Press
Add Component
-> start typingBackbone Manager
, it will appear in search tab, click on it. -
Backbone Manager
component will appear inInspector
. Make sureInitialize On Start
box is UN-ticked. -
Add
Resource Cache
component toBackbone Manager
object.
Implementing client initialization flow into BackboneIntegration
-
In
Inspector
ofBackboneManager
object selectAdd Component
-> typeBackboneIntegration
-> selectNew Script/Create and Add
. -
Add following code into
BackboneIntegration
script.using Gimmebreak.Backbone.User; using System.Collections; using UnityEngine; using UnityEngine.UI; public class BackboneIntegration : MonoBehaviour { private WaitForSeconds waitOneSecond = new WaitForSeconds(1); [SerializeField] private Button tournamentButton = default; private IEnumerator Start() { // Disable tournament button until user is logged in tournamentButton.interactable = false; // wait until player nick was set (this happens on initial screen) while (string.IsNullOrEmpty(ClientInfo.Username)) { yield return this.waitOneSecond; } // keep trying to initialize client while (!BackboneManager.IsInitialized) { yield return BackboneManager.Initialize(); yield return this.waitOneSecond; } // create arbitrary user id (minimum 64 chars) based on nickname // ClientInfo.Username is the nickname you set on the first launch of the game string arbitraryId = "1000000000000000000000000000000000000000000000000000000000000001" + ClientInfo.Username; // log out user if ids do not match if (BackboneManager.IsUserLoggedIn && BackboneManager.Client.User.GetLoginId(LoginProvider.Platform.Anonym) != arbitraryId) { Debug.LogFormat("Backbone user({0}) logged out.", BackboneManager.Client.User.UserId); yield return BackboneManager.Client.Logout(); } // log in user if (!BackboneManager.IsUserLoggedIn) { yield return BackboneManager.Client.Login(LoginProvider.Anonym(true, ClientInfo.Username, arbitraryId)); if (BackboneManager.IsUserLoggedIn) { Debug.LogFormat("Backbone user({0}) logged in.", BackboneManager.Client.User.UserId); } else { Debug.LogFormat("Backbone user failed to log in."); } } if (BackboneManager.IsUserLoggedIn) { // Enable tournament button, because if user is logged in, // then all the information needed is already available tournamentButton.interactable = true; } } }
BackboneIntegration
component now misses theTournamentButton
-
Drag
Tournaments
button fromMainMenuScreen/LayoutGroup/Footer/LayoutGroup
, intoTournamentButton
field inBackboneIntegration
component.
Preparation to implement TournamentMatchHandler
Further changes are done in order to prevent any errors while implementing TournamentMatchHandler
.
Changes are done in game logic itself so that tournament match would have access to necessary data.
GameLauncher
Go to GameLauncher
script add/change variables/methods as follows:
-
Add
SessionName
variable to store name of the session, so we could gain access to it from other Game classes:public string SessionName { get { if (_runner != null) { return _runner.SessionInfo.Name; } return null; } }
-
Add
SetTournamentLobby()
to set gameMode suitable for tournaments.// First player tries to connect to session as a Host, // if session is already created, then user tries to reconnect as a Client public void SetTournamentLobby() => _gameMode = GameMode.AutoHostOrClient;
-
Go to
JoinOrCreateLobby()
method, in_runner.StartGame({...})
change:DisableClientSessionCreation = true
TO
```csharp
// Allows game to attempt to create a session as a Client
// Condition is false only when tournament starts the session
// False because we don't want to disable that option
DisableClientSessionCreation = _gameMode != GameMode.AutoHostOrClient
```
-
Change
SetConnectionStatus()
to:private void SetConnectionStatus(ConnectionStatus status) { Debug.Log($"Setting connection status to {status}"); ConnectionStatus = status; if (!Application.isPlaying) return; if (status == ConnectionStatus.Disconnected || status == ConnectionStatus.Failed) { SceneManager.LoadScene(LevelManager.LOBBY_SCENE); UIScreen.BackToInitial(); // We know that GameMode.AutoHostOrClient is set only when tournament is starting the session // If it is tournament session, then by the end of it we want player to be directed to TournamentHubUI if (_gameMode == GameMode.AutoHostOrClient) { LevelManager.LoadTournamentHub(); } } }
-
Change
OnPlayerJoined()
to:public void OnPlayerJoined(NetworkRunner runner, PlayerRef player) { if (runner.IsServer) { // GameMode.AutoHostOrClient allows tournament session Host to spawn a GameManager if (_gameMode == GameMode.Host || _gameMode == GameMode.AutoHostOrClient) runner.Spawn(_gameManagerPrefab, Vector3.zero, Quaternion.identity); var roomPlayer = runner.Spawn(_roomPlayerPrefab, Vector3.zero, Quaternion.identity, player); roomPlayer.GameState = RoomPlayer.EGameState.Lobby; } SetConnectionStatus(ConnectionStatus.Connected); }
LevelManager
Go to LevelManager
script add/change variables/methods as follows:
-
Add variables:
// LevelManager will use these variables to redirect player back to TournamentHubScreen after tournament match [SerializeField] private UIScreen tournamentHubScreenUI; [SerializeField] private GUITournamentHubScreen tournamentHubScreenGUI; [SerializeField] private UIScreen tournamentListScreenUI;
-
Add method
LoadTournamentHub()
:public static void LoadTournamentHub() { // Redirect player to TournamentListUI and then to TournamentHubUI to keep correct chronology UIScreen.Focus(Instance.tournamentListScreenUI); Instance.tournamentHubScreenGUI.Initialize(GameManager.Instance.TournamentId); UIScreen.Focus(Instance.tournamentHubScreenUI); }
GameManager
Go to GameManager
script add/change variables/methods as follows:
-
Add variables:
// Values to personalize tournament match and later proceed it's statistics [Networked(OnChanged = nameof(OnLobbyDetailsChangedCallback))] public long GameSessionId { get; set; } [Networked(OnChanged = nameof(OnLobbyDetailsChangedCallback))] public long TournamentId { get; set; } = 0; [Networked(OnChanged = nameof(OnLobbyDetailsChangedCallback))] public long TournamentMatchId { get; set; }
-
Add method:
// Allows us to chech if game is a tournament game public bool IsTournamentGame() => this.isActiveAndEnabled && this.TournamentId != 0;
ClientInfo
Go to ClientInfo
script add/change variables/methods as follows:
-
Add static variables to get information about
Tournament(User/Team)Id
when match result is being proceed:public static long TournamentUserId { get => long.Parse(PlayerPrefs.GetString("C_TournamentUserId", "0")); set => PlayerPrefs.SetString("C_TournamentUserId", value.ToString()); } public static byte TournamentTeamId { get => byte.Parse(PlayerPrefs.GetString("C_TournamentTeamId", "0")); set => PlayerPrefs.SetString("C_TournamentTeamId", value.ToString()); }
RoomPlayer
Go to RoomPlayer
script add/change variables/methods as follows:
-
Add variables:
// These variables allow us to identify player and to proceed players statistics after match [Networked] public long TournamentUserId { get; set; } [Networked] public byte TournamentTeamId { get; set; }
-
In
Spawned()
method findRPC_SetPlayerStats()
and change it to:RPC_SetPlayerStats(ClientInfo.Username, ClientInfo.KartId, ClientInfo.TournamentUserId, ClientInfo.TournamentTeamId);
-
Change
RPC_SetPlayerStats()
method to:private void RPC_SetPlayerStats(NetworkString<_32> username, int kartId, long tournamentUserId, byte tournamentTeamId) { // Allows game to store and update information about Tournament(Team/User)Id on in game object Username = username; KartId = kartId; TournamentUserId = tournamentUserId; TournamentTeamId = tournamentTeamId; }
Add new GUI/UIs to LevelManager
Level Manager
is responsible for returning player back to TournamentHubUI
after match is finished.
- Go to
Main Canvas
. -
Add
TournamentHubScreenUI
,TournamentHubScreenGUI
andTournamentListUI
as shown:
Implementing TournamentMatchHandler
-
Go to
TournamentHubUI/TournamentHubScreen/SubScreen/Canvas
. -
Add empty object to
Canvas
and rename it toTournamentMatchHandler
. -
Add new script to it called
TournamentMatchHandler
. -
Add following code into it.
using Fusion;
using Gimmebreak.Backbone.Core;
using Gimmebreak.Backbone.Tournaments;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TournamentMatchHandler : TournamentMatchCallbackHandler
{
Tournament tournament;
TournamentMatch tournamentMatch;
ITournamentMatchController tournamentMatchController;
bool sessionStarted;
bool creatingSession;
private GameLauncher _launcher;
private WaitForSeconds waitOneSec = new WaitForSeconds(1);
private string tournamentSessionName;
void Awake()
{
_launcher = FindObjectOfType<GameLauncher>();
}
public void OnDisable()
{
// Unattach Actions from RoomPlayer Actions
RoomPlayer.PlayerJoined -= OnPlayerEnteredRoom;
RoomPlayer.PlayerLeft -= OnPlayerLeftRoom;
RoomPlayer.PlayerChanged -= OnPlayerPropertiesUpdate;
}
public override void OnJoinTournamentMatch(Tournament tournament, TournamentMatch match, ITournamentMatchController controller)
{
// User is requesting to join a tournament match, create or join appropriate session
this.tournament = tournament;
this.tournamentMatch = match;
this.tournamentMatchController = controller;
this.sessionStarted = false;
this.creatingSession = false;
this.tournamentSessionName = $"{this.tournamentMatch.Secret}_{this.tournamentMatch.CurrentGameCount}";
// Attach actions to RoomPlayer Actions
RoomPlayer.PlayerJoined += OnPlayerEnteredRoom;
RoomPlayer.PlayerLeft += OnPlayerLeftRoom;
RoomPlayer.PlayerChanged += OnPlayerPropertiesUpdate;
// Join Photon session
StartCoroutine(JoinRoomRoutine());
}
private IEnumerator JoinRoomRoutine()
{
while (this.tournamentMatch != null)
{
// If you require specific region for tournament, you can use
// tournament custom properties providing the info about required region.
// string cloudRegion = this.tournament.CustomProperties.Properties["cloud-region"];
// If tournament match is finished then leave
if (this.tournamentMatch.Status == TournamentMatchStatus.MatchFinished ||
this.tournamentMatch.Status == TournamentMatchStatus.Closed)
{
// Check if connected session is for finished match
if (GameLauncher.ConnectionStatus == ConnectionStatus.Connected &&
_launcher.SessionName == this.tournamentSessionName)
{
_launcher.LeaveSession();
}
}
// Try to connect to tournament match session
else if (GameLauncher.ConnectionStatus == ConnectionStatus.Disconnected)
{
// Set player propery with UserId so we can identify users in session
// Set max players for session based on tournament phase setting
ServerInfo.MaxUsers = (byte)(this.tournament.GetTournamentPhaseById(this.tournamentMatch.PhaseId).MaxTeamsPerMatch * this.tournament.PartySize);
// Join or create Photon session with tournamemnt match secret as session id
ServerInfo.LobbyName = this.tournamentSessionName;
ClientInfo.LobbyName = this.tournamentSessionName;
ClientInfo.TournamentUserId = BackboneManager.Client.User.UserId;
ClientInfo.TournamentTeamId = this.tournamentMatch.GetMatchUserById(BackboneManager.Client.User.UserId).TeamId;
_launcher.SetTournamentLobby();
_launcher.JoinOrCreateLobby();
}
// If we are in wrong session then leave
else if (GameLauncher.ConnectionStatus == ConnectionStatus.Connected &&
this.tournamentSessionName != _launcher.SessionName)
{
_launcher.LeaveSession();
}
yield return this.waitOneSec;
}
}
public override bool IsConnectedToGameServerNetwork()
{
// Check if user is connected to photon and ready to join a session
if (GameLauncher.ConnectionStatus == ConnectionStatus.Connected)
{
return true;
}
return false;
}
public override bool IsGameSessionInProgress()
{
// Check if game session has started
return sessionStarted;
}
public override bool IsUserConnectedToMatch(long userId)
{
// Check if tournament match user is connected to session
foreach (RoomPlayer rp in RoomPlayer.Players)
{
if (rp.TournamentUserId == userId) { return true; }
}
return false;
}
public override bool IsUserReadyForMatch(long userId)
{
// In particular case if player is connected to the match it's considered to be ready
return IsUserConnectedToMatch(userId);
}
public override void OnLeaveTournamentMatch()
{
this.tournament = null;
this.tournamentMatch = null;
this.tournamentMatchController = null;
_launcher.LeaveSession();
}
public override void StartGameSession(IEnumerable<TournamentMatch.User> checkedInUsers)
{
// Start tournament game session with users that checked in.
// Be aware that this callback can be called multiple times until
// sessionStarted returns true.
// Check if session has started
if (sessionStarted)
{
return;
}
// Check if session is not being requested
if (!this.creatingSession)
{
this.creatingSession = true;
// Create tournament game session
BackboneManager.Client.CreateGameSession(
checkedInUsers,
this.tournamentMatch.Id,
0)
.ResultCallback((gameSession) =>
{
this.creatingSession = false;
// Check if game session was created
if (gameSession != null)
{
// Indicate that session has started
this.sessionStarted = true;
RoomPlayer.Local.RPC_ChangeReadyState(true);
RoomPlayer.Local.KartId = 1;
// Set session properties
GameManager.Instance.GameSessionId = gameSession.Id;
GameManager.Instance.TournamentId = this.tournament.Id;
GameManager.Instance.TournamentMatchId = this.tournamentMatch.Id;
}
})
.Run(this);
}
}
// Photon callback when player entered the session
public void OnPlayerEnteredRoom(RoomPlayer player)
{
long userId = player.TournamentUserId;
if (this.tournamentMatchController != null)
{
// Report user who joined room
this.tournamentMatchController.ReportJoinedUser(userId);
}
}
// Photon callback when player disconnected from session
public void OnPlayerLeftRoom(RoomPlayer player)
{
if (this.tournamentMatchController != null)
{
long userId = player.TournamentUserId;
// Report user who disconnected from room
this.tournamentMatchController.ReportDisconnectedUser(userId);
}
}
// Photon callback when player properties are updated
public void OnPlayerPropertiesUpdate(RoomPlayer player)
{
if (this.tournamentMatchController != null)
{
// Reporting status change will refresh match metadata
this.tournamentMatchController.ReportStatusChange();
}
}
}
Implementing TournamentMatchHandler into ActiveMatchContainer
After all necessary changes are done, add TournamentMatchHandler
to ActiveMatchContainer
as MatchHandler
.
ActiveMatchContainer
can be found in TournamentHubUI/TournamentHubScreen/SubScreen/Canvas/ActiveMatchContainer
.
Implementing Result submission
- Go to
EndRaceUI
script. - Add variable that will keep track if result processing started.
private bool processingStarted = false;
- Find
RedrawResultsList()
method and edit it as follows:
public void RedrawResultsList(KartComponent updated)
{
var parent = resultsContainer.transform;
ClearParent(parent);
var karts = GetFinishedKarts();
for (var i = 0; i < karts.Count; i++)
{
var kart = karts[i];
// As we disconnect player from session before dispawned is called,
// this method will be called when kart.Controller.RoomUser is already destroyed.
// Doesn't happen in ordinary match.
if (kart.Controller.RoomUser != null)
{
Instantiate(resultItemPrefab, parent)
.SetResult(kart.Controller.RoomUser.Username.Value, kart.LapController.GetTotalRaceTime(), i + 1);
}
}
EnsureContinueButton(karts);
}
- Now edit
EnsureContinueButton()
method and addProcessResult()
method:
private void EnsureContinueButton(List<KartEntity> karts)
{
var allFinished = karts.Count == KartEntity.Karts.Count;
// Remove submission button and proceed result without user interaction to prevent any errors connected to that
if (!this.processingStarted && this.isActiveAndEnabled && allFinished && GameManager.Instance.IsTournamentGame())
{
StartCoroutine(ProcessResult(karts));
}
}
private IEnumerator ProcessResult(List<KartEntity> karts)
{
// Notify that result processing has started
this.processingStarted = true;
List<GameSession.User> users = new List<GameSession.User>();
Dictionary<long, float> results = new Dictionary<long, float>();
GameSession gameSession;
for (int i = 0; i < karts.Count; i++)
{
// Loop through each kart to obtain necessary information for result submition
var tempUserId = tempSorted[i].Controller.RoomUser.TournamentUserId;
var tempTeamId = tempSorted[i].Controller.RoomUser.TournamentTeamId;
var tempUserValue = tempSorted[i].LapController.GetTotalRaceTime();
// All karts are already sorted by time
users.Add(new GameSession.User(tempUserId, tempTeamId) { Place = i + 1 });
results.Add(tempUserId, tempUserValue);
}
// Create new GameSession
gameSession = new GameSession(
GameManager.Instance.GameSessionId,
0,
users,
GameManager.Instance.TournamentMatchId);
// Attach users and their results to current GameSession
gameSession.Users.ForEach(user =>
{
gameSession.AddStat(1, user.UserId, (decimal)results[user.UserId]);
});
//report game session
yield return BackboneManager.Client.SubmitGameSession(gameSession);
//refresh tournament data
yield return BackboneManager.Client.LoadTournament(GameManager.Instance.TournamentId);
// Leave session after result was submited
FindObjectOfType<GameLauncher>().LeaveSession();
}
Tournament creation & Final test
Build project
We need at least 2 players to be sure that project is working fine, so build project to create 2nd player.
Don't start it immediately, wait till we create a tournament, otherwise tournament won't be visible on TournamentListScreen
.
Creating tournament template
Creating the template
- Go to https://www.tournament-sdk.com/tournaments
- There you will see
No tournament templates .Create your first template to get started
. -
Press
Create your first template
.
Edit template
-
Tournament template will appear. Select
Edit template
. -
Go to
Description
and setTournament Name
. -
Go to
Registration
set:
Maximum players
- 2Party(team) size
- 1-
Registration rules
->Open to everyone
- Go to
Format/Add Phase
In Format
set:
Teams
- 2Min teams per match
- 2Max Teams per match
- 2
Leave field Max loses
in Scores
empty
In Rounds
set:
Type
- BO3Minimum game time (minutes)
- 2Maximum round time (minutes)
- 8
-
On the bottom of the screen you should see
Careful - you have unsaved changes!
, pressSave Changes
.
Start tournament
- Get back to
Tournament templates
page. -
Press
Schedule
, setTime
toYour current time + 5 minutes
and pressStart tournament
.
Final test
-
Now get back to Unity and start the project.
-
Press
Tournaments
button that was added earlier. You should seeTournamentListScreen
and the tournament that you just added.Tournament may be unavailable to register for some time, wait until
Sign up
button is available to register to tournament. -
Repeat the process in build version
-
When tournament will start, button
Ready to play
will become available. -
Press on both Unity and Build
Ready to play
. -
Finish the match on both players.
-
Get back to: https://www.tournament-sdk.com/schedule
-
You should see tournament.
-
Click on it, to see more information about the tournament and played matches.
-
To check more detailed information about every match played during the tournament, go to
Phase 1
. -
Press
Show matches
to the right from any participant. -
Click
Show details
for more information. -
Click
Game #ID
andStats
for more information.
- Implementing Tournament-SDK into Fusion based game
- Setting up the scene
- Implementing Tournament-SDK
- Implementing Result submission
- Tournament creation & Final test
FusionTanknarok
Implementing Tournament-SDK into Fusion based game
This tutorial is based on:
Tanknarok
game sample available on https://doc.photonengine.com/fusion/current/game-samples/fusion-tanknarok#download
To run Karts project you need Editor Version 2020.3.47f1 or any 2020 LTS version.
Tournament-SDK 1.3.1
is available on https://www.tournament-sdk.com/docs#-sdk-release-notes
Set up the Fussion
Open downloaded Karts project in unity.
Follow instruction in given link to set up Fussion AppId
: https://doc.photonengine.com/fusion/current/tutorials/host-mode-basics/1-getting-started#step_6___create_an_app_id
Set up Tournament-SDK
Import Tournament-SDK.
To set up the Tournament-SDK Client Game ID follow the steps of Create new game and setup gameId
in instruction on following link: https://www.tournament-sdk.com/docs#Create+new+game+and+setup+gameId
Setting up the scene
Adding/adjusting necessary prefabs/objects/components
-
To open
MainScene
go toAssets/Scenes/MainScene
-
Create two new objects in
MainScene/App
and rename them toTournamentListPanel
andTournamentHubPanel
. -
For both
TournamentListPanel
andTournamentHubPanel
setRect Transform
tostretch
and re-setLeft
/Right
/Top
/Bottom
to 0. -
Add
Panel
Component to bothTournamentListPanel
andTournamentHubPanel
. -
Go to
Assets/TournamentSDK_Demo/Prefabs/DefaultUIScreens
. -
Drag&Drop
TournamentHubScreen
andTournamentListScreen
prefabs toTournamentHubPanel
andTournamentListPanel
respectively. -
Find
SharedMode
inApp/StartUI/SharedMode
, duplicate it and rename toTournamentMode
and change it'sPosX
param inRect Transform
to541
. -
Go to
TournamentMode/Text(TMP)(1)
changeText
component toTournament Mode
. -
Go to
TournamentMode/BtnShared
rename it toBtnTournaments
, inBtnTournament/Text (TMP)
changeText
component toTournaments
.
Before setting up the UI buttons, let's adjust game code to it.
GameLauncher code update for UI usage
Open GameLauncher
script:
-
Add necessary variables to store information about tournament player/match:
// Panels to control UI to: // Open TournamentListPanel from MainMenu // Open TournamentHubPanel from TournamentListPanel // Get back from TournamentHubPanel to TournamentListPanel // Get back from TournamentListPanel to MainMenu [SerializeField] private Panel _tournamentListPanel; [SerializeField] private Panel _tournamentHubPanel; // Store TournamentHubGUI to enable it after tournament game has ended [SerializeField] private GUITournamentHubScreen _tournamentHubGUI; // Variables to store players tournament info public static long TournamentUserId { get;set;} public static byte TournamentTeamId { get;set;} public static string PlayerName { get; set; } // Variables to store information about tournament to process results after match finishes public static long TournamentId { get; set; } public static long GameSessionId { get; set; } public static long TournamentMatchId { get; set; } // Boolean to identify if tournament game has been started/finished (Not ordinary game!) public static bool TournamentGame = false;
-
Add necessary methods to enable switching among
MainMenu/TournamentListScreen/TournamentHubScreen
.Allows to redirect player from
MainMenu
toTournamentListScreen
.public void OnTournamentOptions() { if (GateUI(_uiStart)) _tournamentListPanel.SetVisible(true); }
Getter and Setter for tournament session name which is
_room.text
in this case.public string GetSessionName() { return _room.text; } public void SetSessionName(string name) { _room.text = name; }
Allows us to manipulate
Panels
to enable switching betweenTournamentListScreen/TournamentHubScreen
.public void ForceVisible(Panel panel) { panel.SetVisible(true); } public void ForceInvisible(Panel panel) { panel.SetVisible(false); }
Allows to get players connection status to game session.
public ConnectionStatus GetConnectionStatus() { return _status; }
-
After all addition necessary for UI manipulation, let's change game code to provide smooth tournament game start and result processing:
Change method
Start()
to:private void Start() { // Add or create name to register player for tournament, add to PlayerPrefs not to get random name on every launch if (PlayerPrefs.GetString("login").IsNullOrEmpty()) { PlayerName = $"Player{Random.Range(1, 10)}{Random.Range(1, 10)}{Random.Range(1, 10)}"; PlayerPrefs.SetString("login", PlayerName); } else { PlayerName = PlayerPrefs.GetString("login"); } OnConnectionStatusUpdate(null, FusionLauncher.ConnectionStatus.Disconnected, ""); }
Change method
OnEnterRoom()
to:public void OnEnterRoom() { if (TournamentGame) { _gameMode = GameMode.Shared; // If starting tournament game, _uiRoom must be set visible immediately(second "True" param) to prevent animation from breaking _uiRoom.SetVisible(true, true); } if (GateUI(_uiRoom)) { FusionLauncher launcher = FindObjectOfType<FusionLauncher>(); if (launcher == null) launcher = new GameObject("Launcher").AddComponent<FusionLauncher>(); LevelManager lm = FindObjectOfType<LevelManager>(); lm.launcher = launcher; launcher.Launch(_gameMode, _room.text, lm, OnConnectionStatusUpdate, OnSpawnWorld, OnSpawnPlayer, OnDespawnPlayer); } }
Change method
OnConnetionStatusUpdate()
to:private void OnConnectionStatusUpdate(NetworkRunner runner, FusionLauncher.ConnectionStatus status, string reason) { if (!this) return; Debug.Log(status); if (status != _status) { switch (status) { case FusionLauncher.ConnectionStatus.Disconnected: // No need to show message if we forced disconnect, after tournament game has been finished! if (!TournamentGame) ErrorBox.Show("Disconnected!", reason, () => { }); break; case FusionLauncher.ConnectionStatus.Failed: ErrorBox.Show("Error!", reason, () => { }); break; } } _status = status; UpdateUI(); }
Change method
UpdateUI()
to:private void UpdateUI() { bool intro = false; bool progress = false; bool running = false; switch (_status) { case FusionLauncher.ConnectionStatus.Disconnected: _progress.text = "Disconnected!"; intro = true; break; case FusionLauncher.ConnectionStatus.Failed: _progress.text = "Failed!"; intro = true; break; case FusionLauncher.ConnectionStatus.Connecting: _progress.text = "Connecting"; progress = true; break; case FusionLauncher.ConnectionStatus.Connected: _progress.text = "Connected"; progress = true; break; case FusionLauncher.ConnectionStatus.Loading: _progress.text = "Loading"; progress = true; break; case FusionLauncher.ConnectionStatus.Loaded: running = true; break; } _uiCurtain.SetVisible(!running); _uiProgress.SetVisible(progress); // If player was disconnected from Tournament game, redirect to TournamentHubPanel if (_status == FusionLauncher.ConnectionStatus.Disconnected && TournamentGame) { // Disable Main Menu _uiStart.SetVisible(false); TournamentGame = false; // Initialize recent tournament _tournamentHubGUI.Initialize(TournamentId); // redirect to TournamentHubPanel _tournamentHubPanel.SetVisible(true); } else _uiStart.SetVisible(intro); _uiGame.SetActive(running); if (intro) MusicPlayer.instance.SetLowPassTranstionDirection(-1f); }
-
Get back to
App
inHierarchy
findGameLauncher
component and add missingPanels
andGUI
Setting up UI
Tournament button
Direct player from MainMenu
to TournamentListScreen
.
Go to App/StartUI/TournamentMode/BtnTournaments
, find Button
component and set OnClick()
to GameLauncher.OnTournamentOptions
TournamentListScreen to MainMenu
To allow player to return back from TournamentListPanel
to MainMenu
:
-
Go to
TournamentListPanel/TournamentListScreen/SubScreen/Canvas/Title/ButtonUnderline(Back)
. -
Remove
GUISubScreen.Back
, by pressing-
underOnClick()
. -
Add two new actions, by pressing
+
underOnClick()
. -
Drag&drop
App
fromHierarchy
to empty field underRuntime Only
for both. -
Set them from
No Function
toGameLauncher.ForceInvisible
andGameLauncher.ForceVisible
. -
Drag&drop
TournamentListPanel
andStartUI
respectively.
TournamentListScreen to TournamentHubScreen
-
Go to
TournamentListPanel/TournamentListScreen/SubScreen
. -
Drag&drop
TournamentHubPanel/TournamentHubScreen/SubScreen
into fieldTournamentHubScreen
inGUITournamentListScreen
. -
Go to
TournamentListPanel/TournamentListScreen/SubScreen/Canvas/ScrollView/Viewport/Content/ButtonUnderline(Open)
. -
Add two new
OnClick()
actions. -
Drag&drop
App
fromHierarchy
to empty field underRuntime Only
for both. -
Set them to
GameLauncher.ForceInvisible
andGameLauncher.ForceVisible
. -
Drag&drop
TournamentListPaenl
andTournamentHubPanel
respectively. -
Repeat for
TournamentListPanel/TournamentListScreen/SubScreen/Canvas/ScrollView/Viewport/Content/ButtonUnderline(Play)
.
TournamentHubScreen to TournamentListScreen
To allow player to return back from TournamentHubPanel
to TournamentListPanel
:
-
Go to
TournamentHubPanel/TournamentHubScreen/SubScreen
. -
In
GUISubScreen
component fill empty fieldReturn Screen
withTournamentListPanel/TournamentListScreen/SubScreen
. -
Go to
TournamentHubPanel/TournamentHubScreen/SubScreen/Canvas/Title/ButtonUnderline(Back)
-
Add two more actions.
-
Set them to
GameLauncher.ForceInvisible
andGameLauncher.ForceVisible
as previously. -
Drag&drop
TournamentHubPanel
andTournamentListPanel
respectively.
Backbone integration
To gather information about available tournaments we first need to log in player.
-
Create new empty object at the end of
MainScene
. -
Rename it to
BackboneManager
. -
Add
BackboneManager
andResourceCache
components to it. -
Create new script called
BackboneIntegration
and add it toBackboneManager
.Make sure
Initialize On Start
field is Disabled -
Open
BackboneIntegration
script and add following code:using FusionExamples.Tanknarok; using Gimmebreak.Backbone.User; using System.Collections; using UnityEngine; using UnityEngine.UI; public class BackboneIntegration : MonoBehaviour { private WaitForSeconds waitOneSecond = new WaitForSeconds(1); [SerializeField] private Button tournamentButton = default; private IEnumerator Start() { tournamentButton.interactable = false; // wait until player nick was set (this happens on initial screen) while (string.IsNullOrEmpty(GameLauncher.PlayerName)) { yield return this.waitOneSecond; } // keep trying to initialize client while (!BackboneManager.IsInitialized) { yield return BackboneManager.Initialize(); yield return this.waitOneSecond; } // create arbitrary user id (minimum 64 chars) based on nickname string arbitraryId = "1000000000000000000000000000000000000000000000000000000000000001" + GameLauncher.PlayerName; Debug.Log($"arbitraryId -> {arbitraryId}"); // log out user if ids do not match if (BackboneManager.IsUserLoggedIn && BackboneManager.Client.User.GetLoginId(LoginProvider.Platform.Anonym) != arbitraryId) { Debug.LogFormat("Backbone user({0}) logged out.", BackboneManager.Client.User.UserId); yield return BackboneManager.Client.Logout(); } // log in user if (!BackboneManager.IsUserLoggedIn) { yield return BackboneManager.Client.Login(LoginProvider.Anonym(true, GameLauncher.PlayerName, arbitraryId)); if (BackboneManager.IsUserLoggedIn) { Debug.LogFormat("Backbone user({0}) logged in.", BackboneManager.Client.User.UserId); } else { Debug.LogFormat("Backbone user failed to log in."); } } if (BackboneManager.IsUserLoggedIn) { tournamentButton.interactable = true; } } }
BackboneIntegration
component inBackboneManager
now missesTournamentButton
, go ahead and add it.BackboneIntegration
startsBackboneManager
initialization and logs player in, which allows game gather information about available tournaments.
Tournament Match Handler
Escential changes to implement TournamentMatchHandler
To successfully start a tournament match, before adding TournamentMatchHandler
let's first prepare game to work smoothly with it.
We already added necessary variables and methods to GameLauncher
earlier, now using them let's update code to support tournament games.
-
Open
Player
script:-
Add variables to store user's info connected to tournament:
// Assign call back to receive notification when player has assigned it's unique tourament id [Networked(OnChanged = nameof(TournamentUserIDChanged))] public long TournamentUserId { get; set; } = 0; [Networked] public byte TournamentTeamId { get; set; } public static Action<long> OnPlayerTIDChange; public static Action<long> OnPlayerDespawned;
-
Add method responsible for call back:
public static void TournamentUserIDChanged(Changed<Player> changed) { // Notify subscribers to this call that TournamentUserId has changed OnPlayerTIDChange(changed.Behaviour.TournamentUserId); }
-
Change method
Spawned()
to:public override void Spawned() { if (Object.HasInputAuthority) { // Store player variables escential to process its results. local = this; if (GameLauncher.TournamentGame) { playerName = GameLauncher.PlayerName; TournamentUserId = GameLauncher.TournamentUserId; TournamentTeamId = GameLauncher.TournamentTeamId; } } // Getting this here because it will revert to -1 if the player disconnects, but we still want to remember the Id we were assigned for clean-up purposes playerID = Object.InputAuthority; ready = false; SetMaterial(); SetupDeathExplosion(); _teleportIn.Initialize(this); _teleportOut.Initialize(this); _damageVisuals = GetComponent<TankDamageVisual>(); _damageVisuals.Initialize(playerMaterial); PlayerManager.AddPlayer(this); // Auto will set proxies to InterpolationDataSources.Snapshots and State/Input authority to InterpolationDataSources.Predicted // The NCC must use snapshots on proxies for lag compensated raycasts to work properly against them. // The benefit of "Auto" is that it will update automatically if InputAuthority is changed (this is not relevant in this game, but worth keeping in mind) GetComponent<NetworkCharacterControllerPrototype>().InterpolationDataSource = InterpolationDataSources.Auto; }
-
Change method
Despawned()
to:public override void Despawned(NetworkRunner runner, bool hasState) { Destroy(_deathExplosionInstance); PlayerManager.RemovePlayer(this); OnPlayerDespawned(this.TournamentUserId); }
-
-
Go to
PlayerManager
script:Add new method
GetPlayerFromTID()
// Allows us to get spesific user using its TournamentUserId public static Player GetPlayerFromTID(long id) { foreach (Player player in _allPlayers) { if (player.TournamentUserId == id) return player; } return null; }
-
Go to
GameManager
script:-
Add variable to track if result processing has started:
public bool processingStarted = false;
-
Change method
OnTankDeath()
to:public void OnTankDeath() { if (playState != PlayState.LOBBY) { int playersleft = PlayerManager.PlayersAlive(); Debug.Log($"Someone died - {playersleft} left"); if (playersleft <= 1) { Player lastPlayerStanding = playersleft == 0 ? null : PlayerManager.GetFirstAlivePlayer(); // if there is only one player, who died from a laser (e.g.) we don't award scores. if (lastPlayerStanding != null) { int winningPlayerIndex = lastPlayerStanding.playerID; int nextLevelIndex = _levelManager.GetRandomLevelIndex(); int winningPlayerScore = lastPlayerStanding.score + 1; if (winningPlayerIndex >= 0) { Player winner = PlayerManager.GetPlayerFromID(winningPlayerIndex); if (winner.Object.HasStateAuthority) winner.score = winningPlayerScore; if (winningPlayerScore >= MAX_SCORE) nextLevelIndex = -1; } if (GameLauncher.TournamentGame && nextLevelIndex == -1) { StartCoroutine(ProcessResult()); } else { LoadLevel(nextLevelIndex, winningPlayerIndex); } } } } }
-
Add method
ProcessResult()
:private IEnumerator ProcessResult() { // Wait for one sec, which is enough to propogate winner score to other players yield return new WaitForSeconds(1); this.processingStarted = true; List<GameSession.User> users = new List<GameSession.User>(); Dictionary<long, float> usersScore = new Dictionary<long, float>(); GameSession gameSession; List<Player> players = PlayerManager.allPlayers.OrderBy(x => x.score).Reverse().ToList(); for (int i = 0; i < players.Count; i++) { var TournamentUserId = players[i].TournamentUserId; var TournamentTeamId = players[i].TournamentTeamId; var UserScore = players[i].score; users.Add(new GameSession.User(TournamentUserId, TournamentTeamId) { Place = i + 1 }); usersScore.Add(TournamentUserId, UserScore); } // Create gameSession using previously saved info about tournament in GameLauncher to process the match result gameSession = new GameSession( GameLauncher.GameSessionId, 0, users, GameLauncher.TournamentMatchId); gameSession.Users.ForEach(user => { gameSession.AddStat(1, user.UserId, (decimal)usersScore[user.UserId]); }); //report game session yield return BackboneManager.Client.SubmitGameSession(gameSession); //refresh tournament data yield return BackboneManager.Client.LoadTournament(GameLauncher.TournamentId); Restart(ShutdownReason.Ok); // <- Disconnect from the finished match }
-
Implementing TournamentMatchHandler
-
Go to
App/TournamentHubPanel/TournamentHubScreen/SubScreen/Canvas
. -
Add new game object, rename it to
TournamentMatchHandler
. -
Create new script called
TournamentMatchHandler
, add it as component toTournamentMatchHandler
game object. -
Add following code into it:
using Fusion; using Fusion.Sockets; using FusionExamples.FusionHelpers; using FusionExamples.Tanknarok; using FusionExamples.UIHelpers; using Gimmebreak.Backbone.Core; using Gimmebreak.Backbone.Tournaments; using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class TournamentMatchHandler : TournamentMatchCallbackHandler { Tournament tournament; TournamentMatch tournamentMatch; ITournamentMatchController tournamentMatchController; bool sessionStarted; bool creatingSession; private GameLauncher launcher; private WaitForSeconds waitOneSec = new WaitForSeconds(1); private string tournamentSessionName; [SerializeField] Panel tournamentHubPanel; void Awake() { launcher = FindObjectOfType<GameLauncher>(); // Assign call back to receive notification when player has set unique TournamentUserId Player.OnPlayerTIDChange += OnPlayerIdChanged; // Assign call back to receive notification when player is being despawned Player.OnPlayerDespawned += OnPlayerLeftRoom; } private void LeaveSession() { // To leave game session we call runner.Shutdown which will disconnect player from game session NetworkRunner runner = FindObjectOfType<NetworkRunner>(); if (runner != null && !runner.IsShutdown) { // Calling with destroyGameObject false because we do this in the OnShutdown callback on FusionLauncher runner.Shutdown(false, ShutdownReason.Ok); } } public void OnPlayerIdChanged(long userId) { // Call back that will be fired as soon as new player assings it's tournamentUserId if (PlayerManager.GetPlayerFromTID(userId)) OnPlayerEnteredRoom(userId); } public override void OnJoinTournamentMatch(Tournament tournament, TournamentMatch match, ITournamentMatchController controller) { // User is gathering information about tournament match that player want's to join this.tournament = tournament; this.tournamentMatch = match; this.tournamentMatchController = controller; this.sessionStarted = false; this.creatingSession = false; this.tournamentSessionName = $"{this.tournamentMatch.Secret}_{this.tournamentMatch.CurrentGameCount}"; // Join Photon session according to gathered information StartCoroutine(JoinRoomRoutine()); } private IEnumerator JoinRoomRoutine() { while (this.tournamentMatch != null) { // If tournament match is finished then leave if (this.tournamentMatch.Status == TournamentMatchStatus.MatchFinished || this.tournamentMatch.Status == TournamentMatchStatus.Closed) { if (launcher.GetConnectionStatus() == FusionLauncher.ConnectionStatus.Loaded && launcher.GetSessionName() == this.tournamentSessionName) { LeaveSession(); } } // Try to connect to tournament match session if player is not in any other game session else if (launcher.GetConnectionStatus() == FusionLauncher.ConnectionStatus.Disconnected) { // Set users tournament info for results processing, that will hapen after the match will be finished GameLauncher.TournamentUserId = BackboneManager.Client.User.UserId; GameLauncher.TournamentTeamId = this.tournamentMatch.GetMatchUserById(BackboneManager.Client.User.UserId).TeamId; // Allows to differ tournament game from ordinary one // Used to redirect player back to tournamentHub after match is finished // Triggers result processing start after match is finished GameLauncher.TournamentGame = true; // Set session id as Photon's tournamemnt session match secret launcher.SetSessionName(this.tournamentSessionName); // Create/Join session // GameMode is set to Shared in OnEnterRoom if it's tournament game launcher.OnEnterRoom(); } // If user connected to wrong session -> then leave else if (launcher.GetConnectionStatus() == FusionLauncher.ConnectionStatus.Loaded && this.tournamentSessionName != launcher.GetSessionName()) { LeaveSession(); } yield return this.waitOneSec; } } public override bool IsConnectedToGameServerNetwork() { // Check if user created/joined a lobby // We know that it's correct lobby, due to checks in JoinRoomRoutine return launcher.GetConnectionStatus() == FusionLauncher.ConnectionStatus.Loaded; } public override bool IsUserConnectedToMatch(long userId) { // Check if tournament match user is connected to game session // User is considered connected when entered lobby with own unique TournamentUserId return (PlayerManager.GetPlayerFromTID(userId) != null); } public override bool IsUserReadyForMatch(long userId) { // Check if user is ready to start tournament match // Player is considered as ready to play tournament match when it has connected to game session and has unique TournamentUserId assigned to it return IsUserConnectedToMatch(userId); } public override void OnLeaveTournamentMatch() { // If user leaves tournament match, need to remove all information about the match that user previously connected to this.tournament = null; this.tournamentMatch = null; this.tournamentMatchController = null; // And disconnect player from game session LeaveSession(); GameLauncher.TournamentGame = false; } public override bool IsGameSessionInProgress() { // Check if tournament game session has been started return sessionStarted; } public override void StartGameSession(IEnumerable<TournamentMatch.User> checkedInUsers) { // Start tournament game session with users that checked in. // Be aware that this callback can be called multiple times until sessionStarted returns true. // Check if session has started if (sessionStarted) { return; } // Check if tournament game session creation has started, If not then start tournament game session creation if (!this.creatingSession) { this.creatingSession = true; // Create tournament game session BackboneManager.Client.CreateGameSession( checkedInUsers, this.tournamentMatch.Id, 0) .ResultCallback((gameSession) => { this.creatingSession = false; // Check if game session was created if (gameSession != null) { // Indicate that session has started this.sessionStarted = true; // Set session properties to GameLauncher to be able to access them even after the TournamentMatchHandler will be disabled // So later when game will be finished we could use these properties to process session results GameLauncher.GameSessionId = gameSession.Id; GameLauncher.TournamentId = this.tournament.Id; GameLauncher.TournamentMatchId = this.tournamentMatch.Id; // Start game immediately. GameManager.instance.OnAllPlayersReady(); // Disable THPanel because by this moment lobby is already in the back ground, but TournamentHubPanel is on top of it. launcher.ForceInvisible(tournamentHubPanel); } }) .Run(this); } } // Photon callback when player entered the session public void OnPlayerEnteredRoom(long userId) { // Photon session informing TSDK if (this.tournamentMatchController != null) { // Report TournamentMatchController about user who joined session this.tournamentMatchController.ReportJoinedUser(userId); } } // Photon callback when player disconnected from session public void OnPlayerLeftRoom(long userId) { if (this.tournamentMatchController != null) { // Report TournamentMatchController about user who disconnected from session this.tournamentMatchController.ReportDisconnectedUser(userId); } } }
-
Go to
App/TournamentHubPanel/TournamentHubScreen/SubScreen/Canvas/TournamentMatchHandler
, inTournamentMatchHandler
component will be unsetTournamentHubPanel
, drag&dropTournamentHubPanel
fromHierarchy
into empty field. -
Go to
App/TournamentHubPanel/TournamentHubScreen/SubScreen/Canvas/ActiveMatchContainer
, drag&dropTournamentMatchHandler
intoMatchHandler
forGUITournamentActiveMatch
component.Before running the project, disable both
TournamentListPanel
andTournamentHubPanel
.Make sure
TournamentListPanel/TournamentListScreeb/SubScreen
is enabled.
Tournament creation & Final test
Build project
We need at least 2 players to be sure that project is working fine, so build project to create 2nd player.
Don't start it immediately, wait till we create a tournament, otherwise tournament won't be visible on TournamentListScreen
.
Creating tournament template
Creating the template
- Go to https://www.tournament-sdk.com/tournaments
- There you will see
No tournament templates .Create your first template to get started
. -
Press
Create your first template
.
Edit template
-
Tournament template will appear. Select
Edit template
. -
Go to
Description
and setTournament Name
. -
Go to
Registration
set:
Maximum players
- 2Party(team) size
- 1-
Registration rules
->Open to everyone
- Go to
Format/Add Phase
In Format
set:
Teams
- 2Min teams per match
- 2Max Teams per match
- 2
Leave field Max loses
in Scores
empty
In Rounds
set:
Type
- BO3Minimum game time (minutes)
- 2Maximum round time (minutes)
- 8
-
On the bottom of the screen you should see
Careful - you have unsaved changes!
, pressSave Changes
.
Start tournament
- Get back to
Tournament templates
page. -
Press
Schedule
, setTime
toYour current time + 5 minutes
and pressStart tournament
.
Final test
-
Now get back to Unity and start the project.
-
Press
Tournaments
button that was added earlier. You should seeTournamentListScreen
and the tournament that you just added.Tournament may be unavailable to register for some time, wait until
Sign up
button is available to register to tournament. -
Repeat the process in build version
-
When tournament will start, button
Ready to play
will become available. -
Press on both Unity and Build
Ready to play
. -
Finish the match on both players.
-
Get back to: https://www.tournament-sdk.com/schedule
-
You should see tournament.
-
Click on it, to see more information about the tournament and played matches.
-
To check more detailed information about every match played during the tournament, go to
Phase 1
. -
Press
Show matches
to the right from any participant. -
Click
Show details
for more information. -
Click
Game #ID
andStats
for more information.
Starting With Dashboard
Login
You will receive an email from the Tournament Dashboard, from the email address: no-reply@tournament-sdk.com - Make sure to check your junk/spam mail folder. It will look like this:
The email contains a link for you to click on to validate your account and allow you to choose a password. When you have completed this process, you will be able to log in to the Tournament Dashboard. Bookmark the link and don't forget your password!
Create Game
To set up your game on the platform, click on the icon in the top left hand corner of the screen and select "New game" option:
This will step you through the process of creating your new game in the Tournament Dashboard.
Step 1 (Info)
Enter the following details, then click the "Continue" button:
Game name
The name of your game as it will be displayed in the Tournament dashboard.
Game icon URL
The URL of your game's icon so it can be displayed in the Tournament dashboard.
Version Number
The version number of your game in the format n.n.n.
Step 2 (Seasons)
Unless you know what you're doing at this stage (i.e. you've set up a game in the Tournament Dashboard previously), select "Default" then click the "Continue" button (you can change these settings later):
Step 3 (Confirmation)
Click on the "Create game" button:
Congratulations, you've successfully created your game in the Tournament Dashboard.
Navigation
The Tournament Dashboard is navigated using the icons shown in the bar on the left hand side of the screen. As you click on the icons, the content for that icon will be displayed in the main part of the screen - the left side-bar will always be visible.
As you go through the set up process, the system will attempt to help you make sure that you're completing all the relevant steps by:
-
showing indicators for mandatory data
-
showing specific messages at the bottom of the page if:
- you attempt to save your configuration/set up while there are still tasks that you need to complete
- you've made a mistake
See below:
It will also make sure that, if you make changes, you don't forget to save them by showing the below message at the bottom of the screen when you start working:
And by showing the below message if you attempt to navigate away from the screen before trying to save your changes:
There is also text on the screens that describes the purpose of each feature so, in a lot of cases, what you're attempting to do will be self-explanatory. This document contains a complete glossary of all of the settings and features in the Tournament Dashboard. You can also call or email our team if you get stuck or need help or advice.
First Tournament
This guide explains how to use the Tournament Dashboard to set up and run automated tournaments within your game.
As a prerequisite to using this guide, your development team (or you!) should have already implemented the Tournament SDK into a build of your game.
Step 1: Game settings
The first thing that you'll need to do is configure the Game Settings for your specific game. This is a one-off exercise which involves some, limited technical activity.
You access the Game settings by clicking on the Game Controller icon in the left hand bar as shown in the picture below. All of the settings that you need to complete are described in the Glossary of Settings - "Section 5: Game Settings (Game Controller icon)" so refer to that section and go and complete this activity now.
Step 2: Tournament Settings
Now that your General Settings are complete, you have to create your first tournament template. You access the Tournament templates by clicking on the Trophy icon in the left hand bar as shown in the picture below. All of the settings that you need to complete are described in the Glossary of Settings - "Section 3: Tournament Templates (Trophy icon)" so refer to that section and go and complete this activity now. To get started you will need to click on the "New template" box as seen in the picture below.
When you've been using the Tournament Dashboard for a while, this screen will be full up with templates (each one laying out the plan for a specific tournament series) and you'll be able to create a new template by simply duplicating an existing one.
Step 3: Start Tournament
When you've completed your first template, click on the template's green "Start tournament" button then enter a date and time that you'd like the tournament to be run (this should be a future date/time). The tournament will be started automatically at this time.
When the tournament has been run successfully (you can initially set it up as a "testing" type tournament and then change this later) and you're happy with the way it went, you can then set recurrency settings so that it will automatically be scheduled and run at pre-defined intervals.
To do this, click on the "(change)" Recurrency link against the template:
You can use this to set multiple recurrency rules per tournament template defining (for each):
- Starting from date: The date on which you want the first tournament to run (this should be in the future)
- Tournament Time (GMT): The time at which you want the tournament to start (if you want to run more than one a day, simply add a new recurrency rule by clicking the "+" button)
- Repeat: Choose from options (Every day, Every 2 days, Every 3 days, Every 5 days, Every week, Every 2 weeks, Every 4 weeks)
- Tournaments pre-generated: The number of tournaments you want to be generated from this template. N.B.: Once tournaments have been generated, changes made to the tournament template will not change/update existing tournaments, they will only affect tournaments that you subsequently generate.
Your pre-generated tournaments will now automatically be run. Invites will be sent (if any), players will sign up, entry fees will be collected (if any), requirements will be checked (if any), players will play, lose, draw and win games and matches, some players will be knocked out while others advance, you will have a champion and a number of runners up, their prizes will be calculated and awarded, and invites will be sent to players for subsequent tournaments. All of this will happen without any manual input whatsoever. The players just play and you just chill.
Step 4: Review and refinement
When you have either a) clicked the green button (against the tournament template) to manually start/schedule tournament or b) pre-generated multiple tournaments using the tournament schedule (against the tournament template), the actual tournaments that you've created/scheduled will be shown in the "Tournament schedule" (the calendar icon, second from the top in the left hand menu bar):
Once a tournament has been set up, changes to template settings won't affect individual instances of tournaments shown in this screen. To make changes to a tournament shown here, simply click on the tournament and you will be able to check on progress and also make changes to tournaments that are due to happen at a future date/time (i.e. change the format, extend the invitee list, change entry requirements etc.).
To review your overall tournament performance for a season, you can visit the dashboard screen (graph icon at the top of the left hand menu bar).
You'll quickly get a sense of how popular different tournament formats are and you can use this information to further refine the tournaments and rewards that you offer to your players.
Dashboard Glossary
Dashboard (Line Chart icon)
The Dashboard screen shows you all the relevant metrics and key performance indicators for the tournaments that you've run. Initially, as you haven't used the platform to run any tournaments yet, these screens will be empty - as follows:
Tournament Schedule (Calendar Icon)
The Tournament Schedule screen will show you details of all tournaments that you have run or that are scheduled to run. Initially, as you haven't used the platform to run any tournaments yet, these screens will be empty - as follows:
When you've created tournaments, you will be able to click on the dates to set the date range (as shown above). When individual tournaments are shown here, you'll be able to review historical tournaments and also make changes to individual instances of tournaments that are yet to happen.
Tournament Templates (Trophy icon)
This section allows you to create tournament templates for a tournament series specifying all the relevant rules, formats, entry criteria and prizes. A schedule can then be built based on a template which will automatically create and run the required tournaments. The set up options are described below:
General
This section allows you to decide how many and which players will be invited to this type of tournament.
Generals
Template Name
Give this tournament template a name - this can be anything.
Type
Select a type for the tournament. This will help you to organize your templates - you will be able to see an indicator for the type of tournament when viewing your list of tournament templates. There are four types:
- Public - For when you want anyone to be able to enter the tournament
- Premium - For when you want players to attain/own something before entering
- Private - For when you want participation to be by invite only
- Testing - For when you want to try out new things (it is recommended that you create a test tournament initially. You can always change/update the type late once you're happy with what you've set up)
N.B.: The rules for tournament availability are configured separately, this is just an indicator that you/others will be able to see whilst maintaining your templates.
Registration
Registration opens
Select how long before a tournament you would like players to be able to register for it. There are eight, self-explanatory options:
- immediately
- 2 weeks before start
- 1 week before start
- 2 days before start
- 12 hours before start
- 2 hours before start
- 1 hour before start
You will want to allow longer periods of time for invitation-only or special/important/high value tournaments. Public tournaments should have shorter periods of time as players are more likely to participate on a casual/ad hoc basis and will be registering right up until the tournament starts.
Registration closes
Select how long before a tournament you would like to close registration (i.e. stop new players from registering). There are six, self-explanatory options:
- 2 minutes before start
- 2 hours before start
- 12 hours before start
- 1 days before start
- 2 days before start
- 1 week before start
You will want to allow longer periods of time invitation-only or special/important/high value tournaments. Public tournaments should have shorter periods of time as players are more likely to participate on a casual/ad hoc basis and will be registering right up until the tournament starts.
N.B.: You will need to make sure your registration open and close settings work together (i.e. don't set registration to close before it opens).
Maximum players
Specify the maximum number of players that you want to participate in this type of tournament. You would tend to specify lower numbers (i.e. 32, 64, 128, 256) for special/important/high value tournaments or tournaments that you plan to stream as this keeps them elite and also ensures that they'll be finished in a manageable time.
Don't worry if you don't meet your maximum number of players or if some players don't turn up to play, the Tournament SDK will deal with this for you.
The maximum number of players may also be influenced by your chosen tournament format.
Party (Team) size
Specify how many players will be on each team. This will be determined by the type of game you're creating a tournament for.
N.B.: The maximum number of players in the tournament has to be divisible by the party size (i.e. you can't have teams of two players and then only allow an odd number of players to register for the tournament).
Registration rules
Specify who will be invited to this type of tournament. You can specify multiple registration rules with different registration timelines so you can do things like: initially inviting a select group of players and then opening up a tournament to general player registration at a later date/time.
The options are as follows:
Open to everyone
Make the tournament available to all your players with no restrictions.
When: You must specify when players of this type will be able to see the tournament to register. There are seven, self-explanatory options:
- Immediately
- 1 hour after registration opens
- 2 hours after registration opens
- 12 hours after registration opens
- 1 day after registration opens
- 2 days after registration opens
- 1 week after registration opens
Direct Invite
Invite a specific list of players.
When: You must specify when players of this type will be able to see the tournament to register. The selectable options are the same as those listed above.
Player list: You will add a list of players to send the invite to by clicking the "add user" button then searching for the players that you want to add.
Private Code
Invite only players who knows private code.
When: You must specify when players of this type will be able to see the tournament to register. The selectable options are the same as those listed above.
Private Code: Will be generated when tournament is scheduled. If tournament is public then player has to register with private code. This can be distributed e.g. via social platforms for specific campaign or via influencer.
In case tournament is private and player does not see the tournament in his game client then player still can register with private code. Tournament will become available to him. This can be used when organizing private competition. NOTE: When combined with direct invite option player who receives the invite will have also private code available in there game client. This way they can share code further with desired audience.
The best from tournaments
Make the tournament available to players who have been successful in previous tournaments (i.e. encourage/incent loyal players to keep playing by giving them early entry or just create a champion of champions series).
When: You must specify when players of this type will be able to see the tournament to register. The selectable options are the same as those listed above.
Number of Invites: How many invites of this type will be sent (i.e. the maximum number of invites of this type that will be sent)
Invite based on item [ID]: You can send invites to players who have won a particular item (i.e. something (items, currency, points etc.) won through success in previous tournaments). When the system is deciding which players to send invites to it will look for players with this item - this does not have to be something that is awarded in your game, it can be something that is only set up in the tournament platform (see "Store" settings), for example a virtual tournament chip/coin/ticket.
Add Tournaments: Search for tournaments within a date range and add the specific tournaments that you want to invite players from.
The best score from [Number] of results: This will prioritize invites to players who have the best scores across the given number of tournaments (i.e. if this is set to 2, it will take each players top 2 results, regardless of how many tournaments they have played in). This should incent players to play in more tournaments to improve their top n. scores.
The best from series
Make the tournament available to players who have been successful in particular tournament series (i.e. tournaments that were created from the same tournament template).
When: You must specify when players of this type will be able to see the tournament to register. The selectable options are the same as those listed above.
Number of Invites: How many invites of this type will be sent (i.e. the maximum number of invites of this type that will be sent)
Invite based on item [ID]: You can send invites to players who have won a particular item (i.e. something (items, currency, points etc.) won through success in previous tournaments). When the system is deciding which players to send invites to it will look for players with this item - this does not have to be something that is awarded in your game, it can be something that is only set up in the tournament platform (see "Store" settings), for example a virtual tournament chip/coin/ticket.
Add Tournaments: Search for tournaments within a date range and add the specific tournaments that you want to invite players from.
The best score from [Number] of results: This will prioritize invites to players who have the best scores across the given number of tournaments (i.e. if this is set to 2, it will take each players top 2 results, regardless of how many tournaments they have played in). This is particularly relevant across a tournament series (i.e. a recurring tournament that you might run at the same time every day). This should incent players to play in more tournaments to improve their top n. scores.
Enabled/Disable
As you enable each of these registration rule options, you will see the text "enabled" appear in green. To disable any of the registration rules, move your mouse pointer over the text "enabled" and you will see it change to red text that says "disable". Click on this to disable/remove a registration rule.
Actions - Delete
Don't press this unless you have a really good reason to as it will permanently delete your template!
Description
This section allows you specify information about your tournament that will be displayed to players/invitees.
Language
Select the language from the available list and click the + button. Do this for each language that you want to display tournament information in. All of this will be available to your game through the SDK - remember that you will probably have multiple tournaments available to players at any one time so these settings will help to differentiate/advertise the tournament.
The following settings will be available for each language:
Language
Even though you've already selected this, it can be edited here as well.
Tournament Name
Specify the name of the tournament. This should be something cool.
Image URL
Provide the URL of the image that will be shown to players to represent this tournament
Icon URL
Provide the URL of the icon that will be displayed to players for this tournament.
Theme color
Specify a theme color that will be used. This color can be picked up through the in game SDK and used to change the visuals/highlight items in your game UI for this tournament.
Description
Provide a brief description of the tournament that will be shown to players.
Description 2
Provide some additional information about the tournament.
Enabled/Disable
As you enable each required language, you will see the text "enabled" appear in green. To disable any of the languages, move your mouse pointer over the text "enabled" and you will see it change to red text that says "disable". Click on this to disable/remove language information.
Format
This section allows you to define the format of the tournament. Tournaments will consist of one or more phases, each phase will have multiple rounds (each round will have one match for each player/team), each match can be made up of multiple games (i.e. best of 3). The platform will narrow the field of players/teams down until there is a winner/champion. The possible types of phase are as follows:
-
Arena - It is best to start your tournament with an arena phase if you:
- have a large number of players competing in a tournament
- want to ensure that the best players make it through to the final stages
- want to guarantee that players get to play a certain number of matches rather than being knocked out immediately
In an arena phase players/teams will be automatically matched against each other over a fixed number of rounds during which no players/teams will be eliminated (all teams are guaranteed to play in each round). Players/teams are matched against players of a similar skill level and will accumulate a score based on their performances and strength of competition. At the end of an arena you can either declare a winner/winners, or allow the best players to advance to another phase (either arena or bracket)
-
Bracket - A bracket phase matches players/teams in a single elimination (i.e. lose and you're out) knockout format, building every participant a clear/visible route to the final (through quarter finals, semi finals etc.). You can't have any further phases after a bracket (either bracket or arena) as a bracket phase will always give you a final match and a single winner. As per the above section on Registration rules, you can automatically invite winners to future tournaments.
Phase Type
Click on "+Add Phase" and select either Arena or Bracket.
Teams
Teams
Specify the number of teams that will be in this phase.
Min teams per match
Specify the minimum number of teams that will play in a match. You'll need to set this (as appropriate) for your type of game.
Max teams per match
Specify the maximum number of teams that can play in a match. You'll need to set this (as appropriate) for your type of game (and may be the same as the minimum number)
Scores
Max Losses (arena only)
Specify the maximum number of losses a player/team can have (i.e. if you lose more than this number of games then you're automatically out). In team games, it is possible to have multiple winners so a "loss" is any game when a player doesn't score any match points. In a bracket phase "max losses" its pre-set to "1" (i.e. lose and you're out) so this setting is not shown.
Advanced settings
Click on the "show" advanced settings" link to show these. By default, these settings are automatically set.
The settings define how to award points to players who finish in certain positions within a game and/or match, you might want to alter these if each game/match has large numbers of players.
Game point distribution:
This is specified as a comma delimited list. In a head to head game, this value will be set to "1" i.e 1 point is awarded to the winner of a game. In a more complex scenario (i,.e. battle royale type game) you may choose to give 5 points to the winner of a game, 2 points to the runner up, and 1 point each to the 3rd-5th placed finishers. This would be specified as follows: 5,2,1,1,1
Match point distribution:
When all the games in a match have been played, the platform will rank players based on the number of points awarded across all games. Match points will then be awarded according to the ranked order (i.e. anyone who is categorized as a "winner" will get at least one match point so, if your top 3 players are "winners", you might specify the match point distribution as: 3,2,1). The awarding of match points will determine if players progress to future rounds and, in an arena, who they are matched against.
Format
Add in as many rounds as you require for this phase of the tournament. If this is a Bracket phase, the correct number of rounds will be added for you based on the number of teams/players and the team size. For each round you can then specify additional characteristics.
Type
You must select how each round (match) will be decided. There are options for Best Of (BO) 1-5 (i.e. how many games should the players/teams play against each other to determine the winner of their match). There is also an option for "Custom". If you select this item you must also enter:
- Score to Win - the score that a player/team requires to win the match
- Maximum number of games - the maximum number of games that the teams can play
Minimum game time (minutes)
Enter the minimum amount of time that a game will take to be played. This setting helps to ensure that players will get to play all of the games in an individual round. I.e. if the first game in a best of 3 lasts for a long time, messages can be sent through SDK to kill/end the first game to allow time for subsequent, minimum length, games to be played.
Maximum round time (minutes)
To ensure the tournament is over in a manageable time, specify the maximum duration for each round. This must be more than the sum of the minimum times of the games in the round. When you're setting this you should think about the number of games in a round, the minimum (and average) length of a game and then think about (potentially) adding an appropriate safety margin (i.e. in your game, a typical game play session might last between 1 and 3(ish) minutes). You would specify the minimum game time as 1. Then, if your round/match was the best of 3 games, you might specify the maximum round time as 9 minutes (2 mins average game time * 3 games + 3 minutes buffer/safety margin).
Remember that all matches in a round must be complete before the next round can begin so you may have some players who complete their match early and then have time when they are waiting for their next match to begin. Whilst there is no exact science, it's important to consider this carefully and set an appropriate limit.
Results
Resolve phase tiebreakers
Phase tiebreakers are always resolved. At the end of each phase players are sorted according to these stats:
- Points - higher is better - accumulated from winning match points (rounds)
- Match wins - higher is better - accumulated from winning matches (rounds)
- Match loses - lower is better - accumulated from losing matches (rounds)
- Game wins - higher is better - accumulated from winning games (in each match)
- Game loses - lower is better - accumulated from losing games (in each match)
- Tiebreaker stat 1 (optional) - accumulated from played games
- Tiebreaker stat 2 (optional) - accumulated from played games
- Phase seed - lower is better - final position from previous phase
- Lose weight - lower is better - accumulated from losing matches and weight increases with each round
You may specify two custom tiebreaker stats to be used to break the tie. For example (depending on your game): most kills, furthest distance travelled, total goals scored etc. This must be passed by the game through the SDK, statistics will be set up in "Game settings, Statistics" section (see below).
Resolve match tiebreakers
Tick this box if you want to resolve match tiebreakers. This is only used in scenarios where you set up a round to be the "Best of" an even number of games - in this scenario, you may specify another statistic to be used to break the tie. For example (depending on your game): most kills, furthest distance travelled, total goals scored etc. This must be passed by the game through the SDK, statistics will be set up in "Game settings, Statistics" section (see below).
In case of a tiebreaker the match points are split between all teams that occupy tiebreaker positions. For example if 2 teams finish with same amount of game points in a match and they tiebreak for 2 match points then the points are split equally between them so each gets 1 match point.
N.B.: You should always resolve tiebreakers in a bracket phase or you run the risk of a random player being put through to the next round.
Entry Fee
This section allows you to set tournament entry fees (if any). These can be in-game currency or owned items. You an also choose whether, or not, you want these to be added to a prize pool to be shared amongst the winners.
Drag and drop the items/currencies from the right hand menu. These items will be deducted from each players account when they register for the tournament.
Rewards
This section allows you to set up rewards for the tournament winners. The rewards will be automatically distributed when the tournament ends.
Click on the "Add rule" button (do this as many times as you need to set up rewards for different placed players)
Place
Specify the place that this reward applies to (i.e. first place only, 2-4th place, 5th-10th place etc)
Shared pot
Add the % of the shared pot that this place finisher will win
Drag and drop items here
Items and currencies can be dragged from the right hand menu. You can drag as many as you want for each place.
Add description
- Describe - add a text description of your rewards
- Image - add an image that shows the rewards will/have been
The rewards can be shown to players registering or reviewing the results of past tournaments.
Requirements
This section allows you to specify entry requirements for the tournament (i.e. a player must've attained a certain rank or must possess a certain item to participate). You can enter your own custom requirements (i.e. ones that are relevant to your game) as key-value pairs. When players are signing up to the tournament, these values will be passed to server and to check if the player has met the requirements to register or not.
Add requirement
Click on the "+Add requirement" button and add as many requirements as necessary. These are configured in the format:
Requirement
The name of the requirement (i.e. Player rank).
Requirement Value
The vale of the requirement (i.e. 10).
Administrators
This section allows you to add players as tournament administrators. An administrator is a tournament player who has access to all the matches in a tournament whereas any normal player only has access to the matches that they are participating in. If your game has a spectator mode, this would allow an administrator to join any match. This is might be especially useful for streaming the tournaments as an administrator can join and view any match within the game client.
Custom properties
This section allows you to specify custom properties for the tournament. These can be anything relevant to your game and related to a tournament and will be passed to the game through the SDK (i.e. what map(s) will be used, what rules will be used, what spells/weapons will be used/banned etc.). This can also be used to add a stream link for a particular tournament so you can have a "watch now on twitch" button inside your game.
Add custom property
Click on the "+Add custom property" button and add as many custom properties as necessary. These are configured as key-value pairs.
Webhooks
This section allows you to set up webhooks to automatically send information/notifications about key tournament-related events to other platforms (i.e. Discord etc.). There are several events that can each be configured to make multiple calls:
Tournament registration opens
Executed when registrations are opened
Tournament registration closes
Executed when registrations are closed
Tournament start
Executed when tournament is ready to start.
Tournament end
Executed after tournament is finished (prizes are distributed)
Tournament is next
Executed when tournament is next, will be triggered multiple times (Does not trigger on private tournaments)
Callback
For each of the above events, multiple callbacks can be configured by selecting the event and clicking the "+New callback" button. The following parameters will be specified for each callback - if you're not sure about how to set this up, ask your dev team who will be able to help.
Callback Name
Give this webhook call a name.
Retries
Specify the number of times that this call should be re-attempted if it fails.
Method
Select one of: POST, GET, PUT, DELETE - depending on which option is required.
URL
Specify the URL that will be called.
Headers
Click on the "+Add Header" button to enter additional headers as required. These will be specified as Key-Value pairs.
Parameters
Click on the "+Add Parameter" button to enter additional parameters as required. These will be specified as Key-Value pairs.
Store (Shopping bag icon)
The Store section allows you to add currencies and items that can be used in the Rewards and Entry fee sections of tournament template setup. You will typically import game items and currencies from your servers (by setting up integration in game settings). These settings will allow you to add items and currencies manually if you don't import from your server. The "currencies" here can also be used as a custom, in-tournament points system so you can award points that are not related to your game currency.
Game Settings (Game Controller icon)
General settings
This section contains general settings related to your game.
Game Name
The name of the game for which you are creating tournaments.
Game Icon URL
URL to render your game's icon in the tournament dashboard.
Version Number
The version of the tournament dashboard that you're currently running.
Client Game ID
Unique, system generated ID for your game within the tournament system.
Seasons
This section allows you to set up tournament seasons. You can either set up season recurrence or create new seasons manually. Seasons (and parts within seasons) will help you to manage the tournaments that you run, creating different themes etc. Game/player stats are also aggregated by season.
Options in a drop down 1 month to 24 months.
Part start day
Options in drop down for days of the week (and default).
Part Length (Days)
Options in a drop down: 1 week, 2 weeks, 1 month
Button to "Create Season".
Tournament settings integration
This section allows you to set up webhooks for:
External signup
Method (post), URL, optional Headers, optional Parameters.
External prize delivery
Method (post), URL, optional Headers, optional Parameters.
Theses are for notifying other servers (e.g. your game servers) of these events.
Authentication providers
This section will allow you to set up authentication to allow players to be logged in to tournaments.
The options available are: Custom, Email, Anonymous, Steam, Apple, Google Play. Click on the appropriate icon and enter the required details as appropriate to your game to allow players to be authenticated.
Store integration
This section allows you to set up a webhook to pull in items from your in-game store/servers. This can then be used to set tournament requirements and award prizes etc.
To create the "External Store Hookup" enter the required parameter and the click the "Sync data now" button.
N.B.: you will need to press this button again if you add new items to your game and they're not showing up in the tournament dashboard.
Statistics
This section allows you to set up statistics that will be passed from your game to the tournament platform. As well as being used for potential tie-break situations, these are also, automatically, aggregated for each player for each season and (through the SDK) can be pulled back into the game and shown to players, or on leader boards, or against tournament results etc. (i.e. depending on your game type you could show: what abilities/weapons the top players used to win tournaments, how many kills they had in tournaments, how many goals they scored etc.).
Game statistics aggregation
To create the season profile for a player specify:
Game session
What game types do you want to pull down stats for (array of ids for your game types (comma separated e.g. 1v1, 2v2)).
Last matches
What game types do you want to pull down stats for (array of ids for game types (comma separated e.g. 1v1, 2v2)).
Value aggregation
Specify which stats that will be aggregated as values. You will automatically get; sum, average, minimum, maximum for the season (returned to the client through the SDK, i.e. game time/length).
Count aggregation
Specify which stats that will be aggregated as a "count of" items. This will count the occurrences of an item (rather than summing an ID) and also how many times you win/lose etc. with this item (i.e. a weapon was used n. times; n. wins, n. losses).
Custom properties
This section allows you to define other, global custom parameters (if any) that needs to be passed to the game through the SDK e.g. on which servers can the tournaments be played etc.
These are added by clicking the "Add custom properties" button. These are added as key-value pairs.
Cog icon (left, bottom of screen)
Roles
Clicking on "Roles" allows you to add new users to the tournament dashboard. You do this by clicking the "+ Add user" button and entering the user's; First name, Last name, E-mail, and User type (either User or Admin).
Once a user is created, you can give them permissions (per game that you have set up in the Tournament dashboard) to be able to edit/not edit; Tournaments, Game Settings, Manage Store, Manage Players.
The user will be sent an email with an authentication link. They will need to click on this link and create a password to login.
My Account
Clicking on "My account" allows you to edit user profile details (i.e. change your name or email address or, if your're an administrator, your own system access rights).
Logout
This allows you to sign out of the Tournament dashboard.
- Dashboard (Line Chart icon)
- Tournament Schedule (Calendar Icon)
- Tournament Templates (Trophy icon)
- Store (Shopping bag icon)
- Game Settings (Game Controller icon)
- Cog icon (left, bottom of screen)
Backend Integration
Custom asset server integration
It is possible to load items/currencies from a custom asset server and use them as tournament rewards or entry fees. Also, any sign up can be routed through a custom server that can deduct those items or reject tournament signup (e.g. user does not have enough currency).
Login with custom server
In order to authenticate login via custom server an HTTP endpoint has to be created that returns user basic info in a specific, JSON format. You have to do this if you want to have your user IDs associated with tournament account. Associated user IDs will then be provided in other custom server integration calls (e.g. reporting tournament results to custom server).
Data passed from the client for user authetication will be attached as application/x-www-form-urlencoded
.
Send data from client that are needed for user authentication (e.g. clinet token).
// coroutine
// ...
// use custom external login provider, pass custom data from your backend to authenticate user
var loginOperation = BackboneManager.Client.Login(LoginProvider.CustomExternal(
true,
MyBackend.Username,
new KeyValuePair<string, object>("clientToken", MyBackend.Token),
new KeyValuePair<string, object>("userId", MyBackend.Id)));
yield return loginOperation;
Check passed data from client and determine if user is valid. You have to respond with JSON format that contains your user ID and user nick.
{
"id": "123",
"nickName": "nick"
}
The endpoint also has to respond with status code 200.
Once there is a endpoint that returns the required format, navigate to Game Settings/Authentication providers
and enable custom authentication
.
Insert a valid HTTP path and add any necessary headers/parameters for authentication required by your server.
Loading store items from server
In order to load items and currencies from the custom server an HTTP endpoint has to be created that returns items in a specific, JSON format. You have to do this to make sure that the server loads the items for internal use and marks those items/currencies as "loaded" from the external resource.
JSON format:
{
items:[
{
id:"itemId1",
name:"itemName1",
image:"imageUrl (optional)"
},
{
id:"itemId2",
name:"itemName2",
image:"imageUrl (optional)"
}],
currencies:[
{
id:"currencyId1",
name:"currencyName1"
},
{
id:"currencyId2",
name:"currencyName2"
}]
}
The endpoint also has to respond with status code 200. Always return all items you wish to import. Items/currencies that already exist will be updated.
Once there is a endpoint that returns the required format, navigate to Game Settings/Store Integration
and enable "external store hookup".
Select Custom URL
provider.
Insert a valid HTTP path and add any necessary headers/parameters for authentication required by your server.
Click on the Sync data now
button which will import your items/currencies.
You should now be able to access imported items when you are editing your tournament template rewards and entry fees.
If there are any updates to your assets just import them again by clicking on the Sync data now
button.
Signup via custom server
In order to route tournament signups through a custom server, an HTTP endpoint has to be created that accepts a specific, JSON format. On every user signup, a call will be made to a custom server with a payload containing information about the tournament, user, entry fees, etc.
The HTTP call is POST with a parameter called userTicket containing the JSON payload.
JSON format:
{
isSignup:true,
isSignout:false,
userId:"64bit number as string",
userExternalId:"string",
ticketId:"64bit number as string",
tournamentId:"64bit number as string",
customRequirements:[{
name:"string",
value:"string"
}],
fees:{
items:[{
id:"64bit number as string",
externalId:"string",
amount:1
}],
currencies:[{
id:"64bit number as string",
externalId:"string",
amount:1
}]
}
}
A successful response is then awaited with HTTP status code 200 (or another code in case of rejection).
Once there is a endpoint that accepts the required JSON format navigate to: Game Settings/Tournament settings Integration
and enable "external signup".
Insert a valid HTTP path and add any necessary headers/parameters for authentication required by your server.
After this setup, any signup from a client should be routed through the custom server before it's accepted and confirmed.
Tournament result report to custom server
To receive tournament results via a custom server, an HTTP endpoint has to be created that accepts a specific, JSON format. After every tournament finishes, a call will be made to a custom server with a payload containing information about the tournament, users and their prizes, etc.
The HTTP call is POST with a parameter called jsonPayload containing JSON payload.
JSON format:
{
"tournamentId":"64bit number as string",
"prizes":[{
"ticketId":"64bit number as string",
"userId":"64bit number as string",
"userExternalId":"string",
"place":1,
"items":[{
"id":"64bit number as string",
"amount":1,
"externalId":"string"}],
"currencies":[{
"id":"64bit number as string",
"amount":1,
"externalId":"string"}]
}]
}
A successful response is then awaited with HTTP status code 200. In case of failure, it will be repeated several times.
Once there is an endpoint that accepts the required JSON format, navigate to: Game Settings/Tournament settings Integration
and enable "external prize delivery".
Insert a valid HTTP path and add any necessary headers/parameters for authentication required by your server.
PlayFab asset server integration
It is possible to load items/currencies from a PlayFab backend and use them as tournament rewards or entry fees. Also, any sign up can be routed through a custom cloud script that can deduct those items or reject tournament signup (e.g. user does not have enough currency).
Create PlayFab secret key
For PlayFab integration you will need TitleId and SecretKey. Login to your playfab dashboard, select your desired game and open settings screen.
You can find TitleId in API Features
tab. You can create a new SecretKey in Secret Key
tab.
Do not set expiration date for secret key as that could cause outtage for your game in case you forget to set new one in tournament dashboard.
Login with PlayFab provider
If you integrating with PlayFab services we strongly recommend enabling only PlayFab login provider in tournament dashboard. Also NOTE that if you will not use PlayFab login provider other integration features such as tournament signup or delivering prizes via cloudscript fail to work.
Enter here TitleId & SecretKey and save changes.
In your game client, after you initialize and login with playfab you will get a session ticket. This session ticket is then passed to playfab login provider to authenticate user and login to tournaments. This process will ensure that system has correct and valid playfab id associated with user account.
string playfabId;
string playfabSessionTicket;
bool isPlayfabLoginFinished;
// coroutine
// ...
// login playfab client
PlayFab.PlayFabClientAPI.LoginWithCustomID(
new PlayFab.ClientModels.LoginWithCustomIDRequest() {
CreateAccount = true,
CustomId = "customPlayfabId"
},
(result) =>
{
isPlayfabLoginFinished = true;
// get playfab user id and session ticket (we will use it for authentication)
playfabId = result.PlayFabId;
playfabSessionTicket = result.SessionTicket;
},
(error) =>
{
isPlayfabLoginFinished = true;
});
// wait until playfab login process finishes
yield return new WaitUntil(() => isPlayfabLoginFinished);
// use playfab login provider, pass playfab id and session ticket
yield return BackboneManager.Client.Login(
Gimmebreak.Backbone.User.LoginProvider.Playfab(
true,
"NickName-" + playfabId,
playfabSessionTicket,
playfabId));
To get associated playfab id with user account call BackboneManager.Client.User.GetLoginId(LoginProvider.Platform.Playfab)
.
This can be used to compare if logged playfab client matches with logged tournament client.
Loading store items from Playfab
Navigate to Game Settings/Store Integration
and enable "external store hookup".
Select Playfab
provider.
Insert a valid title id & secret key and save chages.
Click on the Sync data now
button which will import your items/currencies (From primary catalogue).
You should now be able to access imported items when you are editing your tournament template rewards and entry fees.
If there are any updates to your assets just import them again by clicking on the Sync data now
button.
Signup via Playfab CloudScript
In order to route tournament signups through a Playfab CloudScript, an function has to be created that accepts a specific, JSON format. On every user signup, a call will be made to this cloudscript funtion with a payload containing information about the tournament, user, entry fees, etc.
The CloudScript function parameter args will contain the JSON payload.
JSON format:
{
serverSecret:"string",
isSignup:true,
isSignout:false,
userId:"64bit number as string",
userPlayfablId:"string",
ticketId:"64bit number as string",
tournamentId:"64bit number as string",
customRequirements:[{
name:"string",
value:"string"
}],
fees:{
items:[{
id:"64bit number as string",
externalId:"string",
amount:1
}],
currencies:[{
id:"64bit number as string",
externalId:"string",
amount:1
}]
}
}
Here is an example CloudScript function that handles signup/signout:
// This is a example tournament signup/signout handler. If signup is requested
// check if user can sign up and deduct any required fees. If signout is requested
// return any deducted fees back to users account.
handlers.tournamentSignup = function(args, context){
if (args.serverSecret == "secret") {
if (args.isSignup) {
// Check any custom requirement for the tournament. This can be
// e.g. having required minimum rank. If player does not meet
// specified criteria, the signup should be rejected.
for (var i = 0; i < args.customRequirements.length; i++) {
var name = args.customRequirements[i].name;
var value = args.customRequirements[i].value;
if (name == "testRequirement" &&
value == "reject") {
return { success: false };
}
}
// Check if user has required signup fees.
if (args.fees.items.length > 0 ||
args.fees.currencies.length > 0) {
// Get user inventory
var userInvetoryResult = server.GetUserInventory({PlayFabId: currentPlayerId});
// Check if user has enough currency
for (var i = 0; i < args.fees.currencies.length; i++) {
var currencyFee = args.fees.currencies[i];
var userCurrency = userInvetoryResult.VirtualCurrency[currencyFee.externalId];
if (!userCurrency ||
userCurrency < currencyFee.amount) {
// User does not have required currency or amount
return { success: false };
}
}
// Sort user invetory items by id
var userInventory = {};
for (var i = 0; i < userInvetoryResult.Inventory.length; i++) {
var item = userInvetoryResult.Inventory[i];
if (!userInventory[item.ItemId]) {
userInventory[item.ItemId] = [];
}
userInventory[item.ItemId].push(item);
}
// Check if user has enough items
for (var i = 0; i < args.fees.items.length; i++) {
var itemFee = args.fees.items[i];
var userItems = userInventory[itemFee.externalId];
if (!userItems ||
userItems.length < itemFee.amount) {
// User does not have required item or amount
return { success: false };
}
}
// Substract user's currencies
for (var i = 0; i < args.fees.currencies.length; i++) {
var currencyFee = args.fees.currencies[i];
server.SubtractUserVirtualCurrency({PlayFabId: currentPlayerId, VirtualCurrency: currencyFee.externalId, Amount: currencyFee.amount });
}
// Revoke user's items
var revokedItems = { Items: [] };
for (var i = 0; i < args.fees.items.length; i++) {
var itemFee = args.fees.items[i];
for (var p = 0; p < itemFee.amount; p++) {
revokedItems.Items.push({PlayFabId: currentPlayerId, ItemInstanceId: userInventory[itemFee.externalId][p].ItemInstanceId});
// Maximum 25 items can be removed at once (Playfab documentation)
// If container is filled with 25 items, revoke and continue
if (revokedItems.Items.length == 25) {
server.RevokeInventoryItems(revokedItems);
revokedItems.Items = [];
}
}
}
// Check if any items should be revoked (last 25 items)
if (revokedItems.Items.length > 0) {
server.RevokeInventoryItems(revokedItems);
}
// All fees deducted, confirm tournament signup
return { success: true };
}
else {
// No fees to deduct, confirm tournament signup
return { success: true };
}
}
if (args.isSignout) {
// Return user's currencies
for (var i = 0; i < args.fees.currencies.length; i++) {
var currencyFee = args.fees.currencies[i];
server.AddUserVirtualCurrency({PlayFabId: currentPlayerId, VirtualCurrency: currencyFee.externalId, Amount: currencyFee.amount });
}
// Return user's items
var returnItems = { PlayFabId: currentPlayerId, ItemIds: [], Annotation: "Returned tournament fee items. TournamentId: " + args.tournamentId };
for (var i = 0; i < args.fees.items.length; i++) {
var itemFee = args.fees.items[i];
for (var p = 0; p < itemFee.amount; p++) {
returnItems.ItemIds.push(itemFee.externalId);
}
}
// Check if any items should be returned
if (returnItems.ItemIds.length > 0) {
server.GrantItemsToUser(returnItems);
}
// User fees has been returned
return { success: true };
}
}
return { success: false };
};
A successful response is then awaited in form { success: true } (or anything else in case of rejection).
Once there is a CloudScript function that accepts the required JSON format navigate to: Game Settings/Tournament settings Integration
and enable "external signup".
Select Playfab
provider.
Insert a valid title id, secret key, CloudScript function name, function version (revision number) and save changes.
There is pre-generated server secret that you can change if you want to. Use this secret in cloud script to prevent playfab user clients to execute it.
After this setup, any signup from a client should be routed through the cloud script before it's accepted and confirmed.
Tournament result via Playfab CloudScript
To receive tournament results via a Playfab CloudScript, an function has to be created that accepts a specific, JSON format. After every tournament finishes, a call will be made to this cloudscript funtion with a payload containing information about the tournament, users and their prizes, etc.
The CloudScript function parameter args will contain the JSON payload.
JSON format:
{
"serverSecret":"string",
"tournamentId":"64bit number as string",
"prizes":[{
"ticketId":"64bit number as string",
"userId":"64bit number as string",
"userPlayfabId":"string",
"place":1,
"items":[{
"id":"64bit number as string",
"amount":1,
"externalId":"string"}],
"currencies":[{
"id":"64bit number as string",
"amount":1,
"externalId":"string"}]
}]
}
Here is an example CloudScript function that handles prize delivery:
// This is example tournament prize delivery handler. Once tournament is finished
// all results will be provided togather with information which items and currencies
// should be granted to user.
handlers.tournamentPrizeDelivery = function(args, context){
if (args.serverSecret == "secret" &&
args.prizes) {
for (var i = 0; i < args.prizes.length; i++) {
var prize = args.prizes[i];
// Grant user currency prizes
for (var p = 0; p < prize.currencies.length; p++) {
var wonCurrency = {
PlayFabId: prize.userPlayfabId,
VirtualCurrency: prize.currencies[p].externalId,
Amount: prize.currencies[p].amount
};
server.AddUserVirtualCurrency(wonCurrency);
}
// Grant user item prizes
var wonItems = {
PlayFabId: prize.userPlayfabId,
ItemIds: [],
Annotation: "Won items in tournament. Place: " + prize.place + " TournamentId: " + args.tournamentId
};
for (var p = 0; p < prize.items.length; p++) {
var itemPrize = prize.items[p];
for (var c = 0; c < itemPrize.amount; c++) {
wonItems.ItemIds.push(itemPrize.externalId);
}
}
if (wonItems.ItemIds.length > 0) {
server.GrantItemsToUser(wonItems);
}
}
}
};
A successful response is then awaited with HTTP status code 200. In case of failure, it will be repeated several times.
Once there is an endpoint that accepts the required JSON format, navigate to: Game Settings/Tournament settings Integration
and enable "external prize delivery".
Select Playfab
provider.
Insert a valid title id, secret key, CloudScript function name, function version (revision number) and save changes.
There is pre-generated server secret that you can change if you want to. Use this secret in cloud script to prevent playfab user clients to execute it.
Game server integration
Submit game session result from custom server
In order to process results from external server first navigate to: Game Settings/Tournament settings Integration
and enable "external result submission".
To submit game session result from dedicated server or custom backed a specific HTTP call has to be made.
The HTTP call is POST and the format has to be application/x-www-form-urlencoded
.
WARNING You have to request dedicated SERVER ACCESS TOKEN that is meant only for your servers and must not be included in you game clients
Endpoint URL:
https://backbone-client-api.azurewebsites.net/api/v1/gameSessionSetResultFromServer
Headers:
BACKBONE_APP_ID: YOUR-TSDK-GAME-CLIENT-ID
ACCESS_TOKEN: YOU-WILL-BE-GIVEN-SERVER-ACCESS-TOKEN
Content-Type: application/x-www-form-urlencoded
application/x-www-form-urlencoded parameters:
gameSessionData
gameSessionData
value has to be JSON string (do not include comments):
{
// this is 64 bit number as string, id is provided from create game session API call,
"gameSessionId":"491197014055328536",
// type of game session is anything you want between 0-127, default is 0
// e.g. 0 - 1v1 1 - 2v2 etc you can give certain type to your games
"type":"0",
// this is 64 bit number as string, id is provided from match object e.g. match.Id
// NB: NOT match.MatchId that is something different
"tournamentMatchId":"491196930559318805",
// time in seconds your game session lasted
"time":"100",
// list of users and their respective placements
"users":[
{
"teamId":"1",
"userId":"491174601636715209",
"place":"2"
},
{
"teamId":"2",
"userId":"491174351375178434",
"place":"1"
}
],
// game session stats e.g. what type of map was played (optional)
"gameStats":[
{
"statId":"2",
"textValue":"test"
}
],
// user game session stats, e.g. how much damage specific user did (optional)
"userStats":[
{
"statId":"1",
"userId":"491174351375178434",
"floatValue":"134.234"
},
{
"statId":"1",
"userId":"491174601636715209",
"floatValue":"12.234"
}
]
}
Get match data from server
Query tournament match data from game server.
Endpoint URL:
https://backbone-client-api.azurewebsites.net/api/v1/tournamentGetMatchDataForServer
Headers:
BACKBONE_APP_ID: YOUR-TSDK-GAME-CLIENT-ID
Accept-Encoding: gzip
Content-Type: application/x-www-form-urlencoded
application/x-www-form-urlencoded parameters:
accessToken
tournamentId
matchId
accessToken
value has to be SERVER ACCESS TOKENtournamentId
value has to be id of tournament match belongs tomatchId
value has to be id of specific tournament match
WARNING When match is still searching for oppenents not all users are present in the results. Once match starts then returned users can be considered as immutable and final.
Response:
{
"id": "620604471341234",
"secret": "lz59JQiTfdgsdfg",
"deadline": "2022-09-09T13:06:33.797Z",
"matchId": 1,
"phaseId": 1,
"groupId": 1,
"roundId": 1,
"playedGameCount": 1,
"maxGameCount": 1,
"status": 8,
"users": [
{
"userId": "6156293913241234",
"userExternalId": "XXXX",
"userPlayfabId": "XXXX",
"teamId": 1,
"checkedIn": false,
"userScore": 0,
"teamScore": 0,
"userPoints": 0,
"teamPoints": 0,
"matchPoints": 0,
"matchWinner": false
},
{
"userId": "6203549123412344",
"userExternalId": "XXXX",
"userPlayfabId": "XXXX",
"teamId": 2,
"checkedIn": true,
"userScore": 0,
"teamScore": 0,
"userPoints": 0,
"teamPoints": 0,
"matchPoints": 0,
"matchWinner": false
}
],
"roundSettings": [
{
"partySize": 1,
"maxTeamsPerMatch": 16,
"minGameTime": 3,
"maxRoundTime": 6
}
],
"propertySettings": [
{
"name": "property1",
"value": "value1"
},
{
"name": "property2",
"value": "value2"
}
],
"gameSessions": [
{
"gameSessionId": "6206051532412344"
}
]
}
Data reporting
Query tournament data reports
You can query past tournament data for custom analytics.
The HTTP calls are POST and the format has to be application/x-www-form-urlencoded
.
WARNING You have to request dedicated SERVER ACCESS TOKEN that is meant only for your servers and must not be included in you game clients
Tournaments
Query finished tournaments from specific date range.
Endpoint URL:
https://backbone-client-api.azurewebsites.net/api/v1/dataReportGetTournaments
Headers:
BACKBONE_APP_ID: YOUR-TSDK-GAME-CLIENT-ID
Accept-Encoding: gzip
Content-Type: application/x-www-form-urlencoded
application/x-www-form-urlencoded parameters:
accessToken
sinceDate
untilDate
accessToken
value has to be SERVER ACCESS TOKENsinceDate
value has to be UTC date and time in format 2022-10-29T00:00:00 (inclusive)untilDate
value has to be UTC date and time in format 2022-10-30T00:00:00 (inclusive)
Response:
[
{
// 64 bit number as string
"tournamentId": "63809769643125123",
// 64 bit number as string
"templateId": "63009307313241234",
// UTC datetime when tournament was created
"createdAt": "2022-10-27T19:32:26.063Z",
// Default name of the tournament
"name": "NameOfTheTournament 2v2",
// Type of the tournament
// 0 - public
// 1 - premium
// 2 - private
// 3 - testing
"type": 1,
// UTC datetime of tournament start
"startedAt": "2022-10-29T23:00:00.000Z",
// UTC datetime of tournament end
"finishedAt": "2022-10-30T04:15:49.420Z",
// User sign up count
// NOTE: excludes players who fail to form a team
"signedUpCount": 24563,
// Maximum number of users
"maxSignedUpCount": 30000,
// Required size of team/party
"partySize": 1,
// Count of tournament phases
"phaseCount": 4,
// Count of all tournament rounds across all phases
"roundCount": 34
}
]
Tournament participants
Query participants of specific tournament.
Endpoint URL:
https://backbone-client-api.azurewebsites.net/api/v1/dataReportGetTournamentUsers
Headers:
BACKBONE_APP_ID: YOUR-TSDK-GAME-CLIENT-ID
Accept-Encoding: gzip
Content-Type: application/x-www-form-urlencoded
application/x-www-form-urlencoded parameters:
accessToken
tournamentId
accessToken
value has to be SERVER ACCESS TOKENtournamentId
value has to be ID of specific tournament
Response:
[
{
// UTC datetime when user was added (ticket created)
"createdAt": "2022-10-25T15:00:35.447Z",
// 64 bit number as string
"tournamentId": "63592122754314523",
// 64 bit number as string
"ticketId": "63730450823452345234",
// 64 bit number as string
"userId": "410158954303241234",
// External system ID as string
"userExternalId": "XXXXXX",
// 64 bit number as string
"partyId": "6373045088123412432",
// Status of user's ticket
// 0 - invited
// 1 - confirmed (can play)
// 2 - declined
// 3 - incomplete party
// 4 - processing signup
// 5 - signup fail
// 6 - processign signout
// 7 - signout fail
"status": 1,
// Flag indicating user's attendance
"checkIn": false,
// User's final placement
"userPlace": 0,
// User's total playtime (in minutes)
"totalPlayTime": null,
// User's total match count
"matchCount": null,
// User's total game count
"gameCount": null,
// User's reached phase (>=1)
"maxPhase": null
}
]
Tournament template stats
Query daily performance stats of templates from specific date range.
Endpoint URL:
https://backbone-client-api.azurewebsites.net/api/v1/dataReportGetTournamentTemplates
Headers:
BACKBONE_APP_ID: YOUR-TSDK-GAME-CLIENT-ID
Accept-Encoding: gzip
Content-Type: application/x-www-form-urlencoded
application/x-www-form-urlencoded parameters:
accessToken
sinceDate
untilDate
accessToken
value has to be SERVER ACCESS TOKENsinceDate
value has to be UTC date and time in format 2022-10-29T00:00:00 (inclusive)untilDate
value has to be UTC date and time in format 2022-10-30T00:00:00 (inclusive)
Response:
[
{
// UTC datetime of day stats account for
"recordedAt": "2022-10-26T00:00:00.000Z",
// 64 bit number as string
"templateId": "60208317421341234",
// Name of tournament template
"name": "Battlecup",
// Type of the tournament
// 0 - public
// 1 - premium
// 2 - private
// 3 - testing
"type": 2,
// Count of tournaments for a day
"tournamentCount": 1,
// Daily tournament attendance.
// Average percentage of users who checked in and played the tournament.
// Example 100 successful signups, 75 users played would result in 75% attendance.
"dtaAvg": 88.89,
// Sum of checked in users who played all template tournaments this day.
"dtaSum": 8,
// Sum of unique checked in users who played all template tournament this day.
"dtaUSum": 8,
// Monthly tournament attendance.
// Same as daily stat above but aggregated over past 30 days.
"mtaAvg": 79.46,
// Same as daily stat above but aggregated over past 30 days.
"mtaSum": 465,
// Same as daily stat above but aggregated over past 30 days.
"mtaUSum": 47,
// Weekly retention over past month (week 1)
"wtr1": 47,
// Weekly retention over past month (week 2)
"wtr2": 31,
// Weekly retention over past month (week 3)
"wtr3": 16,
// Weekly retention over past month (week 4)
"wtr4": 14
}
]
Unity Plugin
How to start
Install the unity plugin
Download and install the unity package (You will get download link once given access to SDK).
Import the package as you normally would in unity with Assets/Import package/Custom Package
.
This will include the dlls that are required to run tournaments within your game client.
Create new game and setup gameId
Once all the files are imported in to your game/project, you will need to create a game id. To do this, log in to the tournament dashboard, click on the add new game button and follow wizard steps until you have created your new game.
Then open Game settings/General
where you can find your game id.
Next, in your unity project, find imported file named:
BackboneClientSetting.asset
which should be located in the Plugins/Gimmebreak.Backbone/Resources/
folder.
Open the file and copy in your game id as shown below.
Initialize SDK client
Add the BackboneManager
script into your scene object.
Tick the box which says Initialize on start if you want the client to initialize when Unity's Start()
is called.
If you would like to initialize the client manually (e.g. once you check internet connectivity) you can do it with an explicit call later.
public class MyMonoBehaviour : MonoBehaviour
{
//EXAMPLE 1, using callback
public void ExplicitInitializeCall()
{
//initializing Backbone (TournamentSDK) client
BackboneManager.Initialize()
.ResultCallback((result) => {
if(result)
{
//success
}
else
{
//fail
}
})
.Run(this);
}
//EXAMPLE 2, using Unity coroutine
public IEnumerator ExplicitInitializeCallCoroutine()
{
//some other code initialization
//...
//waiting for internet connectivity
//...
//initializing Backbone (TournamentSDK) client
AsyncOperation<bool> asyncOperation = BackboneManager.Initialize();
//wait until initialization is done
yield return asyncOperation;
//check result
if(asyncOperation.ReturnValue)
{
//success
}
else
{
//fail
}
}
}
You can check if the client is initialized by using BackboneManager.IsInitialized
.
Login user
Enable login providers in dashboard
Open the tournament dashboard and navigate to: Game Settings/Authentication providers
.
Enable the providers that are relevant for your game.
Fill in the required settings for each enabled provider and save.
Login user
If your login providers are set up you can proceed to log a user in. After client initialization, you can check if a user is logged in or not. If not you can proceed with the login operation.
In this example we are using Steam authentication:
private IEnumerator Start()
{
//wait until backbone client is initialized
while (!BackboneManager.IsInitialized)
{
yield return null;
}
//check if user is logged in
if (!BackboneManager.IsUserLoggedIn)
{
//Obtain steam user name, auth session ticket, steam id from prefered steam
//api library
//...
//login user using steam authentication provider
var steamLogin = LoginProvider.Steam(true, userName, steamAuthCode, steamUserId);
yield return BackboneManager.Client.Login(steamLogin);
}
}
This will log the user in using the Steam login provider. After the user is successfully logged in, you can interact with the client API.
Basic tournament operations
Get list of tournaments
A list of all tournaments can be found in:
var allTournaments = BackboneManager.Client.Tournaments.TournamentList;
In order to load or refresh the list (it can be empty after login) we have to call BackboneManager.Client.LoadTournamentList()
operation.
//load/refresh tournament list
BackboneManager.Client.LoadTournamentList()
//set finish callback
.FinishCallback(() =>
{
//bind data after operation finishes
BindData();
})
//run async operation on this MonoBehaviour
.Run(this);
NOTE: This operation returns bool result which indicates if the list was successfully refreshed. If this operation is called too often it will return false. The current "allowed refresh limit" is set to 1 minute. You can get the operation result by registering.
ResultCallback((result) => {})
as follows:
//load/refresh tournament list
BackboneManager.Client.LoadTournamentList()
//set finish callback
.FinishCallback(() =>
{
//bind data after operation finishes
BindData();
})
.ResultCallback((result) => {
if(result)
{
//successful refresh/load
}
else
{
//not refreshed/loaded
}
})
//run async operation on this MonoBehaviour
.Run(this);
After the tournament list is loaded, not all properties of the tournament class are populated.
To load all tournament data,BackboneManager.Client.LoadTournament(tournamentId)
has to be called.
The tournament class contains a flag: HasAllDataLoaded
that can be checked if all data has been loaded.
Get tournament
To load/refresh all tournament data call: BackboneManager.Client.LoadTournament(tournament)
.
This operation returns bool which indicates if the tournament was successfully loaded/refreshed.
Also there is a property on tournament class: tournament.HasAllDataLoaded
indicating if all data has already been loaded previously.
//get first tournament in the list
var tournament = BackboneManager.Client.Tournaments.TournamentList[0];
//load/refresh all tournament data
BackboneManager.Client.LoadTournament(tournament)
//set finish callback
.FinishCallback(() =>
{
if(tournament.HasAllDataLoaded)
{
//all data has been loaded/refreshed
}
})
//run async operation on this MonoBehaviour
.Run(this);
Sign up for tournament
To check if a user is already signed up, look at the tournament property: tournament.Invite
.
If invite is null, the user does not have an invite and it's also not confirmed.
To check if an invite is available, check: tournament.Invite.Status
which holds the user's tournament invite status.
//get tournament
var tournament = BackboneManager.Client.Tournaments.GetTournamentById(tournamentId);
//check if user is signed up
if (tournament.Invite == null ||
tournament.Invite.Status != TournamentUserStatus.Confirmed)
{
//user is not signed up for tournament
}
To sign user up for a tournament call: SignupForTournament(tournamentId)
.
//sign up user for tournament
BackboneManager.Client.SignupForTournament(tournamentId)
//set result callback
.ResultCallback((result) =>
{
//check sign up result
if (result.ProcessStatus != TournamentSignUpStatus.Ok)
{
LobbyAlertDialog.Show("Sign up process failed with status: " + result.ProcessStatus.ToString());
}
})
//run async operation on this MonoBehaviour
.Run(this);
This operation returns an InviteResult object that contains information about the signup result.
If the external server for the signup process was set up in dashboard (e.g. to deduct currency, items, etc.) inviteResult.IsExternalSignupError
will indicate if there was a problem during this operation.
Also, any error message thrown by custom server can be found in inviteResult.ErrorMessage
.
Tournament Hub
Initialize Tournament Hub
The Tournament Hub acts as a custom lobby for the tournament.
Once connected, it will provide information about matches and the progress/status of the tournament.
To receive tournament hub callback's, implement the ITournamentHubCallbackHandler
interface.
public void OnInitialized(ITournamentHubController controller)
{
//tournament hub was initialized and provides controller
}
public void OnHubStatusChanged(TournamentHubStatus newStatus)
{
//tournament hub status has changed
}
public void OnTournamentUpdate()
{
//tournament data has been updated
}
public void OnHubMatchStatusChanged(TournamentHubMatchStatus newStatus)
{
//joined tournament match status has changed
}
public void OnHubMatchUpdate()
{
//joined tournament match data has been updated
}
To initialize the Tournament Hub call: ConnectTournamentHub(callbackHandler, tournament);
where callback handler is the object that implements ITournamentHubCallbackHandler.
var tournament = BackboneManager.Client.Tournaments.GetTournamentById(tournamentId);
BackboneManager.Client.ConnectTournamentHub(this, tournament);
Once the Tournament Hub is initialized it will call: OnInitialized(ITournamentHubController controller)
and return a controller that is used to control certain tournament flows from user perspective (e.g. indicating that a user is ready for the next match).
Tournament hub statuses
OnHubStatusChanged(TournamentHubStatus newStatus)
will indicate the current status of the tournament from a user's perspective. Your UI should react to any changes accordingly.
public void OnHubStatusChanged(TournamentHubStatus newStatus)
{
switch (newStatus)
{
case TournamentHubStatus.RegistrationClosed:
//Registration is closed and has not been open yet. You can check
//open time in tournament property 'RegistrationOpenTime'.
break;
case TournamentHubStatus.RegistrationOpening:
//Registration is opening as 'RegistrationOpenTime' was reached but
//confirmation from server is awaited.
break;
case TournamentHubStatus.RegistrationOpened:
//Registration is opened and users can sign up for tournament.
break;
case TournamentHubStatus.RegistrationClosing:
//Registration/Inivitation is closing as 'InvitationCloseTime' was reached
//but confirmation from server is awaited.
break;
case TournamentHubStatus.WaitingForTournamentStart:
//Registration is closed and tournament start confirmation from
//server is awaited.
break;
case TournamentHubStatus.Starting:
//Tournament is starting as tournament 'Time' was reached but
//confirmation from server is awaited.
break;
case TournamentHubStatus.Started:
//Tournament has started. Get current phase from
//'tournament.GetCurrentTournamentPhase()' containing user standings.
break;
case TournamentHubStatus.MatchInProgress:
//User has match in progress he should be part of.
//Get all match metadata from 'tournament.UserActiveMatch'.
break;
case TournamentHubStatus.ResolvingPartiallyFilledMatch:
//User active match was not filled in time. Awaiting confirmation from
//server if match should be played.
break;
case TournamentHubStatus.ClosingOverdueMatch:
//User active match reached a deadline and its due for closure.
//Awaiting confirmation from server.
break;
case TournamentHubStatus.WaitingForUserReadyConfirmation:
//User can proceed to next round of the tournament. Explicit confirmation
//is requested by calling 'tournamentHubController.SetUserReady()'.
break;
case TournamentHubStatus.WaitingForNextPhase:
//Tournament current phase is about to finish. Waiting for next phase to
//start.
break;
case TournamentHubStatus.WaitingForTournamentToFinish:
//User has finished all rounds in current phase or was already knocked
//out of the tournament and waiting for tournament to finish.
break;
case TournamentHubStatus.Finishing:
//Last round of last phase has reached deadline and all matches should
//be finilized. Awaiting confirmation from server.
break;
case TournamentHubStatus.Finished:
//Tournament has finished. Found out who won by looking at
//'tournament.Winner.Users'
break;
}
}
User's active match
When a tournament is running and a user has signed up, they will automatically be given an Active match
that contains all metadata and statuses.
A user's active match can be accessed as follows from the Tournament Hub: tournamentHub.Tournament.UserActiveMatch
.
UserActiveMatch.Secret
can be used as a network room/lobby password or name.
This is distributed only to users that are allowed to join a specific match.
UserActiveMatch.Status
should be used to determine if a user; is waiting for other users/opponents, must connect to a specific room/lobby when the game is in progress or, if the user should proceed to another match.
switch (tournamentHubController.Tournament.UserActiveMatch.Status)
{
case TournamentMatchStatus.Created:
//Match was successfuly created.
break;
case TournamentMatchStatus.WaitingForOpponent:
//Match is not filled and opponents are still awaited to join.
break;
case TournamentMatchStatus.GameReady:
//Match game is ready to be played. Proceed to create game session that will
//change status to 'GameInProgress'.
//NB: users might still be checking in and connecting to room/lobby.
//Client should wait until all parties are successfully connected and ready.
//Only then procced to create game session.
break;
case TournamentMatchStatus.GameInProgress:
//Match game is in progress, game session was created and it's in progress.
//NB: user should be able to reconnect to ongoing game session.
break;
case TournamentMatchStatus.GameFinished:
//Match game has finished as results were reported. If match requires more games
//to be played per series (e.g. best of 3) proceed to create another game session
//that will change status to 'GameInProgress' again.
break;
case TournamentMatchStatus.MatchFinished:
//Match has finished as all games has been played (or deadline was reached).
//It will be closed soon, user can proceed to another match.
break;
case TournamentMatchStatus.Closed:
//Match was finalized and closed by server.
break;
}
You can determine if opponents are ready by checking: UserActiveMatch.Users[i].IsCheckedIn
.
You can refresh: UserActiveMatch
data by calling: tournamentHubController.RefreshActiveMatch()
.
Note that the UserActiveMatch
can be null or it can be in finished state. In order to request another match and proceed in tournament user has to explicitly call tournamentHubController.SetUserReady()
.
User is expected to do this when tournament hub will enter a state TournamentHubStatus.WaitingForUserReadyConfirmation
. This action can be represented with UI (e.g. Ready for next match) or it can be done automatically without user interaction moving him immediately to next match when tournament hub enters the state.
Joined match interface
If your game has defined methods for private game/lobby creation, implementing match interface makes easier to manage user joining and starting tournament matches.
Implement ITournamentMatchCallbackHandler
interface to your lobby script or create a new scrip implementing this.
It provides simple set of methods to communicate your lobby state to tournament hub.
public void OnJoinTournamentMatch(Tournament tournament, TournamentMatch match, ITournamentMatchController controller)
{
//Callback from tournament hub passing tournament, match and controller object.
//Use match data to join correct lobby/room.
//User controller to inform tournament hub about changes in your lobby/room.
}
public bool IsConnectedToGameServerNetwork()
{
//Check if client is successfully connected to your networking backend.
//Return true if user is connected and ready to join lobby/room.
}
public bool IsUserConnectedToMatch(long userId)
{
//Check if specific user is already connected to lobby/room.
//Return true if user is connected.
}
public bool IsUserReadyForMatch(long userId)
{
//Check if specific user is ready (e.g. moved to correct slot)
//Return true if user is ready to start.
//NB: local user that is not checked in for the match yet, will be checked in
//only after returning true
}
public bool IsGameSessionInProgress()
{
//Check if game session is already in progress for given tournament match.
//Return true if game session is in progress.
}
public void OnLeaveTournamentMatch()
{
//Callback from tournament hub informing user should leave joined lobby/room.
}
public void StartGameSession(IEnumerable<TournamentMatch.User> checkedInUsers)
{
//Callback from tournament hub requesting game session to start immediately. Also
//passing users that successfully checked in for current match.
//Create tournament game session, and start your game.
//This might be called multiple times until IsGameSessionInProgress returns true.
}
Object implementing this interface can then passed to tournament hub controller method when joining specific match: tournamentHubController.JoinTournamentMatch()
.
Code flow example:
//Initialize tournament hub
BackboneManager.Client.ConnectTournamentHub(hubCallbackHandler, tournament);
//...
//Get hub controller
public void OnInitialized(ITournamentHubController controller)
{
hubController = controller
}
//...
//Check tournament hub is in "MatchInProgress" status
public void OnHubStatusChanged(TournamentHubStatus newStatus)
{
switch (newStatus)
{
case TournamentHubStatus.MatchInProgress:
case TournamentHubStatus.ResolvingPartiallyFilledMatch:
//...
//Join users active match
var match = hubController.Tournament.UserActiveMatch;
hubController.JoinTournamentMatch(match, matchCallbackHandler);
//...
break;
}
}
NOTE: it is also important to use reporting methods on tournamentMatchController
that is passed in OnJoinTournamentMatch
.
Call these controller methods when specific events occur in connected lobby/room.
Tournament hub is using these to determine when to refresh metadata.
Failing to do so can lead into inconsistencies where one client start match but others do not (e.g. other client thinks user did not check in yet).
Example of using Photon room callbacks to report changes to tournamentMatchController
:
//Photon callback when new player joined room
public void OnPlayerEnteredRoom(Player newPlayer)
{
long userId;
//extract user id from player custom properties
if (this.tournamentMatchController != null &&
TryGetPlayerBackboneUserId(newPlayer, out userId))
{
//report user who joined room
this.tournamentMatchController.ReportJoinedUser(userId);
}
}
//Photon callback when player disconnected from room
public void OnPlayerLeftRoom(Player otherPlayer)
{
long userId;
//extract user id from player custom properties
if (this.tournamentMatchController != null &&
TryGetPlayerBackboneUserId(otherPlayer, out userId))
{
//report user who disconnected from room
this.tournamentMatchController.ReportDisconnectedUser(userId);
}
}
//Photon callback when room properties are updated
public void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
{
if (this.tournamentMatchController != null)
{
//reporting status change will refresh match metadata
this.tournamentMatchController.ReportStatusChange();
}
}
//Photon callback when player properties are updated
public void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
{
if (this.tournamentMatchController != null)
{
//reporting status change will refresh match metadata
this.tournamentMatchController.ReportStatusChange();
}
}
Tournament game session & result submission
Based on tournament settings, a match can contain multiple game sessions. For example if a 1v1 match is set up as a "best of 3" series, it will require at least 2 game sessions to be played. A "game session" represents a single game in match series.
Create game session
In order to create a game session, call: CreateGameSession(users, userActiveMatchId, sessionType)
.
The returned game session id can be then distributed to other clients.
If a another subsequent call is made, the same game session id is returned.
//create game session only for checked in users
var checkedInUsers = userActiveMatch.Users.Where(user => user.IsCheckedIn);
//game session type can be used to identify specific game modes
//e.g. 0-default, 1-4player mode, 2-8player mode
var sessionType = 0;
//create game session
BackboneManager.Client.CreateGameSession(checkedInUsers, userActiveMatch.Id, sessionType)
//set result callback
.ResultCallback((gameSession) =>
{
//get game session id and distribute it to other clients
//NB: this is up to developer, e.g. use custom room/lobby properties
//or broadcast message
var gameSessionId = gameSession.Id;
})
//run async operation on this MonoBehaviour
.Run(this);
Submit results
After a game session is finished, results have to be reported before the match deadline is reached.
If a match is set up to have more than one game session (e.g. best of 3 series) then GetMatchNextGameDeadline(match)
can be used to determine the ideal deadline for the current game session so that the subsequent game session(s) still has time to be played.
//get user active match
var userActiveMatch = tournament.UserActiveMatch;
//get ideal deadline for next game session
var deadline = tournament.GetMatchNextGameDeadline(userActiveMatch);
To submit the game session result use the SubmitGameSession(gameSession)
call.
A game session object has to be created on all clients using the distributed game session id returned from the CreateGameSession
call.
It is important to set the users place which will determine the point distribution for a given game session.
//create game session only for checked in users
var matchUsers = userActiveMatch.Users.Where(user => user.IsCheckedIn).ToList();
//sort users based on your game session results, e.g. kills, deaths etc.
matchUsers.Sort((user1, user2) =>{
//sort users from best to worst based on specific game rules
});
//create list for game session users
List<GameSession.User> gameSessionUsers = new List<GameSession.User>();
//loop through sorted users from best to worst
for(var i = 0; i < matchUsers.Count; i++)
{
var userId = matchUsers[i].UserId;
var teamId = matchUsers[i].TeamId;
//add game session user with set final place in game session (more users
//can have same placement if required)
gameSessionUsers.Add(new GameSession.User(userId, teamId) { Place = (i + 1) });
}
//get user active match id
var matchId = userActiveMatch.Id;
//create game session using id that was obtained from 'CreateGameSession' call as
//well as passing tournament match id
GameSession gameSession = new GameSession(gameSessionId, 0, gameSessionUsers, matchId);
//set played date and game session duration
gameSession.PlayDate = DateTime.UtcNow;
gameSession.PlayTime = gameTime;
//submit game session to server
BackboneManager.Client.SubmitGameSession(gameSession)
.ResultCallback((result) => {
if (result)
{
//game session was successfuly submitted
}
})
.Run(this);
Miscellaneous
Setting client language
Client can define preffered language. When tournament has defined content in multiple languages preffered language will be served to the client. In case preffered laguage does not exist then english is given as default.
To set user language assign ISO639‑1 string to UserLanguage
property.
BackboneManager.Client.User.UserLanguage = "en";
BackboneManager.Client.User.SetDataAsDirty();
BackboneManager.Client.SynchUser();
Tournament list caching and date range
Api call LoadTournamentList()
has default caching set to 15 minutes.
This can be changed by setting TournamentData.tournamentListUpdateLimit
.
Same way also default date range can be set wtih TournamentData.tournamentListSinceOffset
and TournamentData.tournamentListUntilOffset
.
// refresh tournament list cache every 5 minutes
TournamentData.tournamentListUpdateLimit = 5;
// load tournaments from 3 days ago
TournamentData.tournamentListSinceOffset = System.TimeSpan.FromDays(-3);
// load tournaments for next 14 days
TournamentData.tournamentListUntilOffset = System.TimeSpan.FromDays(14);
NOTE: caching does not apply when calling LoadTournaments()
or LoadTournamentsAll()
.
API-BackboneClient
Properties
Game
Get games global data. (E.g. game id, global properties)
public GameData Game
{
get;
}
IsInitialized
Determine if client is successfully initialized.
public bool IsInitialized
{
get;
}
IsUserLoggedIn
Determine if user is logged in and has a valid session to do API calls.
public bool IsUserLoggedIn
{
get;
}
Notifications
Get users notification data. (E.g. tournament party invitations)
public NotificationData Notifications
{
get;
}
Season
Get current season data. (E.g. start date, end date, user season stats)
public SeasonData Season
{
get;
}
Tournaments
Get users tournaments data. (E.g. list of tournaments)
public TournamentData Tournaments
{
get;
}
User
Get users data. (E.g. nickname, platform ids, user properties)
public UserData User
{
get;
}
Methods
AcceptPartyInvite(long, long)
Accepts tournament party invite. User has to sign up for tournament first before he can accept any party invite.
public Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.Tournaments.TournamentAcceptPartyStatus> AcceptPartyInvite(long tournamentId, long partyInviteId)
Parameters
tournamentId
: Tournament id.partyInviteId
: Tournament party invite id.
Returns
Accept party invite status.
Remarks
Party invites are received as notifications (TournamentPartyInviteNotification) and they contain tournamentId as well as partyInviteId.
AcceptPartyInvite(long, string)
Accepts tournament party invite by providing shared party code. User has to sign up for tournament first before he can accept any party invite.
public Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.Tournaments.TournamentAcceptPartyStatus> AcceptPartyInvite(long tournamentId, string partyCode)
Parameters
tournamentId
: Tournament id.partyCode
: Tournament party code.
Returns
Accept party invite status.
Remarks
Party codes are created on demand by calling CreatePartyCodeForTournament method. Once a party code exits, it can be shared with other users (e.g. chat) who can use it to join the party.
ChangeNickname(string)
Change user nick name. Unique hash number will be given on success.
public Gimmebreak.Backbone.Core.AsyncOperation<bool> ChangeNickname(string nickName)
Parameters
nickName
: New nick name (max 100 characters).
Returns
True if operation was successfull.
Remarks
If you want to force specific hash number (E.g. to match your own system) use ChangeNickname(string nickName, int preferredNickHash) instead.
ChangeNickname(string, int)
Change user nick name. Desired/preferred hash number can be provided but if combination is already taken then unique hash number will be given on success.
public Gimmebreak.Backbone.Core.AsyncOperation<bool> ChangeNickname(string nickName, int preferredNickHash)
Parameters
nickName
: New nick name (max 100 characters).preferredNickHash
: Desired/preferred hash number.
Returns
True if operation was successfull.
ConnectTournamentHub(ITournamentHubCallbackHandler, Tournament)
Connect and initialize tournament hub for specific tournament. Passed tournament hub (ITournamentHubCallbackHandler) will start receiveing callbacks until it is disconnected. This is mainly used for ongoing tournament to propagate tournament state changes via tournament hub.
public void ConnectTournamentHub(Gimmebreak.Backbone.Tournaments.ITournamentHubCallbackHandler tournamentHub, Gimmebreak.Backbone.Tournaments.Tournament tournament)
Parameters
tournamentHub
: Tournament hub implementing ITournamentHubCallbackHandler.tournament
: Specific tournament to initialize tournament hub for.
Remarks
Only one tournament can be connected to tournament hub at a time. Even if different tournament hub object is passed connecting to other tournament it will stop callbacks on previousely connected tournament hub.
CreateGameSession(IEnumerable<TournamentMatch.User>, long, byte)
Creates game session for specific tournament match. Users passed must be a subset of users specified in tournament match.
public Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.GameSessions.GameSession> CreateGameSession(System.Collections.Generic.IEnumerable<Gimmebreak.Backbone.Tournaments.TournamentMatch.User> users, long tournamentMatchId, byte gameSessionType = 0)
Parameters
users
: Users participating in tournament match.tournamentMatchId
: Tournament match id.gameSessionType
: Type of game session (defined in dashboard, e.g. team match, deadmatch, capture flag, etc.)
Returns
Game session with unique Id, returns null if operation failed. Will be needed for reporting results.
Remarks
Same game session is returned for specific tournament match id until results are submitted and game session is closed.
CreateGameSession(IEnumerable<GameSession.User>, long, byte)
Creates game session for specific tournament match. Users passed must be a subset of users specified in tournament match.
public Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.GameSessions.GameSession> CreateGameSession(System.Collections.Generic.IEnumerable<Gimmebreak.Backbone.GameSessions.GameSession.User> users, long tournamentMatchId, byte gameSessionType = 0)
Parameters
users
: Users participating in tournament match.tournamentMatchId
: Tournament match id.gameSessionType
: Type of game session (defined in dashboard, e.g. team match, deadmatch, capture flag, etc.)
Returns
Game session with unique Id, returns null if operation failed. Will be needed for reporting results.
Remarks
Same game session is returned for specific tournament match id until results are submitted and game session is closed.
CreatePartyCodeForTournament(long, bool)
Create party code for tournament that can be shared and used to join the party.
public Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.Tournaments.PartyCodeResult> CreatePartyCodeForTournament(long tournamentId, bool recreate = False)
Parameters
tournamentId
: Tournament id.recreate
: If set to true existing party code will be replaced with new one.
Returns
Result of party create code process.
Remarks
When party code is recreated by setting "recreate" parameter to true then old party code will be rendered invalid and can no longer be used for joining the party.
CreatePartyInviteForTournament(long, long)
Create party invite for tournament by providing user id.
public Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.Tournaments.PartyInviteResult> CreatePartyInviteForTournament(long tournamentId, long inviteUserId)
Parameters
tournamentId
: Tournament id.inviteUserId
: Id of user that will receive party invite.
Returns
Result of party invite process.
CreatePartyInviteForTournament(long, LoginProvider.Platform, string)
Create party invite for tournament by providing type and user id of specific platform.
public Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.Tournaments.PartyInviteResult> CreatePartyInviteForTournament(long tournamentId, Gimmebreak.Backbone.User.LoginProvider.Platform inviteUserPlatformType, string inviteUserPlatformId)
Parameters
tournamentId
: Tournament id.inviteUserPlatformType
: Specific platform type.inviteUserPlatformId
: Specific platform id.
Returns
Result of party invite process.
CreatePartyInviteForTournament(long, string, int)
Create party invite for tournament by providing users nick name and hash number.
public Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.Tournaments.PartyInviteResult> CreatePartyInviteForTournament(long tournamentId, string inviteUserNick, int inviteUserHash)
Parameters
tournamentId
: Tournament id.inviteUserNick
: Users nick name.inviteUserHash
: Users hash number.
Returns
Result of party invite process.
DeclinePartyInvite(long)
Decline tournament party invite.
public Gimmebreak.Backbone.Core.AsyncOperation<bool> DeclinePartyInvite(long partyInviteId)
Parameters
partyInviteId
: Party invite id.
Returns
True if operation was successfull.
Remarks
Declining an invite will not prevent other user to send another one again.
DisconnectTournamentHub()
Disconnects tournament hub and stops running callbacks.
public void DisconnectTournamentHub()
DismissNotification(Notification)
Dismiss users notification and remove it from active list. Notification is still available until next list refresh. Check IsDismissed property to determine if notification was already dismissed.
public Gimmebreak.Backbone.Core.AsyncOperation<bool> DismissNotification(Gimmebreak.Backbone.Notifications.Notification notification)
Parameters
notification
: Notification to dismiss.
Returns
Ture if notification was dismissed.
DownloadGameSessionReplay(GameSession)
Downloads game session replay if available and populates list of submissions on game session replay object (GameSessions.GameSession.Replay).
public Gimmebreak.Backbone.Core.AsyncOperation<bool> DownloadGameSessionReplay(Gimmebreak.Backbone.GameSessions.GameSession gameSession)
Parameters
gameSession
: Valid game session.
Returns
True if operation was successful.
Initialize(IBackboneClientCallbackHandler)
Initialize Backbone client using setting from Default BackboneClientSetting asset.
public static Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.Core.BackboneClient> Initialize(Gimmebreak.Backbone.Core.IBackboneClientCallbackHandler callbackHandler = null)
Parameters
callbackHandler
: Pass handler that will listen to client callbacks.
Returns
Initialized Backbone client ready for use.
Initialize(BackboneClientSetting, IBackboneClientCallbackHandler)
Initialize Backbone client using setting from BackboneClientSetting asset.
public static Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.Core.BackboneClient> Initialize(Gimmebreak.Backbone.Core.BackboneClientSetting settings, Gimmebreak.Backbone.Core.IBackboneClientCallbackHandler callbackHandler = null)
Parameters
settings
: BackboneClientSetting asset.callbackHandler
: Pass handler that will listen to client callbacks.
Returns
Initialized Backbone client ready for use.
Initialize(Uri, string, IBackboneClientCallbackHandler)
Initialize Backbone client for specific server and game.
public static Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.Core.BackboneClient> Initialize(System.Uri server, string gameId, Gimmebreak.Backbone.Core.IBackboneClientCallbackHandler callbackHandler = null)
Parameters
server
: Server url or ip.gameId
: Game id obtained from Backbone dashboard.callbackHandler
: Pass handler that will listen to client callbacks.
Returns
Initialized Backbone client ready for use.
LoadNotifications()
Load users active notifications (e.g. news, party invites, etc.)
public Gimmebreak.Backbone.Core.AsyncOperation<bool> LoadNotifications()
Returns
True if notifications were loaded
LoadTournament(Tournament)
Load all tournament data. (Party users, matches, details, etc.)
public Gimmebreak.Backbone.Core.AsyncOperation<bool> LoadTournament(Gimmebreak.Backbone.Tournaments.Tournament tournament)
Parameters
tournament
: Tournament to load data for.
Returns
True if data were loaded.
LoadTournament(long)
Load all tournament data. (Party users, matches, details, etc.)
public Gimmebreak.Backbone.Core.AsyncOperation<bool> LoadTournament(long tournamentId)
Parameters
tournamentId
: Tournament id to load data for.
Returns
True if data were loaded.
LoadTournamentList()
Load tournament list containing past, upcoming and current tournaments. Loaded tournaments do not contain all metadata. You can check property HasAllDataLoaded. To load all data you have to subsequently call LoadTournament().
public Gimmebreak.Backbone.Core.AsyncOperation<bool> LoadTournamentList()
Returns
True if list was refreshed successfully.
LoadTournamentMatches(long, IEnumerable<long>)
Load tournament matches for specific match ids. The return limit is 25 matches per call.
public Gimmebreak.Backbone.Core.AsyncOperation<System.Collections.Generic.List<Gimmebreak.Backbone.Tournaments.TournamentMatch>> LoadTournamentMatches(long tournamentId, System.Collections.Generic.IEnumerable<long> matchIds)
Parameters
tournamentId
: Tournament id.matchIds
: Specific tournament match ids (max 25).
Returns
List of tournament matches on success, otherwise null.
LoadTournamentMatches(long, int, int, int, int, int, bool, int)
Load tournament matches for specific phase. This method allows to browse all matches in tournament.
public Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.Tournaments.MatchesPaginatedResult> LoadTournamentMatches(long tournamentId, int phaseId, int fromRoundId, int toRoundId, int maxResults, int page, bool onlyInProgress, int groupId = 0)
Parameters
tournamentId
: Tournament id.phaseId
: Specific tournament phase to load matches for.fromRoundId
: Phase round id to load matches from, inclusive.toRoundId
: Phase round id to load matche to, inclusive.maxResults
: Max results to be returned per page.page
: Requested page number.onlyInProgress
: Loads only matches that are in progress.groupId
: Specific phase group to load matches for.
Returns
One page of tournament matches.
LoadTournamentMatchesAll(long, int, int)
Load all tournament matches for specific phase. If phase contains large number of matches the operation will be split into more than one call and can take same time to finish. If you wish to load/refresh only specific matches use LoadTournamentMatches() method. (E.g. to load only matches from first round.)
public Gimmebreak.Backbone.Core.AsyncOperation<System.Collections.Generic.List<Gimmebreak.Backbone.Tournaments.TournamentMatch>> LoadTournamentMatchesAll(long tournamentId, int phaseId, int groupId = 0)
Parameters
tournamentId
: Tournament id.phaseId
: Specific tournament phase to load matches for.groupId
: Specific phase group to load matches for.
Returns
List of all phase matches, otherwise null.
LoadTournamentMatchGameSessions(TournamentMatch)
Loads finished game sessions of tournament match. Only tournament match that is ongoing or finished will be processed.
public Gimmebreak.Backbone.Core.AsyncOperation<bool> LoadTournamentMatchGameSessions(Gimmebreak.Backbone.Tournaments.TournamentMatch tournamentMatch)
Parameters
tournamentMatch
: Tournament match that is ongoing or finished.
Returns
True if operation was successfull.
LoadTournaments(DateTime, DateTime, int, int)
Loads tournaments between specific dates. Loaded tournaments do not contain all metadata. You can check property HasAllDataLoaded. To load all data you have to subsequently call LoadTournament().
public Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.Tournaments.TournamentsPaginatedResult> LoadTournaments(System.DateTime sinceDate, System.DateTime untilDate, int maxResults = 25, int page = 1)
Parameters
sinceDate
: UTC date time (smaller value then until date).untilDate
: UTC date time (bigger value than since date).maxResults
: Max results to be returned per page.page
: Requested page number.
Returns
One page of tournament.
LoadTournamentsAll(DateTime, DateTime)
Load all tournaments between specific dates. Loaded tournaments do not contain all metadata. You can check property HasAllDataLoaded. To load all data you have to subsequently call LoadTournament(). If time frame contains large number of tournaments the operation will be split into more than one call and can take same time to finish. If you wish to load/refresh only specific tournaments use LoadTournaments() method.
public Gimmebreak.Backbone.Core.AsyncOperation<System.Collections.Generic.List<Gimmebreak.Backbone.Tournaments.Tournament>> LoadTournamentsAll(System.DateTime sinceDate, System.DateTime untilDate)
Parameters
sinceDate
: UTC date time (smaller value then until date).untilDate
: UTC date time (bigger value than since date).
Returns
List of all tournaments between dates, otherwise null.
LoadTournamentScores(long, int, int, int, int)
Load tournament scores for specific phase. This method allows to browse all party scores in tournament.
public Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.Tournaments.ScoresPaginatedResult> LoadTournamentScores(long tournamentId, int phaseId, int maxResults, int page, int groupId = 0)
Parameters
tournamentId
: Tournament id.phaseId
: Specific tournament phase to load scores for.maxResults
: Max results to be returned per page.page
: Requested page number.groupId
: Specific phase group to load scores for.
Returns
One page of tournament scores.
LoadTournamentScoresAll(long, int, int)
Load all tournament scores for specific phase. If phase contains large number of parties the operation will be split into more than one call and can take same time to finish. If you wish to load/refresh only specific scores use LoadTournamentScores() method. (E.g. to load only scores from top 10 places.)
public Gimmebreak.Backbone.Core.AsyncOperation<System.Collections.Generic.List<Gimmebreak.Backbone.Tournaments.TournamentScore>> LoadTournamentScoresAll(long tournamentId, int phaseId, int groupId = 0)
Parameters
tournamentId
: Tournament id.phaseId
: Specific tournament phase to load scores for.groupId
: Specific phase group to load scores for.
Returns
List of all phase scores, otherwise null.
LoadUserSeasonProfile(long, int)
Loads user profile for specific season. This contains stats such as played games, tournaments, etc., as well as last played game sessions.
public Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.Season.UserSeasonProfile> LoadUserSeasonProfile(long userId, int season)
Parameters
userId
: User idseason
: Season number, starting with 1.
Returns
User season profile if operation was successfull.
LoadUserSeasonProfile(LoginProvider.Platform, string, int)
Loads user profile for specific season. This contains stats such as played games, tournaments, etc., as well as last played game sessions.
public Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.Season.UserSeasonProfile> LoadUserSeasonProfile(Gimmebreak.Backbone.User.LoginProvider.Platform userPlatformType, string userPlatformId, int season)
Parameters
userPlatformType
: Specific platform type.userPlatformId
: Specific platform id.season
: Season number, starting with 1.
Returns
User season profile if operation was successfull.
Login(LoginProvider)
Login user via specific login provider. If user does not exists it fails or creates a new one based on provider setting.
public Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.User.LoginResult> Login(Gimmebreak.Backbone.User.LoginProvider loginProvider)
Parameters
loginProvider
: Login provider used for authentication.
Returns
Access and refresh token pair.
Logout()
Logout user and remove all cached data
public Gimmebreak.Backbone.Core.AsyncOperation<bool> Logout()
Returns
True if user was logged out
RemovePartyUser(long, long)
Remove user from tournament party. Other users in party can be only removed by party leader. You can pass your own id to leave party.
public Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.Tournaments.TournamentRemoveUserStatus> RemovePartyUser(long tournamentId, long userId)
Parameters
tournamentId
: Tournament id of party to remove user from.userId
: Id of user to be removed.
Returns
Remove user status.
Remarks
If party leader leaves party (By removing himself) new party leader is automatically set from remaining party users.
ReportUser(long, ReportReason, long, long, long)
Report user for suspected cheating or other reason. This action can be initiated by user or automatically by any anticheat. Use correct report reason indicating who initiated the report (User or System).
public Gimmebreak.Backbone.Core.AsyncOperation<bool> ReportUser(long userId, Gimmebreak.Backbone.User.ReportReason reportReason, long gameSessionId = 0, long tournamentMatchId = 0, long tournamentId = 0)
Parameters
userId
: Id of user to be reported.reportReason
: Reason of report.gameSessionId
: Optional game session id (usefull to track down game session replay).tournamentMatchId
: Optional tournament match id.tournamentId
: Optional tournament id.
Returns
True if operation was successful.
Remarks
Reports are immediately visible in tournament dashboard. In case of ongoing tournament, admin can kick or ban excessively reported users.
SaveSession()
Snapshots client state and save data to disk. When client initializes it loads latest snapshot. This way game can access user data in offline mode.
public Gimmebreak.Backbone.Core.AsyncOperation<bool> SaveSession()
Returns
True if snapshot was saved.
SignoutFromTournament(long)
Sign out from tournament if registration is open.
public Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.Tournaments.InviteResult> SignoutFromTournament(long tournamentId)
Parameters
tournamentId
: Id of tournament to sign out from.
Returns
Reuslt of signout process. Can return null if tournament id is invalid.
SignupForTournament(long)
Sign up user for tournament if registration is open.
public Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.Tournaments.InviteResult> SignupForTournament(long tournamentId)
Parameters
tournamentId
: Id of tournament to sign up for.
Returns
Result of signup process. Can return null if tournament id is invalid.
Remarks
If there are custom requirements set for tournament that can be verified locally check them before proceeding to user signup that would fail. Use server requirement verification as sanity check rather than directing user experience flow. The same applies for any entry fees for tournament. Check if user has all entry fees (E.g. currency, items, points) before proceeding to user signup that would fail.
SignupForTournament(string)
Sign up user for tournament if registration is open.
public Gimmebreak.Backbone.Core.AsyncOperation<Gimmebreak.Backbone.Tournaments.InviteResult> SignupForTournament(string privateCode)
Parameters
privateCode
: Private code of tournament to sign up for.
Returns
Result of signup process.
Remarks
If there are custom requirements set for tournament that can be verified locally check them before proceeding to user signup that would fail. Use server requirement verification as sanity check rather than directing user experience flow. The same applies for any entry fees for tournament. Check if user has all entry fees (E.g. currency, items, points) before proceeding to user signup that would fail.
SubmitGameSession(GameSession, byte[])
Submits game session result to server. All users have to have final place set in order to assign points. You can provide game stats set up in dahboard (e.g. number of shots fired, kills, picked abilities, etc.). All match participants have to submit results and they have to be same. If someone reports different outcome such match will be marked and users will receive warning report to further investigate potential cheating.
public Gimmebreak.Backbone.Core.AsyncOperation<bool> SubmitGameSession(Gimmebreak.Backbone.GameSessions.GameSession gameSession, byte[] replayData = null)
Parameters
gameSession
: Game session with final results and stats.replayData
: Optional replay data of game session.
Returns
True if submission was succesfull.
SubmitGameSessionReplay(long, byte[])
Submits user game session replay to server.
public Gimmebreak.Backbone.Core.AsyncOperation<bool> SubmitGameSessionReplay(long gameSessionId, byte[] replayData = null)
Parameters
gameSessionId
: Valid game session id.replayData
: Uncompressed raw replay data. (Data will be compressed before sending)
Returns
True if operation is successful.
Remarks
All users have to submit game session replay successfully otherwise it will not indicate that replay is available.
SynchUser(bool)
Get the latest user data from server.
public Gimmebreak.Backbone.Core.AsyncOperation<bool> SynchUser(bool fullSynch = False)
Parameters
fullSynch
: Some parts are updated incrementally, set true if full sych should be done.
Returns
True if operation was successfull.
- Properties
- Methods
- AcceptPartyInvite(long, long)
- AcceptPartyInvite(long, string)
- ChangeNickname(string)
- ChangeNickname(string, int)
- ConnectTournamentHub(ITournamentHubCallbackHandler, Tournament)
- CreateGameSession(IEnumerable<TournamentMatch.User>, long, byte)
- CreateGameSession(IEnumerable<GameSession.User>, long, byte)
- CreatePartyCodeForTournament(long, bool)
- CreatePartyInviteForTournament(long, long)
- CreatePartyInviteForTournament(long, LoginProvider.Platform, string)
- CreatePartyInviteForTournament(long, string, int)
- DeclinePartyInvite(long)
- DisconnectTournamentHub()
- DismissNotification(Notification)
- DownloadGameSessionReplay(GameSession)
- Initialize(IBackboneClientCallbackHandler)
- Initialize(BackboneClientSetting, IBackboneClientCallbackHandler)
- Initialize(Uri, string, IBackboneClientCallbackHandler)
- LoadNotifications()
- LoadTournament(Tournament)
- LoadTournament(long)
- LoadTournamentList()
- LoadTournamentMatches(long, IEnumerable<long>)
- LoadTournamentMatches(long, int, int, int, int, int, bool, int)
- LoadTournamentMatchesAll(long, int, int)
- LoadTournamentMatchGameSessions(TournamentMatch)
- LoadTournaments(DateTime, DateTime, int, int)
- LoadTournamentsAll(DateTime, DateTime)
- LoadTournamentScores(long, int, int, int, int)
- LoadTournamentScoresAll(long, int, int)
- LoadUserSeasonProfile(long, int)
- LoadUserSeasonProfile(LoginProvider.Platform, string, int)
- Login(LoginProvider)
- Logout()
- RemovePartyUser(long, long)
- ReportUser(long, ReportReason, long, long, long)
- SaveSession()
- SignoutFromTournament(long)
- SignupForTournament(long)
- SignupForTournament(string)
- SubmitGameSession(GameSession, byte[])
- SubmitGameSessionReplay(long, byte[])
- SynchUser(bool)
API-ITournamentHubCallbackHandler
Methods
OnHubMatchStatusChanged(TournamentHubMatchStatus)
Callback informing that joined tournament match has changed status for user. If tournament hub status indicates that user should be in ongoing match then UserActiveMatch on tournament object will contain metadata for specific match. To receive match statuses use tournament hub controller and call ITournamentHubController.JoinTournamentMatch to join tournament match.
public abstract virtual void OnHubMatchStatusChanged(Gimmebreak.Backbone.Tournaments.TournamentHubMatchStatus newStatus)
Parameters
newStatus
: New tournament match status for user.
OnHubMatchUpdate()
Callback informing that user active match metadata has been updated.
public abstract virtual void OnHubMatchUpdate()
Remarks
This callback does not necessary mean there have been any changes to user active match. It indicates that user active match was refreshed with the most recent data. This callback can be used to trigger UI updates.
OnHubStatusChanged(TournamentHubStatus)
Callback informing that tournament has changed status for user. Tournament hub undergoes many status changes during ongoing tournament. The status is informing of exact state of tournament for logged user.
public abstract virtual void OnHubStatusChanged(Gimmebreak.Backbone.Tournaments.TournamentHubStatus newStatus)
Parameters
newStatus
: New tournament hub status for user.
Remarks
User that is signed up for tournament will receive different statuses than user who is not. Use statuses to inform user what is happeing, if they should wait, join match, sign up etc.
OnInitialized(ITournamentHubController)
Callback informing that tournament hub was initialized and providing coresponding controller to interact with tournament. This is result of connecting tournament hub with ConnectTournamentHub() call on backbone client.
public abstract virtual void OnInitialized(Gimmebreak.Backbone.Tournaments.ITournamentHubController controller)
Parameters
controller
: Tournament hub controller for interaction with related tournament.
Remarks
This callback is not executed immediately after ConnectTournamentHub() is called. The initialization process takes few seconds after which OnInitialized is executed.
OnTournamentUpdate()
Callback informaing that tournament metadata has been updated.
public abstract virtual void OnTournamentUpdate()
Remarks
This callback does not necessary mean there have been any changes to tournament metadata. It indicates that tournament was refreshed with the most recent data. This callback can be used to trigger UI updates.
API-ITournamentHubController
Properties
ActiveMatchFinishDeadlineCountdown
Determine remaining time until users active match has to be finished.
public abstract virtual TimeSpan ActiveMatchFinishDeadlineCountdown
{
get;
}
Remarks
Finish deadline can be obtained for any match from match property Deadline.
ActiveMatchStartDeadlineCountdown
Determine remaining time until users active match should start.
public abstract virtual TimeSpan ActiveMatchStartDeadlineCountdown
{
get;
}
Remarks
Start deadline can be obtained for any match by calling tournament.GetMatchStartDeadline(TournamentMatch).
IsConnectedToGameServerNetwork
Determine if tournament match handler is connected to game servers.
public abstract virtual bool IsConnectedToGameServerNetwork
{
get;
}
Remarks
This can be used to inform UI (E.g. tournament handler is switching to preferred tournament server region).
IsGameSessionInProgress
Determine if game session is in progress.
public abstract virtual bool IsGameSessionInProgress
{
get;
}
IsMatchHandlerInitialized
Determine if tournament match handler is initialized.
public abstract virtual bool IsMatchHandlerInitialized
{
get;
}
Remarks
This is set to true after successful call by JoinTournamentMatch(TournamentMatch, ITournamentMatchCallbackHandler). Also set back to false after LeaveTournamentMatch() call.
IsTournamentRunning
Determine if tournament is running (has started).
public abstract virtual bool IsTournamentRunning
{
get;
}
IsUserKnockedOut
Determine if user has been knocked out of the tournament.
public abstract virtual bool IsUserKnockedOut
{
get;
}
NextEventCountdown
Determine remaining time until next deciding event for user. (E.g. remaining time until tournament starts.)
public abstract virtual TimeSpan NextEventCountdown
{
get;
}
PartyInvites
Get all party invites user received for this tournament. This contains also dismissed invites.
public abstract virtual IEnumerable<TournamentPartyInviteNotification> PartyInvites
{
get;
}
Remarks
To refresh user invites (E.g. to get recently received ones) call LoadNotifications() on backbone client.
Status
Get current tournament hub status.
public abstract virtual TournamentHubStatus Status
{
get;
}
Tournament
Get tournament that this controller was initialized for.
public abstract virtual Tournament Tournament
{
get;
}
Methods
IsUserConnectedToMatch(long)
Determine if specific user is connected to tournament match room/lobby.
public abstract virtual bool IsUserConnectedToMatch(long userId)
Parameters
userId
: Backbone user id.
Returns
True if user is connected.
Remarks
This can be used to inform UI (E.g. tournament user is connected in room/lobby).
IsUserReadyForMatch(long)
Determine if specific user is ready for tournament match.
public abstract virtual bool IsUserReadyForMatch(long userId)
Parameters
userId
: Backbone user id.
Returns
True if user is ready.
Remarks
This can be used to inform UI (E.g. user is connected in room/lobby, is set on desired room slot, etc.).
JoinTournamentMatch(TournamentMatch, ITournamentMatchCallbackHandler)
Informs tournament hub that user wants to join a specific match and initiate connection to games room/lobby. It also requires to pass tournament match callback handler (ITournamentMatchCallbackHandler) that is an interface to games room/lobby handling API (E.g. set up a private game room with specific parameters).
public abstract virtual void JoinTournamentMatch(Gimmebreak.Backbone.Tournaments.TournamentMatch match, Gimmebreak.Backbone.Tournaments.ITournamentMatchCallbackHandler tournamentMatchHandler)
Parameters
match
: Tournament match user wants to join.tournamentMatchHandler
: Tournament match handler implementing ITournamentMatchCallbackHandler interfacing room/lobby handling API.
Remarks
After requesting to join a match tournament hub will start receiving tournament match status via callback OnHubMatchStatusChanged(TournamentHubMatchStatus).
LeaveTournamentMatch()
Leave joined tournament match.
public abstract virtual void LeaveTournamentMatch()
Remarks
After requesting to leave a match tournament hub will stop receiving tournament match status via callback OnHubMatchStatusChanged(TournamentHubMatchStatus). This will also prompt tournament match handler to leave joined room/lobby.
RefreshActiveMatch()
User request to refresh UserActiveMatch data.
public abstract virtual void RefreshActiveMatch()
SetUserReady()
User informs tournament that he is ready for next match. This is meant to be used in case when tournament hub status is set to TournamentHubStatus.WaitingForUserReadyConfirmation.
public abstract virtual void SetUserReady()
Remarks
Almost every time when user finishes a match then system does not immediately move users to next round. It also depends on specific phase format if user will be given autolose or moved to next match when round finishes. As an example, in bracket setup, winning users are moved forward when round is finished. As an example, in arena setup, users will be given autolose for failing to call SetUserReady().
API-ITournamentMatchCallbackHandler
Methods
IsConnectedToGameServerNetwork()
Callback from tournament hub to check if client is successfully connected to your networking backend.
public abstract virtual bool IsConnectedToGameServerNetwork()
Returns
True if user is connected and ready to join given match.
IsGameSessionInProgress()
Callback from tournament hub to check if game session is already in progress for connected tournament match.
public abstract virtual bool IsGameSessionInProgress()
Returns
True if game session is in progress.
IsUserConnectedToMatch(long)
Callback from tournament hub to check if specific user is already connected to lobby/room. This method is called for every user that is expected to be in connected tournament match.
public abstract virtual bool IsUserConnectedToMatch(long userId)
Parameters
userId
: Backbone user id.
Returns
True if user is connected.
IsUserReadyForMatch(long)
Callback from tournament hub to check if specific user is ready (E.g. moved to correct slot).
public abstract virtual bool IsUserReadyForMatch(long userId)
Parameters
userId
: Backbone user id.
Returns
True if user is ready to start.
OnJoinTournamentMatch(Tournament, TournamentMatch, ITournamentMatchController)
Callback from tournament hub passing tournament, match and match controller object. Use match data to join correct lobby/room (E.g. using match Secret as room id). Use match controller to inform tournament hub about changes in your lobby/room (E.g. new player connected, player disconnected, etc.).
public abstract virtual void OnJoinTournamentMatch(Gimmebreak.Backbone.Tournaments.Tournament tournament, Gimmebreak.Backbone.Tournaments.TournamentMatch match, Gimmebreak.Backbone.Tournaments.ITournamentMatchController controller)
Parameters
tournament
: Tournament which initiate this match.match
: Tournament match requested to be joined.controller
: Tournament match controller for interaction with related tournament hub.
Remarks
If you require specific parameters for room/lobby API (E.g. map, modes, allowed abilities, etc.), you can use tournament custom properties providing the info. This method is also called only once therefore any room/lobby joining procedure should repeatedly try to connect until OnLeaveTournamentMatch() is called.
OnLeaveTournamentMatch()
Callback from tournament hub informing user should leave joined lobby/room.
public abstract virtual void OnLeaveTournamentMatch()
StartGameSession(IEnumerable<TournamentMatch.User>)
Callback from tournament hub requesting game session to start immediately. Also passing users that successfully checked in for current match. Create tournament game session, and start your game. This might be called multiple times until IsGameSessionInProgress() returns true.
public abstract virtual void StartGameSession(System.Collections.Generic.IEnumerable<Gimmebreak.Backbone.Tournaments.TournamentMatch.User> checkedInUsers)
Parameters
checkedInUsers
: Users that checked in for match. (In case of parties, only users from fully checked in parties)
Remarks
When this method is called, user should initiate CreateGameSession(TournamentMatch.User[], long, byte) on backbone client. After user receives a valid tournament game session it should proceed to start a game.
API-ITournamentMatchController
Methods
ReportDisconnectedUser(long)
Report user id who just disconnected room/lobby.
public abstract virtual void ReportDisconnectedUser(long userId)
Parameters
userId
: Backbone user id.
ReportJoinedUser(long)
Report user id who just joined room/lobby.
public abstract virtual void ReportJoinedUser(long userId)
Parameters
userId
: Backbone user id.
ReportStatusChange()
Report any chage in room/lobby that could affect any user "ready for match" status.
public abstract virtual void ReportStatusChange()
API-TournamentMatch
Constructors
TournamentMatch()
Create an instance of tournament match.
public TournamentMatch()
Properties
CheckedInUserCount
Current number of checked in users.
public int CheckedInUserCount
{
get;
set;
}
CurrentGameCount
Current number of games that have been already played (this includes also auto win games).
public int CurrentGameCount
{
get;
set;
}
Deadline
This is total series deadline after which winner has to be determined.
public DateTime Deadline
{
get;
set;
}
FullyCheckedInTeamCount
Current number of fully checked in teams (all party memebers have checked in for match).
public int FullyCheckedInTeamCount
{
get;
set;
}
GameSessions
List of finished game sessions. List has to be loaded on demand. To populate game sessions use LoadTournamentMatchGameSessions(TournamentMatch) on backbone client.
public List<GameSession> GameSessions
{
get;
set;
}
GroupId
Determine match group id (Only used in tournament phases with groups).
public int GroupId
{
get;
set;
}
Id
Unique match id.
public long Id
{
get;
set;
}
MatchId
Match id is used for brackets, Id is only unique for each round.
public int MatchId
{
get;
set;
}
MaxGameCount
Maximum number of games that can be played for this match.
public int MaxGameCount
{
get;
set;
}
MinCheckinsPerTeam
Determine minimum number of checkins per team to consider team as partially checked in.
public int MinCheckinsPerTeam
{
get;
set;
}
PartiallyCheckedInTeamCount
Current number of partially checked in teams (minimum number of party memebers have checked in for match).
public int PartiallyCheckedInTeamCount
{
get;
set;
}
PhaseId
Determine phase id of this match.
public int PhaseId
{
get;
set;
}
RoundId
Determine round id of this match.
public int RoundId
{
get;
set;
}
Secret
Random 16 char value, only users allowed to join match (players/admins) get this value otherwise null (Can be used as a password for match room).
public string Secret
{
get;
set;
}
Status
Determine current match status.
public TournamentMatchStatus Status
{
get;
set;
}
Users
List of match users.
public List<TournamentMatch.User> Users
{
get;
set;
}
WinScore
Required win score for this match.
public int WinScore
{
get;
set;
}
Methods
GetCheckInTeamUsers()
Get only users of teams who at least partially checked in.
public System.Collections.Generic.IEnumerable<Gimmebreak.Backbone.Tournaments.TournamentMatch.User> GetCheckInTeamUsers()
Returns
Checked in users.
GetMatchUserById(long)
Get match user by id.
public Gimmebreak.Backbone.Tournaments.TournamentMatch.User GetMatchUserById(long userId)
Parameters
userId
: User id.
Returns
Match user.
IsTeamFullyCheckedIn(TournamentMatch.User)
Check if whole team is checked in.
public bool IsTeamFullyCheckedIn(Gimmebreak.Backbone.Tournaments.TournamentMatch.User teamMember)
Parameters
teamMember
: Team member.
Returns
True if whole team is checked in, othwrwise false.
IsTeamFullyCheckedIn(byte)
Check if whole team is checked in.
public bool IsTeamFullyCheckedIn(byte teamId)
Parameters
teamId
: Team id
Returns
True if whole team is checked in, othwrwise false.
IsTeamPartiallyCheckedIn(TournamentMatch.User)
Check if team is partially checked in.
public bool IsTeamPartiallyCheckedIn(Gimmebreak.Backbone.Tournaments.TournamentMatch.User teamMember)
Parameters
teamMember
: Team member.
Returns
True if team is partially checked in, othwrwise false.
IsTeamPartiallyCheckedIn(byte)
Check if team is partially checked in.
public bool IsTeamPartiallyCheckedIn(byte teamId)
Parameters
teamId
: Team id
Returns
True if team is partially checked in, othwrwise false.
API-TournamentPhase
Constructors
TournamentPhase()
Creates a tournament phase.
public TournamentPhase()
Properties
GroupCount
Determine how many groups are in current phase.
public int GroupCount
{
get;
set;
}
GroupSize
Determine maximum number of teams/parties per single group.
public int GroupSize
{
get;
}
HasGroups
Determine if there are groups in current phase.
public bool HasGroups
{
get;
}
Id
Phase id (starts with 1).
public int Id
{
get;
set;
}
IsLoserBracketSeeded
Determine if loser bracket is seeded in double elimination bracket phase.
public bool IsLoserBracketSeeded
{
get;
set;
}
IsSkipAllowed
Determine if phase is allowed to be skipped in case there are less or equal teams in current phase then expected in next phase.
public bool IsSkipAllowed
{
get;
set;
}
MaxLoses
Max loses in this phase (E.g. arena can define that if you lose 3 times you are out).
public int MaxLoses
{
get;
set;
}
MaxPlayers
Max players entering this phase.
public int MaxPlayers
{
get;
set;
}
MaxTeams
Max teams/parties entering this phase.
public int MaxTeams
{
get;
set;
}
MaxTeamsPerMatch
Maximum allowed teams per match.
public int MaxTeamsPerMatch
{
get;
set;
}
MinCheckinsPerTeam
Determine how many players in team needs to checkin in order to be allowed to start a tournament match.
public int MinCheckinsPerTeam
{
get;
set;
}
MinTeamsPerMatch
Minimum required teams per match. This indicates if match should be played or not. Only fully checked in teams/parties counts.
public int MinTeamsPerMatch
{
get;
set;
}
Remarks
In certain phase formats if match does not contain MinTeamsPerMatch until start deadline then all checked in parties will get autowin and are moved to the next round.
Rounds
Phase rounds. Each round can have a different time and win conditions (E.g. each round is best of three, but final round is best of five).
public List<TournamentRound> Rounds
{
get;
set;
}
Scores
Determine all party scores in this phase. It holds summary of points, wins, loses, etc. each party gathered. List has to be loaded on demand. To populate scores use LoadTournamentPhaseScores() on backbone client.
public List<TournamentScore> Scores
{
get;
set;
}
Type
Phase type (E.g. Arena or Bracket).
public TournamentPhaseType Type
{
get;
set;
}
UserGroupId
Determine user group id in current phase.
public int UserGroupId
{
get;
set;
}
UserIsGroupAssigned
Determine if user group id has been assigned. In case there are groups in first phase of a tournament users has to check in first. For a team only party leader will trigger group id assigning process on checkin.
public bool UserIsGroupAssigned
{
get;
}
UserIsParticipating
Determine if user is participating in phase. It can be false if user has been knocked out in previous phase.
public bool UserIsParticipating
{
get;
set;
}
UserLoses
Number of user loses in current phase.
public int UserLoses
{
get;
set;
}
UserPlayedRoundCount
Count of already finished rounds for user in given phase. This increases despite if user did or did not really play/participate in previous rounds.
public int UserPlayedRoundCount
{
get;
set;
}
UserPoints
Number of user points in current phase. This represents total points for match results (not total points scored in game sessions).
public int UserPoints
{
get;
set;
}
UserPositionBottom
Users current bottom position in this phase (E.g.(1-3/8) => 3).
public int UserPositionBottom
{
get;
set;
}
UserPositionTop
Users current top position is this phase (E.g. (1-3/8) => 1).
public int UserPositionTop
{
get;
set;
}
Methods
GetRoundById(int)
Gets phase round by specific id.
public Gimmebreak.Backbone.Tournaments.TournamentRound GetRoundById(int roundId)
Parameters
roundId
: Round id (first round always starts with id=1).
Returns
Tournament round or null if round does not exists.
GetScoreByPartyId(long)
Get phase score record by party id. Note that scores has to be loaded prior using this method otherwise the return value will be NULL. Use LoadTournamentPhaseScores() on backbone client.
public Gimmebreak.Backbone.Tournaments.TournamentScore GetScoreByPartyId(long partyId)
Parameters
partyId
: Party id for which we want score record
Returns
Score record or null if it does not exist (or not loaded)
GetScoreByUserId(long)
Get phase score record by user id. Note that scores has to be loaded prior using this method otherwise the return value will be NULL. Use LoadTournamentPhaseScores() on backbone client.
public Gimmebreak.Backbone.Tournaments.TournamentScore GetScoreByUserId(long userId)
Parameters
userId
: User id for which we want score record
Returns
Score record or null if it does not exist (or not loaded)
API-TournamentRound
Constructors
TournamentRound()
Creates an instance of tournament round.
public TournamentRound()
Properties
GamePointDistribution
Determine point distribution for specific position for each game in this round.
public List<TournamentRound.GamePositionPoints> GamePointDistribution
{
get;
set;
}
Id
Round id (starting from 1).
public int Id
{
get;
set;
}
MatchPointDistribution
Determine point distribution for specific position for each match in this round.
public List<TournamentRound.MatchPositionPoints> MatchPointDistribution
{
get;
set;
}
MaxGameCount
Maximum allowed games to be played in match (E.g. if we set this to 2 then we effectively created BO2, this would not work if we set WinScore to 1 in 1v1 scenario).
public int MaxGameCount
{
get;
set;
}
MaxLength
Maximulm length for this round, all games should end within this window. This should be greater than MinGameLength * MAXPOSSIBLEGAMECOUNT plus some margin.
public int MaxLength
{
get;
set;
}
MinGameLength
Minimum game length, all games should try to fit into this window (E.g. round is best of 3 (max 3 games) and minimum game length is 2 minutes then Max length should be at least 6 minutes to allow all games to be played if necessary).
public int MinGameLength
{
get;
set;
}
WinScore
Winning score required for match to be closed (E.g. if we give any points for game win then best of 3 would be represented by WinScore=2 (WinScore = how many times user places on scored position)).
public int WinScore
{
get;
set;
}
API-Tournament
Constructors
Tournament()
Create an instance of tournament.
public Tournament()
Properties
AdditionalDescription
Get additional description of this tournament if any.
public string AdditionalDescription
{
get;
set;
}
AllMatches
All tournament matches from all phases. This field can be populated with LoadTournamentMatches() or LoadTournamentMatchesAll() call on backbone client.
public List<TournamentMatch> AllMatches
{
get;
set;
}
CurrentInvites
Determine current active invites/registrations for tournament. This always represents a single user even if tournament is set to have parties/teams.
public int CurrentInvites
{
get;
set;
}
CurrentPhaseId
Determine current active phase Id (If id=0 then tournament was not initiated yet, first phase always starts with id=1).
public int CurrentPhaseId
{
get;
set;
}
CurrentPhaseStarted
Determine when current phase started. This is UTC datetime.
public DateTime CurrentPhaseStarted
{
get;
set;
}
CustomProperties
Get tournament custom properties. (e.g. game mode, allowed abilities, allowed maps, etc.)
public TournamentCustomProperties CustomProperties
{
get;
set;
}
Description
Get description of this tournament if any.
public string Description
{
get;
set;
}
EntryFee
Determine entry fee of this tournament. This will not be null even if tournament does not have any entry fees set up, however EntryFee.Items will be empty.
public TournamentEntryFee EntryFee
{
get;
set;
}
HasAllDataLoaded
Determine if all tournament data were loaded.
public bool HasAllDataLoaded
{
get;
set;
}
Remarks
This is set to false if tournament was loaded only with LoadTournamentList() call on backbone client. This is set to true if tournament was loaded with LoadTournament() call on backbone client.
HighlightsUrl
Get highlights url of tournament if any.
public string HighlightsUrl
{
get;
set;
}
IconUrl
Get icon url of this tournament if any.
public string IconUrl
{
get;
set;
}
Id
Tournament unique id.
public long Id
{
get;
set;
}
ImageUrl
Get image url of this tournament if any.
public string ImageUrl
{
get;
set;
}
InvitationCloseTime
Determine when invitation closes. After this point nobody can sign up or sign out. This is UTC datetime.
public DateTime InvitationCloseTime
{
get;
set;
}
InvitationOpenTime
Determine when invitation opens. This is UTC datetime.
public DateTime InvitationOpenTime
{
get;
set;
}
Remarks
When invitation opens then only user who has a direct invite can sign up. Direct invites can represent winners from previous tournaments or specific users set up in dashboard. This means you can give time priority to those users for sign up before public registration opens.
Invite
Get tournament invite/ticket. If user was invited to the tournament, ticket is available but not confirmed. This property is null if user was not automatically invited or signed up for the tournament.
public TournamentInvite Invite
{
get;
set;
}
IsAdministrator
Determine if user is administrator of this tournament. Administrator can access match secrets when browsing matches and therefore join as spectator.
public bool IsAdministrator
{
get;
set;
}
IsInvitationOnly
Determine if tournament is only for invited users. If set to true then RegistrationOpenTime should not be used as it does not contain valid value.
public bool IsInvitationOnly
{
get;
set;
}
LastMatchRoundId
Determine last round id of match that user played.
public int LastMatchRoundId
{
get;
set;
}
LastUpdate
Determine last tournament data update.
public DateTime LastUpdate
{
get;
set;
}
MaxInvites
Determine max invites/registrations for tournament. This always represents a single user even if tournament is set to have parties/teams.
public int MaxInvites
{
get;
set;
}
NextPhase
Determine time when next phase will start. This is UTC datetime.
public DateTime NextPhase
{
get;
set;
}
Remarks
If all matches have been played in current phase then tournament might progress to next phase sooner to prevent unnecessary wating for users.
Party
Get user party (signup user is always in a party, even in 1v1). This property is null until user signs up for the tournament.
public TournamentParty Party
{
get;
set;
}
Remarks
Signed up user is always in a party. If user leaves party then he is automatically assigned a new one.
PartySize
Determine requried party size for tournament (E.g. 1v1, 2v2, 3v3, etc).
public int PartySize
{
get;
set;
}
PhaseCount
Determine how many phases are in tournament (always at least one).
public int PhaseCount
{
get;
set;
}
Phases
List of all tournament phases. Tournament always has at least one phase (PhaseId = 1).
public List<TournamentPhase> Phases
{
get;
set;
}
PolicyURL
Get policy url of this tournament if any.
public string PolicyURL
{
get;
set;
}
PrivateCode
Get private code of this tournament. Can be shared with other users to sign up for private tournaments.
public string PrivateCode
{
get;
set;
}
Remarks
Private code is generated only if this type of invite is enabled in dashboard.
Prizes
List of tournament prizes.
public List<TournamentPrize> Prizes
{
get;
set;
}
RegistrationOpenTime
Determine when registration opens. This property is set only if IsInvitationOnly is set to false. This is UTC datetime.
public DateTime RegistrationOpenTime
{
get;
set;
}
Remarks
When registration opens then any user can sign up.
ReplayExpires
Determine when replay url expires (e.g. twitch after 14 days) This is UTC datetime.
public DateTime ReplayExpires
{
get;
set;
}
ReplayUrl
Get replay url of tournament if any. (E.g. twitch VOD)
public string ReplayUrl
{
get;
set;
}
Requirements
Determine tournament requiremts for signup. This will not be null even if tournament does not have any custom requirements set up, however Requirements.CustomRequirements will be empty.
public TournamentRequirements Requirements
{
get;
set;
}
RoundCount
Determine how many rounds in total are in tournament (always at least one).
public int RoundCount
{
get;
set;
}
Season
Determine which season tournament is created in.
public int Season
{
get;
set;
}
SeasonPart
Determine which season part tournament is created in.
public int SeasonPart
{
get;
set;
}
SponsorImageUrl
Get sponsor image.
public string SponsorImageUrl
{
get;
set;
}
SponsorName
Get sponsor name.
public string SponsorName
{
get;
set;
}
Status
Current status of tournament. (E.g. Invitation Open, Finished, etc.) To get more detailed statuses you have to connect tournament with tournament hub.
public TournamentStatus Status
{
get;
set;
}
StreamUrl
Get tournament stream url if any.
public string StreamUrl
{
get;
set;
}
ThemeColor
Get theme color of this tournament if any.
public string ThemeColor
{
get;
set;
}
Time
Determine start time of tournament. This is UTC datetime.
public DateTime Time
{
get;
set;
}
TournamentName
Tournament official name.
public string TournamentName
{
get;
set;
}
Type
Determine type of tournament. (E.g. public, private, testing) Some tournament types are only visible to certain users.
public TournamentType Type
{
get;
set;
}
UserActiveMatch
Currently active match for the user which he should join. This is only applicable for users that are signed up for the tournament.
public TournamentMatch UserActiveMatch
{
get;
set;
}
Remarks
This property is automatically set when user connects to tournament hub. In some situations user will be asked by tournament hub to explicitly confirm that he is ready before tournament match is assigned.
UserMatches
All user matches from all phases.
public List<TournamentMatch> UserMatches
{
get;
set;
}
Remarks
If user failed to start certain rounds on time and totally skiped some round matches (autolose) then those matches will not be present in this list. Use UserPlayedRoundCount property on tournament phase object to determine if user actually missed a match.
Winner
Get basic tournament winner data. This contains list of all users in all parties that finished on first position.
public TournamentWinner Winner
{
get;
set;
}
Remarks
If last phase of tournament has groups then tournament can have multiple winners as final position is calculated within a group.
Methods
GetCurrentTournamentPhase()
Get current tournament phase.
public Gimmebreak.Backbone.Tournaments.TournamentPhase GetCurrentTournamentPhase()
Returns
Tournament phase or null if tournament has not started yet. Finished tournament always returns last phase.
GetMatchMinGameLength(TournamentMatch)
Get minimum lenght (in minutes) required for a game to be played.
public int GetMatchMinGameLength(Gimmebreak.Backbone.Tournaments.TournamentMatch match)
Parameters
match
: Tournament match.
Returns
Minimum lenght (in minutes).
GetMatchNextGameDeadline(TournamentMatch)
WARNING This item is deprecated. Use GetMatchNextGameFinishDeadline() instead.
Calculate a finish deadline for next game within a match. This deadline is calculated in a way there is still at least minimum game time for each game that has to be played.
public System.DateTime GetMatchNextGameDeadline(Gimmebreak.Backbone.Tournaments.TournamentMatch match)
Parameters
match
: Tournament match.
Returns
Maximum possible finish deadline for a next game.
GetMatchNextGameFinishDeadline(TournamentMatch)
Calculate a finish deadline for next game within a match. This deadline is calculated in a way there is still at least minimum game time for each game that has to be played.
public System.DateTime GetMatchNextGameFinishDeadline(Gimmebreak.Backbone.Tournaments.TournamentMatch match)
Parameters
match
: Tournament match.
Returns
Maximum possible finish deadline for a next game.
GetMatchNextGameStartDeadline(TournamentMatch)
Calculate a start deadline for next game within a match. This deadline is calculated in a way there is still at least minimum game time for each game that has to be played.
public System.DateTime GetMatchNextGameStartDeadline(Gimmebreak.Backbone.Tournaments.TournamentMatch match)
Parameters
match
: Tournament match.
Returns
Maximum possible start deadline for a next game.
GetMatchStartDeadline(TournamentMatch)
Determine deadline after which match has to start in order to be able accommodate all required games each to be played with at least minimum game time.
public System.DateTime GetMatchStartDeadline(Gimmebreak.Backbone.Tournaments.TournamentMatch match)
Parameters
match
: Tournament match.
Returns
Deadline after which match has to start.
GetThemeColor()
Get tournament theme color.
public UnityEngine.Color GetThemeColor()
Returns
Unity color.
GetTournamentMatchById(long)
Get tournament match by its unique id.
public Gimmebreak.Backbone.Tournaments.TournamentMatch GetTournamentMatchById(long tournamentMatchId)
Parameters
tournamentMatchId
: Tournament match id.
Returns
Tournament match or null if match cannot be found.
Remarks
This method searches for the match locally and does not access server data. If match id is valid but method return null populate local data with LoadTournamentMatches() on backbone client.
GetTournamentMatchMaxGameCount(TournamentMatch)
Determine maximum allowed number of games to be played in specific match. Once this value is reached then system will determine winners by scored points from all played games.
public int GetTournamentMatchMaxGameCount(Gimmebreak.Backbone.Tournaments.TournamentMatch match)
Parameters
match
: Tournament match.
Returns
Maximum allowed number of games.
GetTournamentMatchMinCheckinsPerTeam(TournamentMatch)
Determine required win score for specific match. Win score determines how many times user has to place on scored position in order to win the match.
public int GetTournamentMatchMinCheckinsPerTeam(Gimmebreak.Backbone.Tournaments.TournamentMatch match)
Parameters
match
: Tournament match.
Returns
Win score for specific match.
GetTournamentMatchWinScore(TournamentMatch)
Determine required win score for specific match. Win score determines how many times user has to place on scored position in order to win the match.
public int GetTournamentMatchWinScore(Gimmebreak.Backbone.Tournaments.TournamentMatch match)
Parameters
match
: Tournament match.
Returns
Win score for specific match.
GetTournamentPhaseById(int)
Get specific tournament phase by its id.
public Gimmebreak.Backbone.Tournaments.TournamentPhase GetTournamentPhaseById(int phaseId)
Parameters
phaseId
: Tournament phase id (first phase always starts with id=1).
Returns
Tournament phase or null if phase does not exists.
GetTournamentPhaseFinishTime(int)
Get finish timestamp of specific tournament phase.
public System.DateTime GetTournamentPhaseFinishTime(int phaseId)
Parameters
phaseId
: Phase id.
Returns
Finish timestamp of requested phase.
GetTournamentPhaseMaxLength(int)
Get tournament phase maximum time lenght in minutes. This is a sum of all round MaxLength values.
public int GetTournamentPhaseMaxLength(int phaseId)
Parameters
phaseId
: Phase id.
Returns
Length of tournament phase in minutes.
GetTournamentPhaseRoundFinishTime(int, int)
Get round finish timestamp in currently active tournament phase.
public System.DateTime GetTournamentPhaseRoundFinishTime(int phaseId, int roundId)
Parameters
phaseId
: Phase id.roundId
: Round id.
Returns
Finish timestamp of requested round.
GetTournamentPhaseRoundStartTime(int, int)
Get round start timestamp in currently active tournament phase.
public System.DateTime GetTournamentPhaseRoundStartTime(int phaseId, int roundId)
Parameters
phaseId
: Phase id.roundId
: Round id.
Returns
Start timestamp of requested round.
GetTournamentPhaseStartTime(int)
Get start timestamp of specific tournament phase.
public System.DateTime GetTournamentPhaseStartTime(int phaseId)
Parameters
phaseId
: Phase id.
Returns
Start timestamp of requested phase.
IsMatchInLoserBracket(TournamentMatch)
Determine if match is in loser bracket. This is used for double elimination braket format.
public bool IsMatchInLoserBracket(Gimmebreak.Backbone.Tournaments.TournamentMatch match)
Parameters
match
: Tournament match.
Returns
True if match is in loser bracket.
IsMatchInWinnerBracket(TournamentMatch)
Determine if match is in winner bracket. This is used for double elimination braket format.
public bool IsMatchInWinnerBracket(Gimmebreak.Backbone.Tournaments.TournamentMatch match)
Parameters
match
: Tournament match.
Returns
True if match is in winners bracket.
IsMatchMergingInLoserBracket(TournamentMatch)
Determine if match is merging within loser bracket. This is used for double elimination braket format.
public bool IsMatchMergingInLoserBracket(Gimmebreak.Backbone.Tournaments.TournamentMatch match)
Parameters
match
: Tournament match.
Returns
True if match is merging in loser bracket.
Remarks
Merging in loser bracket meant that winner of loser bracket match will face another winner of loser bracket match. In opposite case, if match is not merging then winner of loser bracket will face loser of winner bracket therefore number of loser bracket matches will be same in next round. This method helps with UI visualisation of double elimination brackets.
- Constructors
- Properties
- AdditionalDescription
- AllMatches
- CurrentInvites
- CurrentPhaseId
- CurrentPhaseStarted
- CustomProperties
- Description
- EntryFee
- HasAllDataLoaded
- HighlightsUrl
- IconUrl
- Id
- ImageUrl
- InvitationCloseTime
- InvitationOpenTime
- Invite
- IsAdministrator
- IsInvitationOnly
- LastMatchRoundId
- LastUpdate
- MaxInvites
- NextPhase
- Party
- PartySize
- PhaseCount
- Phases
- PolicyURL
- PrivateCode
- Prizes
- RegistrationOpenTime
- ReplayExpires
- ReplayUrl
- Requirements
- RoundCount
- Season
- SeasonPart
- SponsorImageUrl
- SponsorName
- Status
- StreamUrl
- ThemeColor
- Time
- TournamentName
- Type
- UserActiveMatch
- UserMatches
- Winner
- Methods
- GetCurrentTournamentPhase()
- GetMatchMinGameLength(TournamentMatch)
- GetMatchNextGameDeadline(TournamentMatch)
- GetMatchNextGameFinishDeadline(TournamentMatch)
- GetMatchNextGameStartDeadline(TournamentMatch)
- GetMatchStartDeadline(TournamentMatch)
- GetThemeColor()
- GetTournamentMatchById(long)
- GetTournamentMatchMaxGameCount(TournamentMatch)
- GetTournamentMatchMinCheckinsPerTeam(TournamentMatch)
- GetTournamentMatchWinScore(TournamentMatch)
- GetTournamentPhaseById(int)
- GetTournamentPhaseFinishTime(int)
- GetTournamentPhaseMaxLength(int)
- GetTournamentPhaseRoundFinishTime(int, int)
- GetTournamentPhaseRoundStartTime(int, int)
- GetTournamentPhaseStartTime(int)
- IsMatchInLoserBracket(TournamentMatch)
- IsMatchInWinnerBracket(TournamentMatch)
- IsMatchMergingInLoserBracket(TournamentMatch)
AdvancedFeatures
Setting up advanced features on dashboard
How to set up 1v1, 2v2 etc.
1v1
Go to Registration
and set Party(team) size
to 1
, so we could only have 1 player per team.
Depending on amount of player you expecting to participate, set Maximum players
to N
, where N
can be any whole number greater than 1
.
After setting up Registration
go to Format
and add a new Phase
.
In Phase
find Teams
and fill the field with N
set previously for Maximum players
.
2v2
Go to Registration
and set Party(team) size
to 2
, so we could only have 2 players per team.
Depending on amount of player you expecting to participate, set Maximum players
to N
, where N
can be any whole even number greater than 2
, like 4, 6, 8, etc.
After setting up Registration
go to Format
and add a new Phase
.
In Phase
find Teams
and fill the field with N/2
set previously for Maximum players
. So if N
is equal to 8, Teams
should be 4.
NvN
Go to Registration
and set Party(team) size
to N
, so we could only have N players per team.
Depending on amount of player you expecting to participate, set Maximum players
to N*T
, where T
is number of teams you expect to participate.
After setting up Registration
go to Format
and add a new Phase
.
In Phase
find Teams
and fill the field with T
used previously for Maximum players
.
How to set up tournament for BO1 - BO5
Go to Format
, find field Rounds
and there in field Type
you can choose options from BO1
to BO5
or Custom
.
With rounds from BO1
to BO5
you can set only Minimum game time (minutes)
and Maximum round time (minutes)
.
Set timing for BO1-BO5
To set time for rounds, go to Format
/Rounds
and assuming we have a BO3 game where each match is about 10 minutes long, Maximum round time (minutes)
would be 45 minutes and Minimum game time (minutes)
would be 15 minues. Which means that round can be finished after at least 15 minutes after it's beginning and can last up to 45 minutes.
Correct? -> If Maximum round time (minutes)
has expired and last game wasn't finished, round will be considered as finished anyways, so it's better to have some additional time in case of unexpected changes or issues.
If Maximum round time (minutes)
has expired before last game has started, round will be considered as finished anyways, so it's better to have some additional time in case of unexpected changes or issues.
Custom
If Custom
option is chosen, then you can set amount of rounds need to be won for victory in field Score to win
.
Restricting number of games with field Maximum number of games
defines what is the limit of the games that can be played in order to define winner.
Maximum number of games
must be greater than Score to win
, as Score to win
defines amount of games that must be won out of Maximum number of games
in order to define winner.
How to set up fees and prize pool
Entry Fees
In order to set up Entry Fee
you first have to go to Store
, then add there Currencies
and Items
.
Get back to Template settings
and go to Entry fee
. If Currency
or Item
were added, then they can be seen in right part of the screen.
Now just drag&drop any of them into Drag and drop items here
field. If you don't have predefined amount of Currency
or Item
that will be a reward for participants.
You can tick the box on the right side of the added Currency
or Item
under Add to pot
column, then Currency
and Item
will be summed up to present prize pool. Save changes in the popping up, on the bottom of the page, notification.
Go to Rewards
, there you will have to add rewards rule
. In appeared form, there is Place
field where you can define which places will be rewarded.
If you want to have a dynamic prize pool find Shared Pot
field and enter the percentage from overall prize pool to be devided among places defined in Place
field.
NB! IF Shared Pot
DIDN'T APPEAR ON THE Rewards
PAGE, TRY TO RELOAD IT AND MAKE SURE YOU SAVED CHANGES AFTER Add to pot
IS TICKED
If you want to have both dynamic prize pool and additional reward, you can drag&drop from the right table Currency
or Item
that will be a reward, to Drag and drop items here
field.
Now let's assume only first four places will have a reward, that will consist from fifty percent of prize pool and 5 additional items, then in Place
we enter 4, in Shared pot
field enter 50, drag&drop item from right column and set amount to 5. Notice that on the right part of the row for Shared pot
there is Each place
field that shows how many percent from overall prize pool each player from first to fourth place will receive.
If you want to split reward between 32 player so that first 8 places would receive 50% from overall prize pool, then from 8th to 16th would receive 25% and from 16th to 32nd would receive 15%, you have to add 3 rewards rule
forms, and enter percentage and places accordingly.