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

  1. To open MainScene go to Assets/Scenes/MainScene

    MainScene

  2. Create two new objects in MainScene/App and rename them to TournamentListPanel and TournamentHubPanel.

    SetUp

  3. For both TournamentListPanel and TournamentHubPanel set Rect Transform to stretch and re-set Left/Right/Top/Bottom to 0.

    SetUp

  4. Add Panel Component to both TournamentListPanel and TournamentHubPanel.

    SetUp

  5. Go to Assets/TournamentSDK_Demo/Prefabs/DefaultUIScreens.

  6. Drag&Drop TournamentHubScreen and TournamentListScreen prefabs to TournamentHubPanel and TournamentListPanel respectively.

    SetUp

  7. Find SharedMode in App/StartUI/SharedMode, duplicate it and rename to TournamentMode and change it's PosX param in Rect Transform to 541.

    SetUp

  8. Go to TournamentMode/Text(TMP)(1) change Text component to Tournament Mode.

  9. Go to TournamentMode/BtnShared rename it to BtnTournaments, in BtnTournament/Text (TMP) change Text component to Tournaments.

Before setting up the UI buttons, let's adjust game code to it.

GameLauncher code update for UI usage

Open GameLauncher script:

  1. 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;
    
  2. Add necessary methods to enable switching among MainMenu/TournamentListScreen/TournamentHubScreen.

    Allows to redirect player from MainMenu to TournamentListScreen.

    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 between TournamentListScreen/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;
    }
    
  3. 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);
    }
    
  4. Get back to App in Hierarchy find GameLauncher component and add missing Panels and GUI

    SetUp

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

UI

TournamentListScreen to MainMenu

To allow player to return back from TournamentListPanel to MainMenu:

  1. Go to TournamentListPanel/TournamentListScreen/SubScreen/Canvas/Title/ButtonUnderline(Back).

  2. Remove GUISubScreen.Back, by pressing - under OnClick().

    UI

  3. Add two new actions, by pressing + under OnClick().

  4. Drag&drop App from Hierarchy to empty field under Runtime Only for both.

  5. Set them from No Function to GameLauncher.ForceInvisible and GameLauncher.ForceVisible.

  6. Drag&drop TournamentListPanel and StartUI respectively.

    UI

TournamentListScreen to TournamentHubScreen

  1. Go to TournamentListPanel/TournamentListScreen/SubScreen.

  2. Drag&drop TournamentHubPanel/TournamentHubScreen/SubScreen into field TournamentHubScreen in GUITournamentListScreen.

    UI

  3. Go to TournamentListPanel/TournamentListScreen/SubScreen/Canvas/ScrollView/Viewport/Content/ButtonUnderline(Open).

  4. Add two new OnClick() actions.

  5. Drag&drop App from Hierarchy to empty field under Runtime Only for both.

  6. Set them to GameLauncher.ForceInvisible and GameLauncher.ForceVisible.

  7. Drag&drop TournamentListPaenl and TournamentHubPanel respectively.

    UI

  8. Repeat for TournamentListPanel/TournamentListScreen/SubScreen/Canvas/ScrollView/Viewport/Content/ButtonUnderline(Play).

TournamentHubScreen to TournamentListScreen

To allow player to return back from TournamentHubPanel to TournamentListPanel:

  1. Go to TournamentHubPanel/TournamentHubScreen/SubScreen.

  2. In GUISubScreen component fill empty field Return Screen with TournamentListPanel/TournamentListScreen/SubScreen.

    UI

  3. Go to TournamentHubPanel/TournamentHubScreen/SubScreen/Canvas/Title/ButtonUnderline(Back)

  4. Add two more actions.

  5. Set them to GameLauncher.ForceInvisible and GameLauncher.ForceVisible as previously.

  6. Drag&drop TournamentHubPanel and TournamentListPanel respectively.

    UI

Backbone integration

