> For the complete documentation index, see [llms.txt](https://docs.edgegap.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.edgegap.com/fr/docs/tools-and-integrations/playfab-bridge.md).

# Déployer depuis PlayFab

Conservez votre matchmaker PlayFab et déployez vos serveurs de jeu sur Edgegap, en combinant les deux plateformes.

Découvrez comment déployer facilement des serveurs dédiés sur Edgegap et **continuer à utiliser PlayFab Matchmaker**.

## 🚀 Pour commencer

Avant de commencer, assurez-vous d'avoir :

* Vous être familiarisé avec le [PlayFab Console](https://developer.playfab.com/en-us/r/sign-in), en particulier [Données du joueur](https://learn.microsoft.com/en-us/xbox/playfab/player-progression/player-data/) et [Mise en relation](https://learn.microsoft.com/en-us/xbox/playfab/multiplayer/matchmaking/).
* Avoir terminé les étapes de démarrage de votre projet - [Unity](/fr/unity.md) / [Unreal Engine](/fr/unreal-engine.md) / [Godot](/fr/godot.md).
* Déployé votre serveur sur Edgegap à l'aide du plugin ou [du tableau de bord](https://app.edgegap.com/deployment-management/deployments/create), en vous connectant avec l'IP et le port.

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 le netcode Fishnet v3.11.14.

### Script cloud PlayFab

#### Créer une nouvelle fonction

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

Vous devrez transmettre dans la requête une liste des positions latitude/longitude des joueurs, afin que le serveur puisse être déployé à l'endroit le plus adapté pour tout le monde.

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

* le `ID de match` généré par le matchmaking de PlayFab ;
* un PlayFab `Secret de titre`, qui se trouve dans les paramètres de votre titre ;
* une `liste` des `ID de compte joueur principal`.

Ces variables seront utilisées pour mettre à jour le `Données du joueur` avec les informations nécessaires pour se connecter au serveur. Notez les 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" %}
Le moyen le plus simple de créer cette fonction est de déployer une nouvelle révision dans l' `Révisions (héritées)` onglet, en y ajoutant le code suivant. Veillez à mettre à jour les `Edgegap_AppName`, `Edgegap_AppVersion`, `Edgegap_ApiToken` et `PlayFab_TitleSecret` variables en haut avec vos propres valeurs d'abord.

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

La liste des IDs des joueurs est stockée ici sous forme d'une seule chaîne, chaque ID étant séparé par une virgule.
{% endhint %}

{% code expandable="true" %}

```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 GeoUserList = [];
  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,
      },
    });

    GeoUserList .push({
      user_type: "geo_coordinates",
      user_data: {
        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/v2/deployments",
      "post",
      JSON.stringify({
        application: Edgegap_AppName,
        version: Edgegap_AppVersion,
        users: [...GeoUserList],
        tags: [MatchId.slice(0, 20)],
        environment_variables: [
          {
            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);
};
```

{% endcode %}

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

### Règle d'automatisation

Toujours sous `Automatisation`, accédez à l' `onglet Règles` Créez une nouvelle règle avec les paramètres suivants :

* Définissez le `Type d'événement` sur `playfab.matchmaking.match_found`;
* Ajoutez une nouvelle `Action`:
  * Définissez le `Type` sur `Exécuter le script cloud d'entité`;
  * Définissez le `Fonction Cloud Script` sur `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 le `Multijoueur` onglet, sous `Mise en relation`, assurez-vous qu'une file d'attente est disponible, sinon créez-en une nouvelle. Pour des tests rapides, configurez-la simplement pour apparier n'importe quels deux joueurs ensemble.

Vous devrez avoir suffisamment de joueurs enregistrés sur votre titre PlayFab pour satisfaire les règles du matchmaker, donc dans ce cas deux suffiront. Vous pouvez en créer de nouveaux dans l' `onglet Joueurs` si besoin. Pour ce projet d'exemple, veillez à conserver une trace des joueurs `ID personnalisé` et `ID de compte joueur du titre` pour les tests plus tard.

### Modification du jeu

#### Serveur

Après avoir ouvert l'exemple de jeu dans l'éditeur Unity, nous avons ajouté un nouvel objet de jeu vide à la `BattleScene` située sous `Assets/SpaceEdge/Scenes`. Nous lui avons ensuite attaché un nouveau script C# appelé `PlayerDataUpdateService` à celui-ci. 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 qui doivent être envoyées aux différentes API de PlayFab.

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

* Variables personnalisées :
  * `PLAYFAB_MATCH_ID`;
  * `PLAYFAB_TITLE_SECRET`;
  * `PLAYFAB_MATCH_PLAYERS_ID_LIST`
* Par défaut [Variables Edgegap](/fr/learn/orchestration/deployments.md#injected-environment-variables):
  * `ARBITRIUM_PUBLIC_IP`;
  * `ARBITRIUM_PORT_GAMEPORT_EXTERNAL`;

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

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

#### PlayerDataUpdateService

{% code expandable="true" %}

```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";

    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 port = Environment.GetEnvironmentVariable("ARBITRIUM_PORT_GAMEPORT_EXTERNAL");

        if (!string.IsNullOrEmpty(portsMapping))
        {
            foreach (string playerId in playerIdList)
            {
                if (!string.IsNullOrEmpty(playerId))
                {
                    _playfabService.UpdatePlayerData(titleSecretKey, playerId, matchID, $"{address}:{port}");
                }
            }
        }
        else
        {
            Debug.Log("Mappage de port indisponible");
        }
    }

    ...
}
```

{% endcode %}

#### PlayfabRestApiService

{% code expandable="true" %}

```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($"Impossible de mettre à jour les données du joueur de l'ID {playerID}, erreur {(int)_request.StatusCode} avec le message : \n{_response}");
        }
    }
}
```

{% endcode %}

#### Client

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

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

{% hint style="info" %}
Nous utilisons le `LoginWithCustomID` point de terminaison par simplicité, cependant il existe des façons plus adapté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 deux `X-Authorization` et `X-EntityToken` en-têtes ont été correctement configurés pour les requêtes ultérieures, ils peuvent créer un nouveau ticket de matchmaking à l'aide du `Démarrer le jeu` bouton de l'interface utilisateur. Cela enverra une requête à l'API multijoueur de PlayFab et stockera l' `ID du ticket` pour une utilisation ultérieure.

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

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

Une fois que le client est correctement connecté et démarré, une requête est envoyée pour supprimer les informations de connexion du PlayFab de ce joueur `Données du joueur` grâce à une fonction de rappel.

{% hint style="info" %}
Il est important de s'assurer que ces paires clé-valeur sont supprimées, de préférence avant la fin du déploiement. Cela permet d'éviter d'encombrer le `Données du joueur` avec trop d'entrées inutiles.
{% endhint %}

#### MainMenuService

{% code expandable="true" %}

```cs
public class MainMenuService : MonoBehaviour
{
    [SerializeField] private Button startGameButton;
    [SerializeField] private TMP_InputField playerNameInput;
    [SerializeField] private TMP_Text serverStatus;

