Акаунти гравців

Дайте гравцям вашої гри акаунт, який вони зберігають у кожній грі на Aukimi, зі збереженнями, що йдуть за ними з одного пристрою на інший, на сервісі Aukimi або на вашому власному сервері.

Одним рядком#

Гравець входить один раз на play.aukimi.com і знаходить цей акаунт у кожній грі, зробленій на Aukimi (разом зі збереженнями). Ваша гра лише запитує акаунт, вона ніколи не бачить пароль.

Чому ваша гра ніколи не бачить пароль#

Опублікувати гру може будь-хто. Гра, що запитує пароль від Aukimi, стала б цілком переконливою фішинговою сторінкою, і жодні добрі наміри з вашого боку не змінили б того, що міг би зробити інший розробник.

Тож пароль вводиться на play.aukimi.com і більше ніде. Ваша гра викликає PlayerLoginAsync(), відкривається вікно браузера в кольорах самого Aukimi, гравець підтверджує вхід, і вікно передає вашій грі токен.

Цей токен видано лише для вашої гри. Токен гри A, пред'явлений грі B, відхиляється, не ігнорується, а саме відхиляється. У цьому й суть: акаунт спільний між іграми, а доступ ні. Без цього одна зловмисна гра могла б читати збереження гравця в усіх інших.

Що ви отримуєте про гравця#

Дві речі, і не більше:

Ви отримуєтеВи ніколи не отримуєте
Відображуване ім'я (PlayerName())Адресу електронної пошти
Непрозорий id, різний у кожній грі (PlayerID())Будь-що, що ідентифікує їх деінде

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

Ніщо не блокує ігровий цикл#

Кожна команда, що звертається до мережі, повертає номер задачі, а не результат. Ваш цикл продовжує працювати на шістдесяти кадрах за секунду, доки запит у польоті (саме тому гра не може просто «зачекати на сервер»).

job = PlayerLoginAsync()

Потім, раз на кадр:

etat$ = PlatformJobState(job)
if etat$ = "done"
    resultat$ = PlatformJobResult(job)
    PlatformJobRelease(job)
endif
if etat$ = "error"
    Print(PlatformJobError(job))
    PlatformJobRelease(job)
endif

Звільніть задачу, щойно прочитали її результат. Задача, яку ви ніколи не звільните, тримає свій результат у пам'яті до кінця сесії. Нічого не ламається, ніщо не попереджає вас, і витік проявляється лише в довгій сесії гри.

Гра без акаунта#

Не кожен хоче реєструватися перед тим, як спробувати гру. PlayerGuestAsync(name$) створює акаунт без пошти, збережений у цьому браузері.

Це справжній акаунт: він зберігає, завантажує, і його можна підтвердити пізніше (гравець додає пошту й пароль, і наявні збереження йдуть за ним, не переносячись окремо).

Чесна застереження: очищення даних сайту в браузері губить цей акаунт, і відновити його неможливо. Це ціна за «без реєстрації», і ваша гра має сказати про це прямо, а не дати гравцю дізнатися самому. PlayerIsGuest() існує саме для цього речення.

Збереження#

job = PlayerSaveAsync(donnees$)
job = PlayerLoadAsync()

Збереження це JSON вашої власної форми, збережений під парою (цей гравець, ця гра). Він слідує за ними на інший браузер, іншу машину, іншу платформу.

Для жменьки значень SetCloudDataVariable() і CloudDataVariable() простіші: без задачі, без JSON. Спершу перевірте CloudDataAllowed(): вона повертає 0, коли жоден гравець не увійшов, а запис попри це мовчки нікуди не потрапить.

Малювання екрана#

Aukimi не малює жодної форми входу. Форма, складена рушієм, завжди виглядала б як вебсторінка, кинута поверх піксель-арт гри, і це була б єдина частина вашої гри, яку ви не могли б перестилізувати.

Ви малюєте її самі. Єдина частина, яка мусить прийти з браузера, це введення тексту, тож CreateEditBox() дає вам справжнє поле вводу, яке ви позиціонуєте самі:

CreateEditBox(1)
EditBoxPosition(1, 20, 42)
EditBoxSize(1, 60, 8)
nom$ = EditBoxText(1)

Демо Player Account у списку прикладів Engine це повноцінний робочий екран приблизно на сотню рядків. Відкрийте його, прочитайте, а потім заміните кожен Print() власною графікою.

Перш ніж це запрацює#

Гру потрібно оголосити з вашого акаунта Aukimi, у розділі My games (aukimi.com/app/games): назва, адреса, за якою в неї можна грати, і адреси, куди вашій грі дозволено повертатися. До того PlayerAvailable() повертає 0, і добра гра каже «акаунти недоступні» замість того, щоб виглядати завислою.