To gather information about available tournaments we first need to log in player.

  1. Create new empty object at the end of MainScene.

  2. Rename it to BackboneManager.

  3. Add BackboneManager and ResourceCache components to it.

  4. Create new script called BackboneIntegration and add it to BackboneManager.

    BBM

    Make sure Initialize On Start field is Disabled

  5. 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 in BackboneManager now misses TournamentButton, go ahead and add it.

    BBM

    BackboneIntegration starts BackboneManager 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.

  1. Open Player script:

    1. 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;
      
    2. 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);
      }
      
    3. 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;
      }
      
    4. Change method Despawned() to:

      public override void Despawned(NetworkRunner runner, bool hasState)
      {
      	Destroy(_deathExplosionInstance);
      	PlayerManager.RemovePlayer(this);
          OnPlayerDespawned(this.TournamentUserId);
      }
      
  2. 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;
    }
    
  3. Go to GameManager script:

    1. Add variable to track if result processing has started:

      public bool processingStarted = false;
      
    2. 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);
                      }
                  }
              }
          }
      }
      
    3. 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

  1. Go to App/TournamentHubPanel/TournamentHubScreen/SubScreen/Canvas.

  2. Add new game object, rename it to TournamentMatchHandler.

    TMH

  3. Create new script called TournamentMatchHandler, add it as component to TournamentMatchHandler game object.

  4. 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);
            }
        }
    }
    
  5. Go to App/TournamentHubPanel/TournamentHubScreen/SubScreen/Canvas/TournamentMatchHandler, in TournamentMatchHandler component will be unset TournamentHubPanel, drag&drop TournamentHubPanel from Hierarchy into empty field.

    TMH

  6. Go to App/TournamentHubPanel/TournamentHubScreen/SubScreen/Canvas/ActiveMatchContainer, drag&drop TournamentMatchHandler into MatchHandler for GUITournamentActiveMatch component.

    TMH

    Before running the project, disable both TournamentListPanel and TournamentHubPanel.

    TMH

    Make sure TournamentListPanel/TournamentListScreeb/SubScreen is enabled.

    TMH

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

  1. Go to https://www.tournament-sdk.com/tournaments

  2. There you will see No tournament templates .Create your first template to get started.

  3. Press Create your first template.

    TTemplate

Edit template

  1. Tournament template will appear. Select Edit template.

    TTemplate

  2. Go to Description and set Tournament Name.

    TTemplate

  3. Go to Registration set:

  • Maximum players - 2

  • Party(team) size - 1

  • Registration rules -> Open to everyone

    TTemplate

  1. Go to Format/Add Phase

In Format set:

  • Teams - 2
  • Min teams per match - 2
  • Max Teams per match - 2

Leave field Max loses in Scores empty

In Rounds set:

  • Type - BO3
  • Minimum game time (minutes) - 2
  • Maximum round time (minutes) - 8
  1. On the bottom of the screen you should see Careful - you have unsaved changes!, press Save Changes.

    TTemplate

Start tournament

  1. Get back to Tournament templates page.

  2. Press Schedule, set Time to Your current time + 5 minutes and press Start tournament.

    TTemplate

Final test

  1. Now get back to Unity and start the project.

  2. Press Tournaments button that was added earlier. You should see TournamentListScreen and the tournament that you just added.

    FinalTest

    Tournament may be unavailable to register for some time, wait until Sign up button is available to register to tournament.

    FinalTest

  3. Repeat the process in build version

  4. When tournament will start, button Ready to play will become available.

    FinalTest

  5. Press on both Unity and Build Ready to play.

    FinalTest

    FinalTest

  6. Finish the match on both players.

  7. Get back to: https://www.tournament-sdk.com/schedule

  8. You should see tournament.

    FinalTest

  9. Click on it, to see more information about the tournament and played matches.

  10. To check more detailed information about every match played during the tournament, go to Phase 1.

  11. Press Show matches to the right from any participant.

  12. Click Show details for more information.

    FinalTest

  13. Click Game #ID and Stats for more information.

    FinalTest