    private PlayfabRestApiService _playfabService;

    private void Start()
    {
        if (!InstanceFinder.ServerManager.GetStartOnHeadless())
        {
            _playfabService = FindObjectOfType<PlayfabRestApiService>();
            _playfabService.OnStatusUpdate += ServerStatusUpdate;
            _playfabService.OnUpdateButton += UpdateButtonState;

            startGameButton.onClick.AddListener(StartGame);
            playerNameInput.onValueChanged.AddListener(NameChanged);
            if (PlayerPrefs.HasKey("PlayerName"))
                playerNameInput.text = PlayerPrefs.GetString("PlayerName");

            _playfabService.LoginPlayer();
        }
    }

    private void OnDestroy()
    {
        if (!InstanceFinder.ServerManager.GetStartOnHeadless())
        {
            _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;
        Debug.Log(status);
    }

    private void UpdateButtonState(bool state)
    {
        startGameButton.interactable = state;
        serverStatus.text = "";
    }

    private void NameChanged(string text) => PlayerPrefs.SetString("PlayerName", text);
}
```

{% endcode %}

#### PlayfabRestApiService

{% code expandable="true" %}

```cs
public class PlayfabRestApiService : MonoBehaviour
{
    [SerializeField] private string PlayerCustomID = "PLAYER_CUSTOM_ID";
    [SerializeField] private string PlayerTitleID = "PLAYER_TITLE_ID";
    [SerializeField] private string TitleID = "TITLE_ID";
    [SerializeField] private string QueueName = "QUEUE_NAME";

