This document is about: QUANTUM 2
SWITCH TO

Unity Plugin


Available in the Gaming Circle and Industries Circle
Circle

如何開始

安裝Unity外掛程式

下載及安裝Unity套件(一旦獲得SDK存取權限,您將會取得下載連結)。
在Unity中以您一般的方式以Assets/Import package/Custom Package來匯入套件。

這將包含在您的遊戲客戶端中運行錦標賽所需的DLL。

建立新的遊戲並且設定遊戲ID

當所有檔案都已匯入到您的遊戲/專案,您將需要建立一個 遊戲ID
為了完成這個,登入錦標賽儀表板,按一下新增新的遊戲按鈕,並且跟隨精靈的步驟,直到您完成建立您的遊戲。

Add game image

隨後開啟Game settings/General,在其中您可以找到您的 遊戲ID

Get gameid image

下一步,在您的Unity專案中,找到已匯入的檔案名為:
BackboneClientSetting.asset,其應該位於Plugins/Gimmebreak.Backbone/Resources/資料夾。
開啟檔案並且複製貼上您的 遊戲ID,如下所示。

Set gameid image

初始化SDK客戶端

新增BackboneManager指令碼到您的場景物件。
如果您希望在調用Unity的Start()時,初始化客戶端,請勾選敘述 在啟動時初始化 的勾選框。

Add backbone manager image
Set backbone manager image

如果您希望手動地初始化客戶端(比如,當您檢查網際網路連線能力時),您可以稍後以一個明確的調用來完成它。

C#

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
        }
    }
}

您可以透過使用BackboneManager.IsInitialized來檢查客戶端是否被初始化。

登入使用者

在儀表板啟用登入提供者

開啟錦標賽儀表板並且導航到:Game Settings/Authentication providers
啟用與您的遊戲相關的提供者。
針對各個已啟用的提供者,填入所需的設定並且儲存。

Login providers image

登入使用者

如果您的登入提供者已經設定完成,您可以繼續來登入一位使用者。
在客戶端初始化後,您可以檢查一位使用者是否已經登入。
如果沒有,您可以繼續操作登入。

在這個示例中,我們使用Steam授權:

C#

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);
    }
}

這將使用Steam登入提供者來讓使用者登入。
在使用著成功登入之後,您可以與客戶端API互動。

基本錦標賽操作

取得錦標賽清單

一個所有錦標賽的清單可在此找到:

C#

var allTournaments = BackboneManager.Client.Tournaments.TournamentList;

為了載入或重新整理清單(在登入後它可以是空的),我們必須調用BackboneManager.Client.LoadTournamentList()操作。

C#

 //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);

注意事項: 這個操作傳回 布林值 結果,其指出清單是否已經成功重新整理。
如果太常調用這個操作,它將傳回錯誤。
目前的「允許的重新整理限制」被設定為1分鐘。
您可以透過註冊來取得操作結果。

ResultCallback((result) => {})如下:

C#

 //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);

在載入錦標賽清單之後,不是所有錦標賽層級的屬性都會被填入。
為了載入所有錦標賽資料,必須調用BackboneManager.Client.LoadTournament(tournamentId)
錦標賽層級含有一個旗標:HasAllDataLoaded,可以檢查是否它已經載入所有資料。

取得錦標賽

為了載入/重新整理所有錦標賽資料,請調用:BackboneManager.Client.LoadTournament(tournament)
這個操作傳回 布林值,其指出錦標賽是否已成功被載入/重新整理。
同時,在錦標賽層級上有一個屬性:tournament.HasAllDataLoaded,其指出所有資料是否已經在先前被載入。

C#

//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);

註冊錦標賽

為了檢查一名使用者是否已經註冊,請查看錦標賽資產:tournament.Invite
如果邀請是 空值,則使用者沒有一個邀請,並且它也沒有被確認。
為了檢查一個邀請是否可用,請檢查:tournament.Invite.Status,其持有使用者的錦標賽邀請狀態。

C#

//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
}

為了針對一個錦標賽來註冊使用者,請調用:SignupForTournament(tournamentId)

C#

//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);

這個操作傳回一個 邀請結果 物件,其含有關於註冊結果的資訊。
如果針對註冊流程的外部伺服器是在儀表板中被設定(比如,為了扣除貨幣、物品等等),inviteResult.IsExternalSignupError將指出在這個操作中是否有問題。
同時,任何由自訂伺服器擲出的錯誤訊息,可在inviteResult.ErrorMessage中找到。

錦標賽中樞

初始化錦標賽中樞

錦標賽中樞針對錦標賽來作為一個自訂大廳。
當連接之後,它將提供關於對戰及錦標賽的進度/狀態的資訊。
為了接收錦標賽中樞回調的,請執行ITournamentHubCallbackHandler介面。

C#

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
}

為了初始化錦標賽中樞,請調用:ConnectTournamentHub(callbackHandler, tournament);,在其中回調處理常式是執行 I錦標賽中樞回調處理常式 的物件。