Той самий екран показує, хто грає. Читайте ці цифри як те, чим вони є: він рахує гравців, які увійшли і розпочаті сесії, а не зіграні партії. Гра, яка нікого ніколи не просить увійти, не покаже там нічого, і екран каже про це прямо, замість того щоб показувати мовчазний нуль.

Використання власного сервера акаунтів#

Усе вище звертається до сервісу акаунтів Aukimi. Якщо ви розміщуєте гру на власному сервері, ті самі дванадцять команд можуть звертатися до вашого сервісу замість цього, і ваш скрипт не зміниться ні на рядок.

В Engine: панель Multiplayer → розділ Player accounts → оберіть My own server → заповніть одне поле, адресу вашого сервера. Ця адреса це налаштування сцени, тож дві гри можуть використовувати два різні сервіси.

Два способи увійти гравцю#

Вибір робиться у вашому скрипті, а не в налаштуванні. Обидві форми існують незалежно від того, на який сервер ви вказали.

Вікно (те, що ви вже знаєте)#

job = PlayerLoginAsync()

Відкривається вікно, гравець входить у ньому, і воно передає вашій грі право діяти від його імені. Із сервісом Aukimi це вікно, play.aukimi.com. З вашим власним сервером цю сторінку будуєте ви самі.

Напряму, іменем користувача й паролем#

job = PlayerLoginAsync(username$, password$)

Жодного вікна. Ваша гра читає те, що гравець набрав на вашому власному екрані, і надсилає це на ваш сервер. Простіше написати, і це дозволяє зберегти вигляд вашої гри від початку до кінця.

Попередження: у цій формі пароль подорожує з вашої гри на ваш сервер у тому вигляді, як його набрали. Два наслідки, обидва тверді: адреса вашого сервера має починатися з https:// (звільнений від цього лише тест на власній машині), і ця форма призначена лише для сервера, яким володієте ви.

play.aukimi.com відмовляє в цьому навмисно. Aukimi ніколи не хоче опинитися в позиції, де бачить пароль гравця, і так само не мала б грати у це гра, яку написали не ви.

Два варіанти незалежні від налаштування: саморобний сервер цілком може пропонувати лише вікно і ніколи не приймати пароль напряму.

Конкретний приклад#

Гра з власним екраном «Увійти»: два текстові поля, намальовані грою, одна кнопка, і ось що за нею стоїть.

job = PlayerLoginAsync(typedName$, typedPassword$)
DO
    etat$ = PlatformJobState(job)
    if etat$ = "done"
        PlatformJobRelease(job)
        Print("Welcome " + PlayerName())
    endif
    if etat$ = "error"
        PlatformJobRelease(job)
        Print(PlayerLastError())
    endif
    Sync()
LOOP

Усе після входу ідентичне решті цієї сторінки: PlayerSaveAsync, PlayerLoadAsync, PlayerName та інші не знають і не переймаються тим, який сервер відповів.

Що має вміти відповідати ваш сервер#

Чотири адреси обов'язкові, який би вхід ви не пропонували:

Для чогоЩо очікує гра
Грати як гістьПовернути акаунт без пошти
Хто цей гравецьВідображуване ім'я, id, чи це гість
Прочитати збереженняЗбережені дані
Записати збереженняЗберегти те, що надсилає гра

Далі, залежно від того, що ви вирішите пропонувати:

  • Форма з вікном потребує сторінки входу на вашому сервері, яка повідомляє грі, хто увійшов, коли все завершено.
  • Пряма форма потребує ще однієї адреси, яка приймає ім'я користувача й пароль і відповідає «так» чи «ні».

Точні адреси, форма кожної відповіді та коди помилок містяться в репозиторії, у docs/engine-player-accounts.md. Той файл це контракт; ця сторінка це карта.

Примітка: ніщо з цього не працює в нативному експорті. Команди Player* потребують сервера, з яким говорити, і браузера, крізь який говорити, а в нативній збірці немає ні того, ні іншого.

Команди#

КомандаЩо вона робить
PlayerAvailable()Чи налаштований сервіс акаунтів для цієї гри
PlayerLoginAsync()Відкриває вікно входу. Повертає задачу
PlayerLoginAsync(user, pass)Входить напряму, лише на вашому власному сервері. Повертає задачу
PlayerGuestAsync()Створює акаунт без пошти. Повертає задачу
PlayerLoggedIn()Чи хтось зараз увійшов
PlayerName()Його відображуване ім'я
PlayerID()Його непрозорий id, специфічний для цієї гри
PlayerIsGuest()Чи це акаунт без пошти
PlayerSaveAsync()Записує збереження. Повертає задачу
PlayerLoadAsync()Читає його назад. Повертає задачу
PlayerRefreshAsync()Оновлює токен до того, як він спливе
PlayerLogout()Виходить на цьому пристрої
PlayerLastError()Чому провалився останній виклик