    private const string TypeHeaderValue = "application/json";
    private readonly HttpClient _playfabHttpClient = new();
    private HttpResponseMessage _request;
    private StringContent _requestData;
    private string _response;
    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));

        if (_instance is null)
        {
            DontDestroyOnLoad(this);
            _instance = this;
        }
        else
        {
            Destroy(this);
        }
    }

    public async void LoginPlayer()
    {
        var requestJson = new JSON();
        requestJson.Add("CustomID", PlayerCustomID);
        requestJson.Add("CreateAccount", false);
        requestJson.Add("TitleID", TitleID);
        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);

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

        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");
            _playfabHttpClient.DefaultRequestHeaders.Add("X-Authorization", sessionTicket);

            OnUpdateButton?.Invoke(true);
            OnStatusUpdate?.Invoke("Connexion réussie", false);
        }
        else
        {
            OnStatusUpdate?.Invoke($"Impossible de connecter le joueur, erreur {(int)_request.StatusCode} avec le message : \n{_response}", true);
        }
    }

    public async void CreateTicket()
    {
        var requestJson = new JSON();

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

        var creatorProperty = new JSON();
        creatorProperty.Add("Entity", entityProperty);

        requestJson.Add("Creator", creatorProperty);
        requestJson.Add("GiveUpAfterSeconds", 90);
        requestJson.Add("QueueName", QueueName);
        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);

        _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Match/CreateMatchmakingTicket", _requestData);
        _response = await _request.Content.ReadAsStringAsync();

        if (_request.IsSuccessStatusCode)
        {
            var responseJson = JSON.ParseString(_response);
            _ticketID = responseJson.GetJSON("data").GetString("TicketId");

            OnStatusUpdate?.Invoke($"Ticket créé, ID #{_ticketID}", false);

            GetMatchIdAttempt();
        }
        else
        {
            OnUpdateButton?.Invoke(true);
            OnStatusUpdate?.Invoke($"Impossible de créer le ticket, erreur {(int)_request.StatusCode} avec le message : \n{_response}", true);
        }
    }

    private async void GetMatchIdAttempt()
    {
        bool isMatchFound = false;
        var requestJson = new JSON();
        requestJson.Add("EscapeObject", true);
        requestJson.Add("QueueName", QueueName);
        requestJson.Add("TicketId", _ticketID);
        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);

        while (!isMatchFound)
        {
            _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Match/GetMatchmakingTicket", _requestData);
            _response = await _request.Content.ReadAsStringAsync();

            if (_request.IsSuccessStatusCode)
            {
                var responseJson = JSON.ParseString(_response);
                string ticketStatus = responseJson.GetJSON("data").GetString("Status");
                isMatchFound = ticketStatus == "Matched";

                if (isMatchFound)
                {
                    _matchID = responseJson.GetJSON("data").GetString("MatchId");
                    OnStatusUpdate?.Invoke($"Le ticket a été apparié, ID #{_matchID}", false);

                    StartConnectionAttempt();
                }
                else if (ticketStatus == "Canceled")
                {
                    isMatchFound = true;
                    string reason = responseJson.GetJSON("data").GetString("CancellationReason");

                    OnUpdateButton?.Invoke(true);
                    OnStatusUpdate?.Invoke($"Le ticket a été annulé en raison de {reason}", false);
                }
                else
                {
                    OnStatusUpdate?.Invoke("Correspondance introuvable, nouvelle tentative dans 10 secondes...", false);
                    await Task.Delay(10000);
                }

            }
            else
            {
                isMatchFound = true;
                OnUpdateButton?.Invoke(true);
                OnStatusUpdate?.Invoke($"Impossible d'obtenir les informations du ticket, erreur {(int)_request.StatusCode} avec le message : \n{_response}", true);
            }
        }
    }

    private async void StartConnectionAttempt()
    {
        bool gotConnectionInfo = false;

        var requestJson = new JSON();
        requestJson.Add("Keys", new List<string> { _matchID });

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

        while (!gotConnectionInfo)
        {
            _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Client/GetUserData", _requestData);
            _response = await _request.Content.ReadAsStringAsync();

            if (_request.IsSuccessStatusCode)
            {
                var responseJson = JSON.ParseString(_response);
                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);
                        InstanceFinder.TransportManager.Transport.SetClientAddress(serverAddress);
                        Debug.Log($"Connexion au serveur à l'aide du port {serverPort} et de l'adresse IP {serverAddress}");
                        InstanceFinder.ClientManager.StartConnection();
                    }
                    else
                    {
                        OnUpdateButton?.Invoke(true);
                        OnStatusUpdate?.Invoke($"Erreur lors de l'analyse de la valeur du port du serveur", true);
                    }
                }
                else
                {
                    OnStatusUpdate?.Invoke("Aucune donnée de connexion trouvée, nouvelle tentative dans 10 secondes...", false);
                    await Task.Delay(10000);
                }
            }
            else
            {
                gotConnectionInfo = true;
                OnUpdateButton?.Invoke(true);
                OnStatusUpdate?.Invoke($"Impossible d'obtenir les données du joueur, erreur {(int)_request.StatusCode} avec le message : \n{_response}", true);
            }
        }
    }

    public async void RemovePlayerData()
    {
        var requestJson = new JSON();
        requestJson.Add("KeysToRemove", new string[] { _matchID });

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

        if (!_request.IsSuccessStatusCode)
        {
            Debug.Log($"Impossible de mettre à jour les données du joueur, erreur {(int)_request.StatusCode} avec le message : \n{_response}");
        }
    }

    ...
}
```

{% endcode %}

#### PlayerDataUpdateService

{% code expandable="true" %}

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

    ...

    public override void OnStartClient()
    {
        _playfabService = FindObjectOfType<PlayfabRestApiService>();
        _playfabService.RemovePlayerData();
    }
}
```