C#

var tournament = BackboneManager.Client.Tournaments.GetTournamentById(tournamentId);
BackboneManager.Client.ConnectTournamentHub(this, tournament);

當初始化錦標賽中樞後,它將調用:OnInitialized(ITournamentHubController controller)並且傳回一個控制器,其用於從使用者角度來控制特定錦標賽流程(比如,指出一名使用者已經準備好進行下一場對戰)。

錦標賽中樞狀態

OnHubStatusChanged(TournamentHubStatus newStatus)將從一個使用者的觀點來指出錦標賽的目前的狀態。您的UI應該對任何更改作出相應的回應。

C#

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;
    }
}

使用者的啟用中對戰

當一個錦標賽正在運行中,並且一個使用者已經註冊,他們將自動地得到一個Active match,其含有所有的中繼資料及狀態。
一名使用者的啟用中的對戰可以透過如下方式從錦標賽中樞存取:tournamentHub.Tournament.UserActiveMatch

UserActiveMatch.Secret可作為一個網路房間/大廳密碼或名稱。
這只會分配給被允許加入一個特定對戰的使用者。

UserActiveMatch.Status應該用於決定一名使用者;是否正在等待其他使用者/對手,在遊戲正在進行中時是否必須連接到一個特定的房間/大廳,或是使用者是否必須繼續前往另一個對戰。

C#

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;
}

您可以決定對手是否準備好,方法是檢查:UserActiveMatch.Users[i].IsCheckedIn
您可以 重新整理UserActiveMatch資料,方法是調用:tournamentHubController.RefreshActiveMatch()

請注意,UserActiveMatch可以是空值,或它可以在已完成狀態。為了 請求另一個對戰 並且繼續進行錦標賽,使用者必須明確地調用tournamentHubController.SetUserReady()
當錦標賽中樞將進入一個狀態TournamentHubStatus.WaitingForUserReadyConfirmation時,使用者被期待做這個。這個動作可透過UI來代表(比如,準備好下一場對戰),或是它可以 被自動完成 而不需要使用者互動,當錦標賽中樞進入狀態時立即將他移到下一場對戰。

已加入的對戰介面

如果您的遊戲已經針對私人遊戲/大廳建立,來定義了方法,那麼執行對戰介面讓管理使用者加入及開始錦標賽對戰變得更加容易。

執行ITournamentMatchCallbackHandler介面到您的大廳指令碼,或建立一個新的指令碼來執行這個。
它提供了簡單的方法集來交流您的大廳狀態到錦標賽中樞。

C#

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.
}

隨後當加入特定對戰時,執行這個介面的物件可以被傳送到錦標賽中樞控制器方法:tournamentHubController.JoinTournamentMatch()

程式碼流程示例:

C#

//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;        
    }
}

注意事項: 同樣 重要 的是在OnJoinTournamentMatch中被傳送的tournamentMatchController上使用 報告方法
當特定事件發生在已連接的大廳/房間時,調用這些控制器方法。
錦標賽中樞使用這些以決定何時來重新整理中繼資料。
沒有這樣做的話,可能導致不一致,其中一個客戶端開始對戰,但是其他客戶端還沒有(比如,其他客戶端認為使用者還沒有報到)。

使用Photon房間回調以報告對tournamentMatchController的更改的示例:

C#

//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();
    }
}

錦標賽遊戲階段及結果提交

基於錦標賽設定,一個對戰可以含有多個遊戲階段。
舉例而言,如果一個1對1對戰被設定為一個「前三名」的系列戰,它將需要至少遊玩2個遊戲階段。
一個「遊戲階段」代表在對戰系列戰中的一個單一的遊戲。

建立遊戲階段

為了建立一個遊戲階段,請調用:CreateGameSession(users, userActiveMatchId, sessionType)
已傳回的遊戲階段ID隨後可以被分配到其他客戶端。
如果之後做了另一個後續的調用,則傳回同樣的遊戲階段ID。

C#

//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);

提交結果

在完成一個遊戲階段之後,在到達對戰 期限 之前,必須報告結果。
如果一個對戰被設定為有超過一個遊戲階段(比如,前三名系列戰),那麼GetMatchNextGameDeadline(match)可用於針對目前的遊戲階段來決定 理想的期限,這樣後續的遊戲階段仍然有可遊玩的時間。

C#

//get user active match
var userActiveMatch = tournament.UserActiveMatch;
//get ideal deadline for next game session
var deadline = tournament.GetMatchNextGameDeadline(userActiveMatch);

為了提交遊戲階段結果,請使用SubmitGameSession(gameSession)調用。
必須使用從CreateGameSession調用傳回的已分配的遊戲階段ID,在所有的客戶端上建立一個遊戲階段物件。
重要的事情是設定使用者 地點,其將針對一個給定的遊戲階段來決定點分佈。

C#

//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);
Back to top