# Photon Fusion 1

### Photon Fusion на Arbitrium

Это руководство поможет вам создать безголовый сервер на Edgegap для проекта Unity с использованием [Photon Fusion](https://doc.photonengine.com/en-us/fusion/current/getting-started/fusion-intro) в качестве сетевого решения.

Мы будем использовать пример проекта [Tanknarok от Photon Fusion](https://doc.photonengine.com/en-us/fusion/current/samples/fusion-tanknarok) в качестве примера.

Поскольку Edgegap работает лучше, когда он знает, где находятся все игроки (чтобы выбрать сервер в оптимальной локации), игра Tanknarok сначала запускается в режиме, хостируемом клиентом, а затем переключается на сервер Edgegap после того, как все игроки присоединились. Это потому, что в Tanknarok есть играбельное лобби, которое запускается до начала матча, где игроки могут свободно присоединяться и выходить. Логика работы следующая:

1. Игроки подключаются к одной и той же комнате, используя `AutoHostOrClient` режим, который автоматически выбирает первого игрока в комнате в качестве хоста.
2. Все игроки отправляют свой IP-адрес хосту при присоединении.
3. После того как игроки подтвердили готовность, хост использует эти IP-адреса, чтобы запустить сервер Edgegap-версию игры со случайным кодом комнаты (используя guid).
4. Когда хост видит, что экземпляр Edgegap завершил развертывание, он сообщает другим игрокам новый код комнаты.
5. Все игроки, включая хоста, отключаются от своей текущей комнаты и переподключаются в режиме Client к новой комнате, размещенной сервером Edgegap.
6. Когда сервер Edgegap видит, что все ожидаемые игроки присоединились, он запускает матч.
7. Когда сервер Edgegap видит, что все покинули матч, он завершает свою работу.

### Настройка учетной записи

Чтобы запустить это приложение, вам понадобится учетная запись Photon и учетная запись на Edgegap. Сначала вам нужно создать Fusion-приложение на панели управления Photon и указать id приложения в файле `PhotonAppSettings` scriptable object. Затем вам нужно добавить приложение и создать токен API на панели управления. Вы будете использовать их для установки App Name, Version и Api Token в `EdgegapConfig` scriptable object файле.

{% hint style="info" %}
При создании приложения Edgegap этой программе требуется 512 единиц CPU и 512 МБ памяти.
{% endhint %}

Также есть два плагина для помощи в разработке: Newtonsoft Json для лучшей сериализации Json и [ParrelSync](https://github.com/VeriorPies/ParrelSync) для более простого тестирования с несколькими редакторами Unity.

### Шаг 1: Настройка серверного режима

Вы можете взять модифицированный проект Tanknarok на [GitHub](https://github.com/edgegap). Также вам нужно импортировать [Photon Fusion SDK](https://doc.photonengine.com/en-us/fusion/current/getting-started/sdk-download) в проект.

Проект Photon Fusion Tanknarok потребовал небольших изменений для работы как сервер. Это в основном включало автоматический старт матча и установку режима.

1. Изменен `GameLauncher.cs` чтобы запускаться при старте с `GameMode.Server`.
2. Изменен `FusionLauncher.cs` для вызова `_spawnWorldCallback` после запуска игры вместо того, чтобы полагаться на `InstantiatePlayer` (так как для сервера игроки не инстанцируются).
3. Пропуск лобби-сцены, если это сервер, и переход сразу к матчу, когда все игроки присоединились.

Это уже выполнено в модифицированном проекте Tanknarok, доступном на нашем [GitHub](https://github.com/edgegap).

#### Dockerfile

Docker-файл довольно простой. Мы используем базовый образ ubuntu, копируем скомпилированную сборку linux-сервера, выставляем права на файл и запускаем его.

### Шаг 2: Первый клиент

Начальный клиент берет на себя роль хоста и управление сервером Edgegap. Он делает это через `EdgegapManager.cs` файл, который, в свою очередь, использует `EdgegapAPIInterface.cs` файл, инкапсулирующий большую часть функциональности взаимодействия с Edgegap.

В `GameManager.cs`, в `OnAllPlayersReady` функции для хоста будет развернут сервер Edgegap вместо старта матча. Это делается на основе IP-адресов всех игроков. Корутины Unity позволяют выполнять эти действия асинхронно. Метод Deploy принимает функцию обратного вызова, которой передается имя новой комнаты, которую будет использовать сервер. Отправляется функция Photon RPC, чтобы сигнализировать всем клиентам о переключении.

```cs
    if (EdgegapManager.EdgegapPreServerMode)
    {
        //если хост, запустить новый сервер edgegap
        //как только сервер будет готов, подключиться к нему

        FindObjectOfType<GameLauncher>().ShowMatchStartingUI();

        if (Object.HasStateAuthority)
        {
            string[] ips = PlayerManager.allPlayers.Select(p => p.ipAddress).ToArray();
            StartCoroutine(EdgegapManager.Instance.Deploy(ips, RpcOnEdgegapServerReady));
        }
    }
    else
    {
        LoadLevel(_levelManager.GetRandomLevelIndex(), -1);
    }
```

Переключение происходит в пустой сцене под названием `TransferringScene`.

Внутри EdgegapManager создается случайный код комнаты и передается вместе с количеством игроков как переменные окружения в новый экземпляр через API развертывания. Это дополняется `status` api-вызовами, которые начинают раз в секунду проверять, когда экземпляр завершит развертывание. Вы также можете использовать [webhook](https://docs.edgegap.com/ru/docs/deployment/arbitrium-deploy-webhook) опции.

```cs
    /// <summary>
    /// Вызывает Edgegap API для развертывания сервера
    /// </summary>
    /// <param name="clientIpList">Список IP-адресов клиентов</param>
    /// <param name="OnDeployed">Метод обратного вызова с именем сессии в качестве параметра</param>
    public IEnumerator Deploy(string[] clientIpList, Action<string> OnDeployed = null)
    {
        var roomCode = Guid.NewGuid().ToString();
        yield return EdgegapAPIInterface.RequestDeployment(clientIpList,
             new Dictionary<string, string> {
                { "room_code", roomCode },
                { "player_count", clientIpList.Length.ToString() } });

        yield return EdgegapAPIInterface.WaitForDeployment();

        OnDeployed(roomCode);
    }
```

### Шаг 3: Присоединяющиеся игроки

Помимо переключения на новый сервер Edgegap по сигналу, присоединяющимся игрокам также нужно сообщить хосту свой IP-адрес. Поскольку Photon не предоставляет эту информацию, RPC-вызов в `GameManager` под названием `ShareIpAddress` вызывается из `Player.cs` класса в `Spawned` методе.

Затем хост сохраняет эти IP-адреса в классе игрока для всех игроков на основе того, кто был источником RPC-сообщения:

```cs
    [Rpc(sources:RpcSources.All, RpcTargets.StateAuthority)]
    public void RpcShareIpAddress(string ipAddress, RpcInfo info = default)
    {
        if (info.Source == PlayerRef.None)
        {
            // для локального игрока источник будет None
            Player.local.ipAddress = ipAddress;
            return;
        }

        foreach (var player in PlayerManager.allPlayers)
        {
            if (player.playerID == info.Source.PlayerId)
            {
                player.ipAddress = ipAddress;
                break;
            }
        }
    }
```

### Шаг 4: Очистка

Вернувшись на сервер, измененный `GameManager.cs` отслеживает количество игроков в матче в основной функции Update. Он как проверяет, когда целевое количество игроков присоединилось, чтобы можно было начать матч, так и определяет, когда пора завершить работу. Если игра идет больше минуты и игроков не осталось, он вызывает Edgegap API, чтобы остановить собственное развертывание.

```cs
    if (EdgegapManager.IsServer())
    {
        if (GameManager.playState == GameManager.PlayState.LOBBY)
        {
            // если мы в лобби и все игроки присоединились, начинаем матч
            if (PlayerManager.allPlayers.Count >= EdgegapManager.ExpectedPlayerCount)
            {
                OnAllPlayersReady();
            }
        }

        if(Time.time > 60 && PlayerManager.allPlayers.Count == 0
            && GameManager.playState != GameManager.PlayState.TRANSITION)
        {
            // если прошла минута и никого нет, останавливаем сервер
            Debug.LogWarning("Shutting Down");
            StartCoroutine(EdgegapAPIInterface.StopDeploymentFromServer());
            GameManager.playState = PlayState.TRANSITION;
        }
    }
```

Теперь у вас есть проект Photon Fusion, готовый к развертыванию по запросу!
