# 게임 통합

### 왜 매치메이커 및/또는 로비가 필요한가요?

로비 서비스([#nakama-by-heroic-labs](https://docs.edgegap.com/docs.edgegap.com-ko/advanced-features/managed-clusters#nakama-by-heroic-labs "mention")) 시스템은 다양한 기술적 이유로 온라인 멀티플레이어 게임에서 중요한 역할을 합니다.

**플레이어 관리**

로비 또는 매치메이커 시스템은 실력 수준, 지리적 위치, 게임 선호도와 같은 다양한 기준에 따라 플레이어를 적절한 매치로 그룹화하여 관리하는 데 도움을 줍니다. 이는 모든 참가자에게 균형 잡히고 즐거운 게임 경험을 제공합니다.

**백엔드**

플레이어가 백엔드와 직접 통신하도록 두지 않고 중앙화된 접근 지점을 제공하고자 합니다. 예를 들어 특정 제공업체에서 VM을 시작하는 API 토큰이 있다면, 해당 토큰을 게임 클라이언트에 하드코딩하여 모든 플레이어가 접근할 수 있게 해서는 안 됩니다!

**보안**

중앙화된 로비 또는 매치메이커 시스템을 사용하면 게임의 보안을 유지하는 데 도움이 됩니다. 플레이어 연결을 필터링하고 검증함으로써 매치메이커는 무단 접근을 방지하고 사용자 데이터를 보호하며 잠재적인 치팅 또는 해킹 시도를 완화할 수 있습니다.

**게임 세션 관리**

로비 또는 매치메이커 시스템은 필요에 따라 게임 세션을 생성, 업데이트 및 종료하여 관리합니다. 이를 통해 플레이어가 게임에 원활하게 참가하거나 이탈할 수 있고, 게임 인스턴스를 효율적으로 시스템에서 관리할 수 있습니다.

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

### 당장 필요하지 않을 수 있습니다! (개발 모드)

개발 단계에서는 여러 기술적 이유로 완전한 로비나 매치메이커 시스템이 필요하지 않을 수 있습니다:

**단순화된 테스트 환경**

개발 초기 단계에서는 핵심 게임 메커니즘과 네트워킹 기능 구현 및 테스트에 집중할 수 있습니다. 로비나 매치메이커 시스템의 복잡성을 피함으로써 개발 팀 내 소규모 인원으로 게임을 더 쉽게 테스트하고 디버그할 수 있습니다.

**더 빠른 반복**

로비나 매치메이커가 없으면 매치메이킹 프로세스에 미치는 영향을 걱정하지 않고 게임 코드를 빠르게 변경해 새로운 기능이나 수정 사항을 테스트할 수 있습니다. 이는 보다 민첩한 개발 접근 방식을 가능하게 하여 팀이 게임 메커니즘을 더 빠르게 반복하고 개선할 수 있게 합니다.

**자원 할당**

완전한 규모의 로비나 매치메이커 시스템을 개발하려면 상당한 시간과 자원이 필요합니다. 이러한 구성요소의 구현을 미룸으로써 리소스를 보다 효율적으로 할당하고 우선적으로 핵심 게임플레이 및 네트워킹 기능에 집중할 수 있습니다.

**확장성 문제**

개발 초기에는 관리해야 할 플레이어 수가 많지 않을 가능성이 큽니다. 결과적으로 플레이어를 연결하는 기본 시스템이나 테스트 목적의 수동 연결만으로도 충분할 수 있습니다. 그러나 게임이 성장하고 더 많은 플레이어를 끌어들이면 플레이어 연결을 관리하고 원활한 게임플레이를 보장하기 위해 로비나 매치메이커 시스템이 점점 더 필요해집니다.

향후 로비나 매치메이커 시스템을 수용할 수 있도록 게임 아키텍처를 설계하는 것이 중요합니다. 이러한 선견지명적 접근은 더 큰 그룹의 플레이어로 게임을 테스트할 준비가 되었을 때 구성요소를 통합하기 쉽게 만들어 원활한 전환과 더 완성도 높은 최종 제품을 보장합니다.

### Edgegap API와 상호작용하기

Edgegap는 플레이어가 최저 지연을 위해 가장 가까운 릴레이에 연결할 수 있도록 릴레이 세션과 상호작용하는 간단한 API를 제공합니다.

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

API와 상호작용하려면, `Authorization Header Token` 을(를) 요청에 제공해야 합니다.

{% hint style="info" %}
계정을 등록하고 릴레이 프로파일을 생성하면 토큰을 얻을 수 있습니다. [**여기에서 등록하세요**](https://app.edgegap.com/auth/register)

릴레이 전용으로 지정된 토큰이 필요하다는 점에 유의하세요. 이미 표준 API 토큰이 있는 경우에는 작동하지 않습니다.
{% endhint %}

이 항목은 대시보드의 사이드바에서 접근할 수 있습니다.

<figure><img src="https://1562312210-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FsR0dHSFv9ymoC0DO5G8J%2Fuploads%2Fgit-blob-52bbb7251013ce023d5ceb59e7a078c1755312cb%2Fcreatetoken.png?alt=media" alt=""><figcaption></figcaption></figure>

토큰을 얻으면 모든 API 호출의 요청 헤더에 포함하세요. API 키 앞에는 반드시 접두사를 붙이는 것을 잊지 마세요 `토큰`.

{% hint style="success" %}
API 사용에 익숙하지 않은 경우, [이 섹션](#interacting-with-api-curl-or-postman) 을(를) 참조하여 시작하는 데 도움을 받을 수 있습니다.
{% endhint %}

### 릴레이 세션 생성하기

Distributed Relay를 사용하려면 릴레이 세션을 생성해야 합니다. 이는 `POST` 요청을 `/v1/relays/sessions` 엔드포인트로 JSON 페이로드를 보내어 수행됩니다. 세션은 수동으로, 매치메이커와 함께, 로비와 함께 또는 자체 커스텀 서비스로 생성할 수 있습니다.

이것은 실시간으로 플레이어에게 가장 적합한 사용 가능한 릴레이를 동적으로 선택하고 릴레이 접근 권한을 생성합니다. 다만 극도의 거리 차이를 피하기 위해 같은 지역 내에서 플레이어를 그룹화하도록 매치메이킹을 사전 필터링할 것을 권장합니다.

{% 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` 응답으로 세션 정보를 검색하는 데 필요한 `session_id`를 포함한 JSON 응답을 받게 됩니다.
{% 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 데이터를 추출하여 사용할 수 있습니다.

[자세한 내용은 여기를 참조하세요](https://docs.edgegap.com/docs.edgegap.com-ko/docs/api)

{% hint style="success" %}
또는 릴레이 세션을 생성할 때 \`webhook\_url\` 매개변수를 사용하여 세션이 릴레이에 할당되었거나 할당에 실패했을 때 알림을 받을 수 있습니다.
{% endhint %}

### 릴레이 세션 종료하기

릴레이와의 연결을 종료하기로 결정하면 릴레이 세션을 쉽게 종료할 수 있습니다. 다음 엔드포인트에 `DELETE` 요청을 보내야 합니다:

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

{% hint style="info" %}
종료하려는 실제 세션 ID로 `{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);
        // 서버(1.1.1.1)용 JSON 본문 생성
        {
            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}");
        // 클라이언트(2.2.2.2)용 JSON 본문 생성
        {
            var serverJsonBody = new
            var clientJsonBody = new
            port = relaySessionAuthorization.RelayServerPort,
            port = relaySessionAuthorization.RelayClientPort,
        };
        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과 같은 도구를 사용하여 API에 HTTP 요청을 보낼 수 있습니다.

cURL은 터미널에서 직접 HTTP 요청을 만들고 API와 상호작용할 수 있게 해주는 명령줄 도구입니다. POSTMAN은 HTTP 요청을 생성, 전송 및 분석할 수 있도록 사용자 친화적인 인터페이스를 제공하는 인기 있는 GUI 애플리케이션입니다.

```bash
다음은 cURL을 사용하여 릴레이 세션을 생성하는 예시입니다:
  "users": [
    {
      "ip": "1.1.1.1"
    },
    {
      "ip": "2.2.2.2"
    }
  ],
  "webhook_url": "https://webhook.example.com/notify"
curl -X POST -H "Content-Type: application/json" -H "Authorization: Token API_TOKEN" -d '{
```

{% hint style="info" %}
}' "<https://api.edgegap.com/v1/relays/sessions>" `API_TOKEN` 을(를) 실제 릴레이 API 토큰으로 교체하는 것을 잊지 마세요.
{% endhint %}

API는 또한 데이터를 포함한 응답을 반환합니다. 이 응답을 파싱하여 필요한 데이터를 추출해야 합니다.
