Fusion
Tanks (BattleRoyale)
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.
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
Start by importing Tournament-SDK to your Unity project.
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
-
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
- 2 -
Party(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.