Custom Unity Integration
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
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
.
Authenticate 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
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.
Initialize Tournament Hub
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()
.