{% endcode %}

### Construction du serveur de jeu et création du conteneur

Répétez maintenant les étapes de compilation avec la fonctionnalité ajoutée - [Unity](/fr/unity.md) / [Unreal Engine](/fr/unreal-engine.md) / [Godot](/fr/godot.md).

### Tests

Si vous utilisez FishNet, assurez-vous d’abord de désactiver `Démarrer en mode headless` l’option dans le `NetworkManager`.

Définissez le `PlayfabRestApiService` script avec l’ `ID personnalisé` et `ID du titre` de votre premier joueur, ainsi que le `ID du titre` et `nom de la file d’attente`. Dans les `paramètres de compilation`, créez une nouvelle compilation client avec ces paramètres.

Une fois la compilation terminée, modifiez les `ID personnalisé` et `ID du titre` valeurs pour celles de votre deuxième joueur. Vous pouvez créer une compilation client séparée si vous le souhaitez, bien que l'éditeur Unity en mode lecture devrait tout aussi bien fonctionner.

Si les deux clients se connectent correctement au lancement, cliquez sur le bouton de chacune des instances `Démarrer le jeu` Après un court instant, ils seront appariés via le système de matchmaking de PlayFab, puis se connecteront au même déploiement sur Edgegap une fois que le serveur aura terminé sa configuration.
