Skip to content

🇬🇧 English | 🇺🇦 Українська

← До змісту

13. Профілі на практиці: сценарії гри

Розділ про те, як ассети S3 Storage Profile розкладаються по реальних конфігураціях проєкту — однокористувацька гра, listen-сервер, виділений сервер, настільний застосунок — і звідки в кожній з них беруться ключі.

Попередні розділи описують ці речі окремо: 2. Налаштування — що таке профіль, 3. Облікові дані — де тримати ключі, 10. Сценарії розгортання — хто звертається до сховища. Тут — їхній перетин, на конкретних ассетах із конкретними значеннями.


Головне правило

Профіль зберігає не ключі, а джерело, з якого їх узяти. Тому питання «який профіль зробити» завжди зводиться до питання «який процес ним користуватиметься» — бо джерело має бути доступним саме тому процесу.

З цього випливає наслідок, який економить найбільше часу:

Профіль із Environment variables — це серверний профіль. Якщо він поїде у збірку до гравців і ті його викличуть, змінних оточення там не буде, і кожен запит завершиться Authentication Error. Це не помилка плагіна — це джерело, обране не для того процесу.

Якщо і клієнт, і сервер працюють з одним бакетом, але з різними правами — це два профілі з однаковими адресою й бакетом і різними джерелами, а не один спільний.


Чи потрібен вам профіль узагалі

Ситуація Що брати
Одне сховище на весь проєкт Project Settings і Get Default S3 Client. Профіль не потрібен
Два й більше сховищ Профіль на кожне
Конфігурація відома лише в рантайм (прийшла від бекенда) Get Named S3 Client — профіль тут не допоможе, бо ассет фіксується на етапі розробки

Профілі починають окупатися саме з другого сховища: спільна пара редакторських ключів одна на проєкт і може описувати тільки одне з них — див. «Ключі профілю».


Як профіль отримує ключі: чотири джерела в дії

Credential Source Хто постачає ключі Доступно з Blueprint Типовий процес
Environment variables Середовище запуску процесу — (нічого викликати не треба) Виділений сервер, бекенд, збірковий агент
Local user store Кінцевий користувач, через Save S3 Credentials ✅ так Настільний застосунок
Anonymous Ніхто, запити не підписуються Публічний бакет, підписані посилання
Supplied in code Ваш код, нодою Set Static Credentials на клієнті ✅ так Клієнт гри з короткоживучими ключами від бекенда

Дві схожі ноди, які діють на різні клієнти — найважливіше місце цього розділу.

Нода На що діє
Set Runtime Credentials (на підсистемі) Тільки на клієнта за замовчуванням
Set Static Credentials (на клієнті) На той клієнт, на якому викликана — зокрема на клієнта профілю

Клієнти з Get S3 Client For Profile беруть ключі виключно з Credential Source свого ассета, і Set Runtime Credentials вони не бачать. Це тиха невдача: сама нода не помиляється, просто профіль поводиться так, ніби ключів йому не давали. Для профілю потрібна саме Set Static Credentials.

Для профілю з Supplied in code ключі встановлюються так:

On Login Response                        (ваш бекенд повернув короткоживучі ключі)
   │
   └─► Get S3 Subsystem → Get S3 Client For Profile (SP_PlayerSaves)
          │
          └─► Set Static Credentials
                 Access Key Id      : (від бекенда)
                 Secret Access Key  : (від бекенда)
                 Session Token      : (від бекенда)
                 Expires In Seconds : 3600

Ключі лишаються тільки в пам'яті: на диск нічого не пишеться, у збірці нічого немає. Плагін припиняє ними користуватися трохи раніше за вказаний строк, щоб запит, підписаний під самий кінець, не прибув уже після його завершення. Expires In Seconds = 0 — ключі без терміну дії.

З C++ доступний ще й провайдер, який сам ходить по нові ключі, коли старі спливають:

US3Subsystem* Subsystem = GetGameInstance()->GetSubsystem<US3Subsystem>();
US3Client*    Client    = Subsystem->GetClientForProfile(ArchiveProfile);

