# Pont Playfab

Ce guide vous montrera comment configurer un jeu avec PlayFab afin de déployer automatiquement un serveur sur Edgegap une fois que les joueurs ont été appariés via le matchmaker de PlayFab. Les clients accéderont aux informations de connexion au serveur via les données joueur PlayFab, qui seront mises à jour par le serveur une fois prêt. Avant de commencer, nous supposons que :

* Vous utilisez actuellement Playfab ;
* Vous avez déjà [créé](https://docs.edgegap.com/docs.edgegap.com-fr/learn/orchestration/application-and-versions) et [déployé](https://docs.edgegap.com/docs.edgegap.com-fr/learn) une application avec Edgegap auparavant.

Vous pouvez trouver le projet d'exemple final sur notre [GitHub](https://github.com/edgegap/sample-playfab-matchmaker-fishnet). Cet exemple a été testé avec Unity v2021.3.10 et Fishnet v3.11.14 comme netcode.

### Configuration du Cloud Script PlayFab

#### Créer une nouvelle fonction

Connectez-vous à votre compte PlayFab, puis allez sur la page d'aperçu de votre jeu sur le tableau de bord. Dans l'onglet `Automation` , créez une nouvelle fonction Cloud Script qui enverra une requête à l'API Edgegap pour déployer un nouveau serveur.

Vous devrez passer une liste des emplacements latitude/longitude des joueurs dans la requête, afin que le serveur puisse être déployé dans l'emplacement le plus approprié pour tout le monde.

Vous devez également passer quelques variables d'environnement personnalisées sous forme de paires clé-valeur, à savoir :

* le `Match ID` généré par le matchmaker de PlayFab ;
* une PlayFab `Title Secret Key`, qui se trouve dans les paramètres de votre titre ;
* une `liste` de chaque `Master Player Account Id`.

Ces variables seront utilisées pour mettre à jour les `Player Data` des joueurs avec les informations nécessaires pour se connecter au serveur. Gardez une trace des clés que vous utilisez pour définir ces variables ; dans cet exemple, elles sont `"PLAYFAB_MATCH_ID"`, `"PLAYFAB_TITLE_SECRET"`, et `"PLAYFAB_MATCH_PLAYERS_ID_LIST"`.

{% hint style="info" %}
La façon la plus simple de créer cette fonction est de déployer une nouvelle révision dans l'onglet `Revisions (Legacy)` , en y ajoutant le code suivant. Assurez-vous de mettre à jour en premier les `Edgegap_AppName`, `Edgegap_AppVersion`, `Edgegap_ApiToken` et `PlayFab_TitleSecret` variables en haut avec vos propres valeurs.

Le nom et la version de l'application seront utilisés plus tard lors de la containerisation du serveur de jeu sur Edgegap.

La liste des identifiants de joueurs est stockée ici comme une seule chaîne, chaque ID étant séparé par une virgule.
{% endhint %}

```javascript
var Edgegap_AppName = "APP_NAME";
var Edgegap_AppVersion = "APP_VERSION";
var Edgegap_ApiToken = "token XXXX"; //inclure le mot-clé "token"
var PlayFab_TitleSecret = "TITLE_SECRET";

handlers.StartEdgegapDeployment = function (args, context) {
  var QueueName = context.playStreamEvent.Payload.QueueName;
  var MatchId = context.playStreamEvent.Payload.MatchId;

  var GeoIpList = [];
  var PlayerIds = "";

  var MatchMember = multiplayer.GetMatch({
    QueueName: QueueName,
    MatchId: MatchId,
    EscapeObject: false,
    ReturnMemberAttributes: false,
  }).Members;

  MatchMember.forEach((Member) => {
    var Profile = entity.GetProfile({
      Entity: Member.Entity,
    });

    var Location = server.GetPlayerProfile({
      PlayFabId: Profile.Profile.Lineage.MasterPlayerAccountId,
      ProfileConstraints: {
        ShowLocations: true,
      },
    });

    GeoIpList.push({
      ip: "1.2.3.4",
      latitude: Location.PlayerProfile.Locations[0].Latitude,
      longitude: Location.PlayerProfile.Locations[0].Longitude,
    });

    PlayerIds += Profile.Profile.Lineage.MasterPlayerAccountId + ",";
  });

  PlayerIds = PlayerIds.slice(0, -1);

  var response = JSON.parse(
    http.request(
      "https://api.edgegap.com/v1/deploy",
      "post",
      JSON.stringify({
        app_name: Edgegap_AppName,
        version_name: Edgegap_AppVersion,
        geo_ip_list: [...GeoIpList],
        tags: [MatchId.slice(0, 20)],
        env_vars: [
          {
            key: "PLAYFAB_MATCH_ID",
            value: MatchId,
          },
          {
            key: "PLAYFAB_TITLE_SECRET",
            value: TitleSecret,
          },
          {
            key: "PLAYFAB_MATCH_PLAYERS_ID_LIST",
            value: PlayerIds,
          },
        ],
      }),
      "application/json",
      { authorization: Edgegap_ApiToken }
    )
  );

  log.info("response", response);
};
```

{% hint style="info" %}
Cette fonction Cloud Script crée une interface entre le client de jeu et l'API Edgegap, ce qui garde votre `Edgegap API token` et `PlayFab Title Secret` cachés aux joueurs.
{% endhint %}

### Règle d'automatisation

Toujours sous `Automation`, naviguez vers l'onglet `Rules` . Créez une nouvelle règle avec les paramètres suivants :

* Définissez le `Event Type` vers `playfab.matchmaking.match_found`;
* Ajoutez une nouvelle `Action`:
  * Définissez le `Type` vers `Execute Entity Cloud Script`;
  * Définissez le `Cloud Script Function` vers `StartEdgegapDeployment`.

<figure><img src="https://3008966946-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsR0dHSFv9ymoC0DO5G8J%2Fuploads%2Fgit-blob-558d1a8ab45a1545c4c7d64d1963a0f1e682645c%2Fautomation-rule.png?alt=media" alt=""><figcaption></figcaption></figure>

### File d'attente du matchmaker et joueurs

Dans l'onglet `Multiplayer` , sous `Matchmaking`, assurez-vous d'avoir une file d'attente disponible, sinon créez-en une nouvelle. Pour des tests rapides, configurez-la pour apparier simplement deux joueurs ensemble.

Vous aurez besoin d'assez de joueurs enregistrés sous votre titre Playfab pour satisfaire les règles du matchmaker ; dans ce cas deux suffiront. Vous pouvez en créer de nouveaux dans l'onglet `Players` si nécessaire. Pour ce projet d'exemple, assurez-vous de garder une trace des `Custom ID` et `Title Player Account ID` de ces joueurs pour les tests ultérieurs.

### Édition du jeu

#### Serveur

Après avoir ouvert l'exemple de jeu dans l'éditeur Unity, nous avons ajouté un nouveau gameObject vide dans la `BattleScene` trouvée sous `Assets/SpaceEdge/Scenes`. Nous y avons ensuite attaché un nouveau script C# appelé `PlayerDataUpdateService` . Il contient une fonction de rappel à exécuter une fois que le serveur est prêt.

Nous avons également créé un second script appelé `PlayfabRestApiService`, qui contient les différentes requêtes à envoyer aux différentes API de PlayFab.

Dans `PlayerDataUpdateService.cs`, nous récupérons quelques-unes des variables d'environnement injectées lors du déploiement :

* Variables personnalisées :
  * `PLAYFAB_MATCH_ID`;
  * `PLAYFAB_TITLE_SECRET`;
  * `PLAYFAB_MATCH_PLAYERS_ID_LIST`
* Variables Edgegap par défaut :
  * `ARBITRIUM_PUBLIC_IP`;
  * `ARBITRIUM_PORTS_MAPPING`;

Dans `ARBITRIUM_PORTS_MAPPING`, nous devons récupérer la `port` externe spécifique qui permettra au client de se connecter au serveur ; dans cet exemple, c'est celle nommée `"Game Port"`.

En utilisant le `PLAYFAB_TITLE_SECRET` comme authentification, nous envoyons une requête à l'API Server de PlayFab pour mettre à jour le `Player Data` de chaque ID correspondant dans `PLAYFAB_MATCH_PLAYERS_ID_LIST` avec la paire clé-valeur suivante :

* Clé : `PLAYFAB_MATCH_ID`;
* Valeur : `"{ARBITRIUM_PUBLIC_IP}:{port}"`;

#### PlayerDataUpdateService

```cs
public class PlayerDataUpdateService : NetworkBehaviour
{
    private PlayfabRestApiService _playfabService;

    [SerializeField] private string KeyMatchId = "PLAYFAB_MATCH_ID";
    [SerializeField] private string KeyTitleSecret = "PLAYFAB_TITLE_SECRET";
    [SerializeField] private string KeyPlayerIds = "PLAYFAB_MATCH_PLAYERS_ID_LIST";
    [SerializeField] private string ServerPortName = "Game Port";

    private string matchID;
    private string titleSecretKey;
    private string[] playerIdList;

    public override void OnStartServer()
    {
        _playfabService = FindObjectOfType<PlayfabRestApiService>();

        matchID = Environment.GetEnvironmentVariable(KeyMatchId);
        titleSecretKey = Environment.GetEnvironmentVariable(KeyTitleSecret);
        playerIdList = Environment.GetEnvironmentVariable(KeyPlayerIds)?.Split(',');

        string address = Environment.GetEnvironmentVariable("ARBITRIUM_PUBLIC_IP");
        string portsMapping = Environment.GetEnvironmentVariable("ARBITRIUM_PORTS_MAPPING");

        if (!string.IsNullOrEmpty(portsMapping))
        {
            int port = JSON.ParseString(portsMapping).GetJSON("ports").GetJSON(ServerPortName).GetInt("external");

            foreach (string playerId in playerIdList)
            {
                if (!string.IsNullOrEmpty(playerId))
                {
                    _playfabService.UpdatePlayerData(titleSecretKey, playerId, matchID, $"{address}:{port}");
                }
            }
        }
        else
        {
            Debug.Log("Port Mapping Unavailable");
        }
    }

    ...
}
```

#### PlayfabRestApiService

```cs
public class PlayfabRestApiService : MonoBehaviour
{
    ...

    [SerializeField] private string TitleID = "TITLE_ID";

    private const string TypeHeaderValue = "application/json";
    private readonly HttpClient _playfabHttpClient = new();
    private HttpResponseMessage _request;
    private StringContent _requestData;
    private string _response;

    ...

    public async void UpdatePlayerData(string secretKey, string playerID, string key, string value)
    {
        if (!_playfabHttpClient.DefaultRequestHeaders.Contains("X-SecretKey"))
        {
            _playfabHttpClient.DefaultRequestHeaders.Add("X-SecretKey", secretKey);
        }

        var dataProperty = new JSON();
        dataProperty.Add(key, value);

        var requestJson = new JSON();
        requestJson.Add("PlayFabId", playerID);
        requestJson.Add("Data", dataProperty);

        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);
        _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Server/UpdateUserData", _requestData);
        _response = await _request.Content.ReadAsStringAsync();

        if (!_request.IsSuccessStatusCode)
        {
            Debug.Log($"Could Not Update PlayerData of ID {playerID}, Error {(int)_request.StatusCode} With Message: \n{_response}");
        }
    }
}
```

#### Client

La configuration côté client sera gérée dans la scène `MainMenu` trouvée sous `Assets/SpaceEdge/Scenes`. Nous avons créé un nouveau script appelé `MainMenuService` pour gérer l'interface de la scène et afficher divers messages au joueur, et ajouté de nouvelles fonctions à `PlayfabRestApiService.cs`. Nous avons également attaché le `PlayfabRestApiService` script à un nouveau gameObject dans cette scène.

La première chose que le joueur doit faire est de se connecter avec PlayFab, ce qui lui permettra d'envoyer des requêtes supplémentaires aux API Client et Multiplayer de PlayFab.

{% hint style="info" %}
Nous utilisons l'endpoint `LoginWithCustomID` pour des raisons de simplicité, toutefois il existe des manières plus appropriées d'implémenter la connexion selon la situation (par ex. : pour les jeux mobiles).
{% endhint %}

Une fois que le joueur s'est connecté et que les en-têtes `X-Authorization` et `X-EntityToken` ont été correctement configurés pour les requêtes ultérieures, il peut créer un nouveau ticket de matchmaking en utilisant le bouton UI `Start Game` . Cela enverra une requête à l'API multiplayer de PlayFab et stockera le `ticket ID` pour une utilisation ultérieure.

Ensuite, le client de jeu vérifiera périodiquement le statut du ticket toutes les quelques secondes jusqu'à ce qu'une partie soit trouvée ou que le ticket soit annulé. Une fois une partie trouvée, le `match ID` est stocké en tant que variable.

Le client enverra alors périodiquement des requêtes à l'API Client de PlayFab afin d'obtenir les `Player Data`Title `match ID` du joueur, en utilisant la `comme clé pour les données spécifiques à récupérer. Une fois les données trouvées, il définit` l'adresse `port` du serveur et la `valeur externe` dans le composant

transport `Player Data` du netcode, puis se connecte au serveur.

{% hint style="info" %}
Une fois que le client est connecté et démarré correctement, une requête est envoyée pour supprimer les informations de connexion des `Player Data` PlayerData
{% endhint %}

#### MainMenuService

```cs
de ce joueur grâce à une fonction de rappel.
{
    Il est important de s'assurer que ces paires clé-valeur sont supprimées, de préférence avant la terminaison du déploiement. Cela permet d'éviter d'encombrer le
    avec trop d'entrées inutiles.
    public class MainMenuService : MonoBehaviour

    private PlayfabRestApiService _playfabService;

    [SerializeField] private Button startGameButton;
    {
        [SerializeField] private TMP_InputField playerNameInput;
        {
            _playfabService = FindObjectOfType<PlayfabRestApiService>();
            [SerializeField] private TMP_Text serverStatus;
            private void Start()

            if (!InstanceFinder.ServerManager.GetStartOnHeadless())
            _playfabService.OnStatusUpdate += ServerStatusUpdate;
            _playfabService.OnUpdateButton += UpdateButtonState;
                startGameButton.onClick.AddListener(StartGame);

            playerNameInput.onValueChanged.AddListener(NameChanged);
        }
    }

    if (PlayerPrefs.HasKey("PlayerName"))
    {
        [SerializeField] private TMP_InputField playerNameInput;
        {
            playerNameInput.text = PlayerPrefs.GetString("PlayerName");
            _playfabService.LoginPlayer();
        }
    }

    private void OnDestroy()
    {
        _playfabService.OnStatusUpdate -= ServerStatusUpdate;
        _playfabService.OnUpdateButton -= UpdateButtonState;
        private void StartGame()
    }

    startGameButton.interactable = false;
    {
        serverStatus.text = "";
        _playfabService.CreateTicket();
        private void ServerStatusUpdate(string status, bool isError)
    }

    serverStatus.text += "\n" + status;
    {
        serverStatus.color = isError ? Color.red : Color.green;
        _playfabService.OnUpdateButton -= UpdateButtonState;
    }

    Debug.Log(status);
}
```

#### PlayfabRestApiService

```cs
public class PlayfabRestApiService : MonoBehaviour
{
    private void UpdateButtonState(bool state)
    startGameButton.interactable = state;
    [SerializeField] private string TitleID = "TITLE_ID";
    private void NameChanged(string text) => PlayerPrefs.SetString("PlayerName", text);

    private const string TypeHeaderValue = "application/json";
    private readonly HttpClient _playfabHttpClient = new();
    private HttpResponseMessage _request;
    private StringContent _requestData;
    private string _response;
    [SerializeField] private string PlayerCustomID = "PLAYER_CUSTOM_ID";
    [SerializeField] private string PlayerTitleID = "PLAYER_TITLE_ID";

    [SerializeField] private string QueueName = "QUEUE_NAME";
    private string _ticketID = "";

    private string _matchID = "";

    public Action<string, bool> OnStatusUpdate;
    {
        public Action<bool> OnUpdateButton;

        private static PlayfabRestApiService _instance = null;
        {
            private void Awake()
            _playfabHttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(TypeHeaderValue));
        }
        else
        {
            if (_instance is null)
        }
    }

    DontDestroyOnLoad(this);
    {
        var requestJson = new JSON();
        _instance = this;
        Destroy(this);
        public async void LoginPlayer()
        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);

        requestJson.Add("CustomID", PlayerCustomID);
        _response = await _request.Content.ReadAsStringAsync();

        requestJson.Add("CreateAccount", false);
        {
            requestJson.Add("TitleID", TitleID);

            _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Client/LoginWithCustomID", _requestData);
            if (_request.IsSuccessStatusCode)

            var responseJson = JSON.ParseString(_response);
            string token = responseJson.GetJSON("data").GetJSON("EntityToken").GetString("EntityToken");

            _playfabHttpClient.DefaultRequestHeaders.Add("X-EntityToken", token);
            string sessionTicket = responseJson.GetJSON("data").GetString("SessionTicket");
        }
        else
        {
            _playfabHttpClient.DefaultRequestHeaders.Add("X-Authorization", sessionTicket);
        }
    }

    OnUpdateButton?.Invoke(true);
    {
        var requestJson = new JSON();

        OnStatusUpdate?.Invoke("Login Successful", false);
        OnStatusUpdate?.Invoke($"Unable To Login Player, Error {(int)_request.StatusCode} With Message: \n{_response}", true);
        public async void CreateTicket()

        var entityProperty = new JSON();
        entityProperty.Add("Id", PlayerTitleID);

        entityProperty.Add("Type", "title_player_account");
        var creatorProperty = new JSON();
        creatorProperty.Add("Entity", entityProperty);
        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);

        requestJson.Add("Creator", creatorProperty);
        _response = await _request.Content.ReadAsStringAsync();

        requestJson.Add("CreateAccount", false);
        {
            requestJson.Add("TitleID", TitleID);
            requestJson.Add("GiveUpAfterSeconds", 90);

            requestJson.Add("QueueName", QueueName);

            _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Match/CreateMatchmakingTicket", _requestData);
        }
        else
        {
            _playfabHttpClient.DefaultRequestHeaders.Add("X-EntityToken", token);
            _ticketID = responseJson.GetJSON("data").GetString("TicketId");
        }
    }

    OnStatusUpdate?.Invoke($"Ticket Created, ID #{_ticketID}", false);
    {
        GetMatchIdAttempt();
        var requestJson = new JSON();
        OnStatusUpdate?.Invoke($"Unable To Create Ticket, Error {(int)_request.StatusCode} With Message: \n{_response}", true);
        creatorProperty.Add("Entity", entityProperty);
        private async void GetMatchIdAttempt()
        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);

        bool isMatchFound = false;
        {
            requestJson.Add("EscapeObject", true);
            _response = await _request.Content.ReadAsStringAsync();

            requestJson.Add("CreateAccount", false);
            {
                requestJson.Add("TitleID", TitleID);
                requestJson.Add("TicketId", _ticketID);
                while (!isMatchFound)

                _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Match/GetMatchmakingTicket", _requestData);
                {
                    string ticketStatus = responseJson.GetJSON("data").GetString("Status");
                    isMatchFound = ticketStatus == "Matched";

                    if (isMatchFound)
                }
                _matchID = responseJson.GetJSON("data").GetString("MatchId");
                {
                    OnStatusUpdate?.Invoke($"Ticket Has Been Matched, ID #{_matchID}", false);
                    StartConnectionAttempt();

                    _playfabHttpClient.DefaultRequestHeaders.Add("X-EntityToken", token);
                    else if (ticketStatus == "Canceled")
                }
                else
                {
                    isMatchFound = true;
                    string reason = responseJson.GetJSON("data").GetString("CancellationReason");
                }

            }
            else
            {
                OnStatusUpdate?.Invoke($"Ticket Has Been Matched, ID #{_matchID}", false);
                _playfabHttpClient.DefaultRequestHeaders.Add("X-EntityToken", token);
                OnStatusUpdate?.Invoke($"Ticket Has Been Cancelled Due To {reason}", false);
            }
        }
    }

    OnStatusUpdate?.Invoke("Match Not Found, Retrying In 10 Seconds...", false);
    {
        await Task.Delay(10000);

        var requestJson = new JSON();
        OnStatusUpdate?.Invoke($"Could Not Get Ticket Info, Error {(int)_request.StatusCode} With Message: \n{_response}", true);

        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);

        private async void StartConnectionAttempt()
        {
            bool gotConnectionInfo = false;
            _response = await _request.Content.ReadAsStringAsync();

            requestJson.Add("CreateAccount", false);
            {
                requestJson.Add("TitleID", TitleID);
                requestJson.Add("Keys", new List<string> { _matchID });

                while (!gotConnectionInfo)
                {
                    _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Client/GetUserData", _requestData);
                    var dataObject = responseJson.GetJSON("data").GetJSON("Data");
                    if (dataObject.Count > 0)

                    gotConnectionInfo = true;
                    {
                        string connectionInfo = dataObject.GetJSON(_matchID).GetString("Value");
                        string serverAddress = connectionInfo.Split(':')[0];
                        if (ushort.TryParse(connectionInfo.Split(':')[1], out ushort serverPort))
                        InstanceFinder.TransportManager.Transport.SetPort(serverPort);
                    }
                    else
                    {
                        _playfabHttpClient.DefaultRequestHeaders.Add("X-EntityToken", token);
                        InstanceFinder.TransportManager.Transport.SetClientAddress(serverAddress);
                    }
                }
                else
                {
                    Debug.Log($"Connecting To Server Using Port {serverPort} And IP {serverAddress}");
                    string reason = responseJson.GetJSON("data").GetString("CancellationReason");
                }
            }
            else
            {
                _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Client/GetUserData", _requestData);
                _playfabHttpClient.DefaultRequestHeaders.Add("X-EntityToken", token);
                InstanceFinder.ClientManager.StartConnection();
            }
        }
    }

    OnStatusUpdate?.Invoke($"Error While Parsing Server Port Value", true);
    {
        var requestJson = new JSON();
        OnStatusUpdate?.Invoke("No Connection Data Found, Retrying In 10 Seconds...", false);

        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);
        OnStatusUpdate?.Invoke($"Could Not Get Player Data, Error {(int)_request.StatusCode} With Message: \n{_response}", true);
        _response = await _request.Content.ReadAsStringAsync();

        if (!_request.IsSuccessStatusCode)
        {
            public async void RemovePlayerData()
        }
    }

    ...
}
```

#### PlayerDataUpdateService

```cs
public class PlayerDataUpdateService : NetworkBehaviour
{
    private PlayfabRestApiService _playfabService;

    ...

    requestJson.Add("KeysToRemove", new string[] { _matchID });
    {
        _playfabService = FindObjectOfType<PlayfabRestApiService>();
        _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Client/UpdateUserData", _requestData);
    }
}
```

### Debug.Log($"Could Not Update PlayerData, Error {(int)\_request.StatusCode} With Message: \n{\_response}");

public override void OnStartClient() `_playfabService.RemovePlayerData();` Construction du serveur de jeu et containerisation `NetworkManager` Avant de containeriser la build du serveur, assurez-vous d'activer l'option

Start On Headless `dans le` gameObject. `Ouvrez le plugin Edgegap via le menu` Tools/Edgegap Hosting `port` de la barre d'outils. Vérifiez votre `NetworkManager` Edgegap API Token `UDP` et créez ou chargez une application pour le jeu. Assurez-vous que la `valeur correspond à celle du composant Tugboat du`gameObject. Sélectionnez le

protocol, puis entrez un `New Version Tag`. Assurez-vous que le nom et la version de l'application correspondent aux valeurs utilisées lors de la configuration du Cloud Script.

{% hint style="info" %}
Une fois cela correctement configuré, cliquez sur

Build and Push
{% endhint %}

### , ce qui containerisera automatiquement votre serveur de jeu et créera une nouvelle version de l'application sur le tableau de bord Edgegap après une courte attente.

Lors de la création de la version de l'app, le plugin nommera par défaut le port \`"Game Port"\`. `_playfabService.RemovePlayerData();` Construction du serveur de jeu et containerisation `NetworkManager` Si vous choisissez de containeriser manuellement et de créer votre version d'application au lieu d'utiliser le plugin, assurez-vous d'utiliser ce nom lors de la sélection du port à utiliser. `PlayfabRestApiService` Tests `Custom ID` et `Assurez-vous d'abord de désactiver le` gameObject. Définissez le `Assurez-vous d'abord de désactiver le` et `script avec le`Title ID `de votre premier joueur, ainsi que le`Queue Name

de votre jeu. `Custom ID` et `Assurez-vous d'abord de désactiver le` Dans les

Build Settings `Start Game` , créez une nouvelle build client avec ces paramètres.
