# Интеграция игры

### Зачем мне нужен подборщик матчей (Matchmaker) и/или Лобби?

Служба лобби ([Управляемые кластеры](/ru/learn/advanced-features/managed-clusters.md#nakama-by-heroic-labs)) играет решающую роль в онлайн-многопользовательских играх по различным техническим причинам.

**Управление игроками**

Система лобби или подборщика матчей помогает управлять игроками, группируя их в подходящие матчи на основе различных критериев, таких как уровень навыков, географическое расположение или игровые предпочтения. Это обеспечивает сбалансированный и приятный игровой опыт для всех участвующих игроков.

**Бэкенд**

Вы не хотите, чтобы ваш игрок напрямую связывался с вашим бэкендом, и вы хотите предоставить централизованную точку доступа. Представьте, что у вас есть API-токен, который запускает виртуальную машину у определённого провайдера — вы не хотите, чтобы токен был захардкожен в клиенте игры и доступен любому игроку!

**Безопасность**

Использование централизованной системы лобби или подборщика матчей помогает поддерживать контроль над безопасностью игры. Фильтруя и проверяя подключения игроков, подборщик может предотвращать несанкционированный доступ, защищать данные пользователей и снижать вероятность мошенничества или взломов.

**Управление игровыми сессиями**

Система лобби или подборщика матчей управляет игровыми сессиями, создавая, обновляя и завершая их по мере необходимости. Это гарантирует, что игроки могут беспрепятственно присоединяться к играм или покидать их, а экземпляры игры могут эффективно управляться системой.

<figure><img src="/files/5825df10503da9f268b9b639a096b76973b3964d" alt=""><figcaption></figcaption></figure>

### Вам не нужно это прямо сейчас! (Режим разработки)

На этапе разработки вам может не понадобиться полнофункциональная система лобби или подборщика матчей по нескольким техническим причинам:

**Упрощённая тестовая среда**

На начальных этапах разработки вы можете сосредоточиться на реализации и тестировании основных игровых механик и сетевых функций. Избегая сложности лобби или подборщика матчей, вы можете проще тестировать и отлаживать игру с небольшой группой игроков, часто в составе вашей команды разработчиков.

**Быстрая итерация**

Без лобби или подборщика матчей вы можете быстро вносить изменения в код игры и тестировать новые функции или исправления, не беспокоясь о влиянии на процесс матчмейкинга. Это позволяет применять более гибкий подход к разработке, позволяя вашей команде быстрее итеративно улучшать игровые механики.

**Распределение ресурсов**

Разработка полнофункциональной системы лобби или подборщика матчей требует значительного времени и ресурсов. Отложив реализацию этих компонентов, вы сможете более эффективно распределить ресурсы, сначала сосредоточившись на основной игровой механике и сетевых функциях.

**Проблемы масштабируемости**

На ранних этапах разработки у вас, вероятно, не будет большого числа игроков для управления. В результате может быть достаточно базовой системы для подключения игроков или даже ручных подключений для целей тестирования. Однако по мере роста вашей игры и привлечения большего числа игроков система лобби или подборщик матчей станет всё более необходимой для управления подключениями игроков и обеспечения плавного игрового процесса.

Крайне важно спроектировать архитектуру игры с учётом возможности интеграции системы лобби или подборщика матчей в будущем. Такой дальновидный подход облегчит интеграцию этих компонентов, когда вы будете готовы тестировать игру с более крупной группой игроков, обеспечив более плавный переход и более отполированный конечный продукт.

### Взаимодействие с API Edgegap

Edgegap предлагает простой API для взаимодействия с сессиями реле, чтобы авторизовать ваших игроков подключаться к ближайшему реле для низкой задержки.

<figure><img src="/files/4d77dbb0b8d18be954815b9a0a14e2a7997d21d6" alt=""><figcaption></figcaption></figure>

Для взаимодействия с API вам потребуется указать `токен в заголовке авторизации` в ваших запросах.

{% hint style="info" %}
Вы можете получить токен, зарегистрировав аккаунт и создав профиль реле. [**Зарегистрируйтесь здесь**](https://app.edgegap.com/auth/register)

Обратите внимание, что вам нужен токен, предназначенный исключительно для реле. Если у вас уже есть стандартный API-токен, он не подойдёт.
{% endhint %}

Этот параметр доступен в боковой панели на панели управления.

<figure><img src="/files/dad17e995d5fc18dbd4f2a7219754828709a88c5" alt=""><figcaption></figcaption></figure>

После получения токена включите его в заголовки запросов для всех вызовов API. Не забудьте добавить префикс к вашему API-ключу `token`.

{% hint style="success" %}
Если вы не знакомы с работой с API, вы можете обратиться к [этому разделу](#interacting-with-api-curl-or-postman) чтобы помочь вам начать.
{% endhint %}

### Создание сессии реле

Чтобы использовать распределённое реле, вам нужно создать сессию реле. Это делается путём отправки `POST` запроса на `/v1/relays/sessions` эндпоинт с JSON-пayload. Сессии могут создаваться вручную, с помощью подборщика матчей, через лобби или через вашу собственную пользовательскую службу.

Это динамически выберет лучшее доступное реле для ваших игроков в реальном времени и создаст авторизацию для доступа к реле. Однако мы рекомендуем предварительно фильтровать матчмейкинг, чтобы группировать игроков в рамках одного региона, чтобы избежать экстремальных расстояний между ними.

{% hint style="info" %}
Этот шаг не подключит ваших игроков непосредственно к реле; это нужно будет сделать на более позднем этапе процесса. Этот шаг только предоставляет вам реле, с которым следует установить соединение.
{% endhint %}

Пример запроса:

```bash
POST - /v1/relays/sessions
```

В теле должен содержаться массив объектов пользователей, где каждый объект содержит IP-адрес пользователя. Вы также можете включить `webhook_url`, который представляет собой URL, который будет получать уведомления, связанные с сессией.

Пример полезной нагрузки:

```json
{
  "users": [
    {
      "ip": "1.1.1.1"
    },
    {
      "ip": "2.2.2.2"
    }
  ],
  "webhook_url": "https://webhook.example.com/notify"
}
```

{% hint style="info" %}
Где `"users"` — массив объектов пользователей, содержащих `"ip"` каждого пользователя в сессии, и `"webhook_url"` — это необязательный URL для получения обновлений о сессии.

После отправки `POST` запроса вы получите JSON-ответ, содержащий `session_id`, который потребуется для последующего получения информации о сессии.
{% endhint %}

Пример ответа:

```json
{
  "session_id": "3960c873aafd-S",
  "authorization_token": null,
  "status": "Initializing",
  "ready": false,
  "linked": false,
  "error": null,
  "session_users": [],
  "relay": null,
  "webhook_url": "https://webhook.example.com/notify"
}
```

### Получение информации о сессии реле

Для получения информации о сессии отправьте `GET` запроса на `/v1/relays/sessions/{session_id}` эндпоинт с `session_id` полученным из `POST` запроса.

Пример запроса:

```bash
GET - /v1/relays/sessions/3960c873aafd-S
```

Ответ будет содержать информацию о сессии, включая статус сессии, информацию о пользователях и сведения о реле.

Ожидаемый ответ будет содержать информацию о сессии, примерно такой:

```json
{
  "session_id": "3960c873aafd-S",
  "authorization_token": 1031196689,
  "status": "Linked",
  "ready": true,
  "linked": true,
  "error": null,
  "session_users": [
    {
      "ip_address": "2.2.2.2",
      "latitude": 48.8602294921875,
      "longitude": 2.34106993675232,
      "authorization_token": 3499933322
    },
    {
      "ip_address": "1.1.1.1",
      "latitude": -37.7036018371582,
      "longitude": 145.180633544922,
      "authorization_token": 4261594560
    }
  ],
  "relay": {
    "ip": "178.79.131.238",
    "host": "cc84b011777b.pr.edgegap.net",
    "ports": {
      "server": {
        "port": 31527,
        "protocol": "UDP",
        "link": "cc84b011777b.pr.edgegap.net:31527"
      },
      "client": {
        "port": 32089,
        "protocol": "UDP",
        "link": "cc84b011777b.pr.edgegap.net:32089"
      }
    }
  },
  "webhook_url": "https://webhook.example.com/notify"
}
```

Получение авторизации к ближайшему реле может занять небольшое время, поэтому когда поле `ready` будет `true` вы можете извлечь JSON-данные для использования.

[Подробнее здесь](/ru/docs/api.md)

{% hint style="success" %}
В качестве альтернативы вы можете использовать параметр \`webhook\_url\` при создании сессии реле, чтобы получать уведомления о том, что сессия либо успешно назначена реле, либо не смогла этого сделать.
{% endhint %}

### Завершение сессии реле

Когда вы решите прекратить подключение к реле, вы можете легко завершить сессию реле. Вам нужно отправить `DELETE` запрос на следующий эндпоинт:

```bash
DELETE - /v1/relays/sessions/{session_id}
```

{% hint style="info" %}
Замените `{session_id}` на фактический идентификатор сессии, которую вы хотите завершить.
{% endhint %}

Ответ со статусом 204 No Content означает, что сессия успешно удалена. Игроки потеряют доступ к реле, и вам больше не будут начисляться расходы за эту сессию.

{% hint style="warning" %}
Важно правильно завершать сессии, чтобы избежать оставления неиспользуемых выделенных ресурсов, что может повлиять на производительность и повлечь за собой лишние расходы.
{% endhint %}

***

### Пример на C\#

Это пример на C#, который ваше Лобби или Подборщик матчей мог бы выполнить для создания сессии реле и извлечения данных, которые вам нужно вернуть клиенту игры.

{% hint style="info" %}
Этот пример использует C# версии > 7.0
{% endhint %}

```csharp
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace LobbyNamespace
{
    public class EdgegapAPI
    {
        private readonly HttpClient _httpClient;

        public EdgegapAPI(string apiToken)
        {
            _httpClient = new HttpClient();
            _httpClient.DefaultRequestHeaders.Add("Authorization", $"Token {apiToken}");
        }

        public async Task<RelaySessionAuthorization> CreateRelaySession(List<string> ips)
        {
            var jsonBody = new
            {
                users = ips.ConvertAll(ip => new { ip })
            };

            var postResponse = await _httpClient.PostAsJsonAsync("https://api.edgegap.com/v1/relays/sessions", jsonBody);
            var postJsonData = await postResponse.Content.ReadFromJsonAsync<Dictionary<string, object>>();

            if (!postJsonData.ContainsKey("session_id"))
            {
                throw new Exception("Failed to create relay session");
            }

            var sessionId = postJsonData["session_id"].ToString();

            var retries = 5;
            while (retries > 0)
            {
                var getResponse = await _httpClient.GetAsync($"https://api.edgegap.com/v1/relays/sessions/{sessionId}");
                var getJsonData = await getResponse.Content.ReadFromJsonAsync<Dictionary<string, object>>();
                var ready = (JsonElement)getJsonData["ready"];

                if (ready.ValueKind == JsonValueKind.True)
                {
                    var usersData = (JsonElement)getJsonData["session_users"];
                    var userAuthorizationTokens = new Dictionary<string, UInt32>();
                    foreach (var user in usersData.EnumerateArray())
                    {
                        var ipAddress = user.GetProperty("ip_address").GetString();
                        var userAuthorizationToken = user.GetProperty("authorization_token").GetUInt32();
                        userAuthorizationTokens[ipAddress] = userAuthorizationToken;
                    }

                    var relayData = (JsonElement)getJsonData["relay"];
                    var relayAddress = relayData.GetProperty("host").GetString();
                    var relayServerPort = relayData.GetProperty("ports").GetProperty("server").GetProperty("port").GetInt32();
                    var relayClientPort = relayData.GetProperty("ports").GetProperty("client").GetProperty("port").GetInt32();

                    return new RelaySessionAuthorization
                    {
                        SessionAuthorizationToken = sessionId,
                        UserAuthorizationTokens = userAuthorizationTokens,
                        RelayAddress = relayAddress,
                        RelayServerPort = relayServerPort,
                        RelayClientPort = relayClientPort
                    };
                }

                retries--;
                await Task.Delay(2000); // wait for 2 second before retrying
            }

            throw new Exception("Failed to get a ready relay session");
        }
    }

    public class RelaySessionAuthorization
    {
        public string? SessionAuthorizationToken { get; set; }
        public Dictionary<string, UInt32>? UserAuthorizationTokens { get; set; }
        public string? RelayAddress { get; set; }
        public int RelayServerPort { get; set; }
        public int RelayClientPort { get; set; }
    }
}
```

Вам нужно будет вернуть эту информацию соответствующему игроку.

Игрок, выступающий в роли сервера, будет нуждаться в `RelayServerPort` а все остальные будут нуждаться в `RelayClientPort`

```csharp
using System.Text.Json;
using LobbyNamespace;

class Program
{
    static async Task Main(string[] args)
    {
        // Замените YOUR_API_TOKEN на ваш реальный API-токен
        var apiToken = "YOUR_API_TOKEN";

        // 1.1.1.1 — ваш игрок, выступающий в роли сервера
        // 2.2.2.2 — ваш игрок, выступающий в роли клиента
        var ips = new List<string> { "1.1.1.1", "2.2.2.2" };

        var edgegapAPI = new EdgegapAPI(apiToken);
        var relaySessionAuthorization = await edgegapAPI.CreateRelaySession(ips);

        // Сформировать JSON-тело для сервера (1.1.1.1)
        var serverJsonBody = new
        {
            relay_address = relaySessionAuthorization.RelayAddress,
            port = relaySessionAuthorization.RelayServerPort,
            session_authorization_token = relaySessionAuthorization.SessionAuthorizationToken,
            user_authorization_token = relaySessionAuthorization.UserAuthorizationTokens["1.1.1.1"]
        };
        var serverJson = JsonSerializer.Serialize(serverJsonBody);
        Console.WriteLine($"JSON body for server (1.1.1.1):\n{serverJson}");

        // Сформировать JSON-тело для клиента (2.2.2.2)
        var clientJsonBody = new
        {
            relay_address = relaySessionAuthorization.RelayAddress,
            port = relaySessionAuthorization.RelayClientPort,
            session_authorization_token = relaySessionAuthorization.SessionAuthorizationToken,
            user_authorization_token = relaySessionAuthorization.UserAuthorizationTokens["2.2.2.2"]
        };
        var clientJson = JsonSerializer.Serialize(clientJsonBody);
        Console.WriteLine($"JSON body for client (2.2.2.2):\n{clientJson}");
    }
}
```

### Взаимодействие с API (cURL или POSTMAN)

Во время разработки вашей игры вам потребуется взаимодействовать с API для создания, управления и удаления сессий реле. Вы можете использовать инструменты, такие как cURL или POSTMAN, чтобы отправлять HTTP-запросы к API.

cURL — это инструмент командной строки, который позволяет выполнять HTTP-запросы и взаимодействовать с API прямо из терминала. POSTMAN — популярное GUI-приложение, которое упрощает тестирование API, предоставляя удобный интерфейс для создания, отправки и анализа HTTP-запросов.

Вот пример того, как создать сессию реле с помощью cURL:

```bash
curl -X POST -H "Content-Type: application/json" -H "Authorization: Token API_TOKEN" -d '{
  "users": [
    {
      "ip": "1.1.1.1"
    },
    {
      "ip": "2.2.2.2"
    }
  ],
  "webhook_url": "https://webhook.example.com/notify"
}' "https://api.edgegap.com/v1/relays/sessions"
```

{% hint style="info" %}
Не забудьте заменить `API_TOKEN` на ваш реальный relay API-токен.
{% endhint %}

API вернёт вам ответ, содержащий данные. Вам потребуется распарсить этот ответ и извлечь необходимые данные.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.edgegap.com/ru/learn/distributed-relay/relay-edgegap-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