Client->SetCredentialsProvider(
    MakeShared<FS3CredentialsProvider_Callback, ESPMode::ThreadSafe>(/* ваш запит до бекенда */));

Сценарій 1. Однокористувацька гра

Хмарні збереження плюс публічний контент — два сховища з різними правами.

Ассети:

Ассет Provider / бакет Credential Source Навіщо
SP_PlayerSaves ваш основний, my-game-saves Supplied in code Збереження гравця: запис, приватно
SP_PublicContent той самий чи інший, my-game-content Anonymous Патчі й ассети: лише читання, бакет публічний

Граф після входу гравця (бекенд віддав короткоживучі ключі):

On Login Response
   │
   └─► Get S3 Subsystem → Get S3 Client For Profile (SP_PlayerSaves)
          │
          └─► Set Static Credentials
                 Access Key Id      : (від бекенда)
                 Secret Access Key  : (від бекенда)
                 Session Token      : (від бекенда)
                 Expires In Seconds : 3600

Далі — звичайне збереження:

Event Save Game
   │
   ├─► Save Game to Slot                        (спершу локально)
   │
   └─► Get S3 Subsystem → Get S3 Client For Profile (SP_PlayerSaves) → S3 Upload File
              │
              ├─ On Success → «збережено у хмару»
              └─ On Failure → лишити локальне збереження, спробувати пізніше

Публічний контент не потребує нічого:

Get S3 Subsystem → Get S3 Client For Profile (SP_PublicContent) → S3 Download File

Чому так. Anonymous для публічного бакета означає, що у збірці немає взагалі нічого, що можна видобути. А Supplied in code для збережень — що ключі живуть лише в пам'яті процесу: на диск не пишуться, у збірку не потрапляють, і після виходу гравця зникають разом із сесією.

Не плутайте з Local user store. Той навмисно зберігає ключі на диск (зашифровано) — це правильно для настільного застосунку, де ключі належать користувачеві й мають пережити перезапуск, і зайве для гри, де бекенд щоразу видає нові.

Set Static Credentials діє на клієнта одразу — Forget Profile Client тут не потрібен. Він потрібен в іншому випадку: коли ключі змінилися за джерелом (наприклад, у локальному сховищі користувача), бо тоді клієнт продовжить користуватися вже отриманими.


Сценарій 2. Мережева гра: listen-сервер

Хост — і сервер, і гравець. Головне рішення — хто звертається до сховища.

Ассети:

Ассет Credential Source Хто ним користується
SP_WorldState Supplied in code — ключі хост отримує від вашого бекенда Тільки хост
SP_PlayerUploads Supplied in code — свої ключі в кожного гравця Кожен клієнт своїм

Стан світу — лише хост:

Event Save World State
   │
   └─► Switch Has Authority
          ├─ Authority → Get S3 Client For Profile (SP_WorldState) → S3 Upload File
          │                 └─ On Success → Multicast: «світ збережено»
          └─ Remote    → нічого не робити

Без Switch Has Authority подія виконається у всіх: сесія на вісьмох дасть вісім однакових завантажень одного файлу, вісім оплачених запитів і непередбачуваний порядок запису.

Власні дані гравця — кожен сам. Тут кожен клієнт має отримати свої ключі з політикою, обмеженою його префіксом (users/${user_id}/*). Спільний ключ на всіх — це можливість перезаписати чужі дані.


Сценарій 3. Виділений сервер

Випадок, заради якого профілі й варто заводити: бінарник сервера у гравців не буває, тож довгоживучі ключі тут доречні, а Environment Variable Prefix дає кілька сховищ в одному оточенні.

Ассети:

Ассет Credential Source Environment Variable Prefix Читає змінні
SP_Saves Environment variables SAVES SAVES_ACCESS_KEY_ID, SAVES_SECRET_ACCESS_KEY
SP_Replays Environment variables REPLAYS REPLAYS_ACCESS_KEY_ID, REPLAYS_SECRET_ACCESS_KEY

Запуск сервера:

# systemd unit, Docker, скрипт запуску - будь-що, що ставить оточення
export SAVES_ACCESS_KEY_ID=...
export SAVES_SECRET_ACCESS_KEY=...

export REPLAYS_ACCESS_KEY_ID=...
export REPLAYS_SECRET_ACCESS_KEY=...

./MyGameServer -log

Два сховища, дві пари ключів, кожна з власною політикою — і жодного рядка коду, який би їх розрізняв: профіль сам знає, який префікс читати.

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

А що на клієнтах. Вони до сховища не звертаються взагалі — або звертаються лише за підписаними посиланнями, які видає сервер:

Клієнт: RPC до сервера — «хочу залити збереження»
   │
Сервер: перевіряє право, далі Make Presigned URL (PUT, 900 секунд)
   │
   └─► повертає адресу клієнтові
         │
Клієнт: звичайний HTTP PUT за цією адресою

Клієнт при цьому налаштований як Anonymous — підписувати йому нічого.

Не кладіть SP_Saves у клієнтську збірку в надії, що «там просто не спрацює». Воно справді не спрацює, але помилка виглядатиме як Authentication Error без очевидної причини. Якщо клієнту потрібен доступ до того самого бакета — заведіть окремий профіль з Anonymous або Local user store.


Сценарій 4. Настільний застосунок

Бакет належить користувачеві, тож ключі — його, а не ваші.

Ассет: SP_UserStorage, Credential Source = Local user store, Local Store Profile Name = default.

(натиснуто «Підключитися» на екрані налаштувань)
   │
   └─► Save S3 Credentials (Profile Name: default, ключі з полів вводу)
          │
          └─► Forget Profile Client (SP_UserStorage)
                 │
                 └─► Get S3 Client For Profile (SP_UserStorage) → S3 List Objects (Max Keys 1)
                        ├─ On Success → до основного екрана
                        └─ On Failure → показати помилку одразу, поки користувач ще тут

Перевірка одразу після введення — те, що економить найбільше підтримки: та сама помилка через півгодини під час першого справжнього збереження вже нічого користувачеві не пояснює.

Кілька застосунків на одній машині — дайте кожному своє Local Store Profile Name. Сховище до того ж солиться назвою проєкту, тож два різні застосунки не прочитають ключі один одного навіть за однакового імені профілю.


Зведена таблиця

Сценарій Профіль Credential Source Де насправді лежать ключі Blueprint?
Сингл: збереження SP_PlayerSaves Supplied in code Лише в пам'яті, Set Static Credentials
Сингл: публічний контент SP_PublicContent Anonymous Ніде — підпису немає
Listen: стан світу SP_WorldState Supplied in code Лише в пам'яті, у хоста
Виділений сервер SP_Saves, SP_Replays Environment variables Оточення процесу, свій префікс на профіль
Клієнт при виділеному сервері SP_PublicRead Anonymous Ніде; доступ через підписані посилання
Настільний застосунок SP_UserStorage Local user store Зашифрований файл у теці користувача

Типові помилки

Помилка Що видно Що робити
Environment variables у збірці для гравців Authentication Error на кожній операції Це серверне джерело. Клієнту — Anonymous, Local user store або підписані посилання
Set Runtime Credentials разом із профілем Профіль поводиться так, ніби ключів не давали Ця нода діє лише на клієнта за замовчуванням. Для профілю — Set Static Credentials на його клієнті
Ключі змінили в локальному сховищі, а працюють старі Стара відмова 403 повторюється Forget Profile Client — клієнт кешує вже отримане. Для Set Static Credentials не потрібен: вона замінює ключі одразу
Два профілі, ключі лише у спільній редакторській секції Запит до одного сховища підписаний ключем від іншого Заповніть Editor Access Key Id на самому профілі — див. 3. Облікові дані
Порожній Local Store Profile Name у двох профілів Обидва читають одні й ті самі ключі Дайте кожному своє ім'я

← До змісту

Далі: 14. Рецепти облікових даних