Учётные записи игроков
Дайте игрокам вашей игры учётную запись, которую они сохраняют в каждой игре на 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$) создаёт учётную запись без email, привязанную к
этому браузеру.
Это настоящая учётная запись: она сохраняет, загружает, и её можно позже подтвердить: игрок добавляет email и пароль, и его сохранения переходят без потерь.
Честная оговорка: очистка данных сайта теряет эту учётную запись без
возможности восстановления. Это цена «без регистрации», и ваша игра должна
сказать об этом прямо, а не позволить игроку узнать это самому.
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 и остальные не знают, и им
не нужно знать, какой именно сервер ответил.
Что должен уметь ваш сервер#
Четыре адреса обязательны, какой бы вход вы ни предложили:
| Для чего это нужно | Что ожидает игра |
|---|---|
| Играть как гость | Вернуть учётную запись без email |
| Кто этот игрок | Отображаемое имя, id, признак гостя |
| Прочитать сохранение | Сохранённые данные |
| Записать сохранение | Сохранить то, что отправляет игра |
Затем, в зависимости от того, что вы решите предложить:
- Форма с окном требует страницу входа на вашем сервере, которая сообщает игре, кто вошёл, как только это произошло.
- Прямая форма требует ещё один адрес, принимающий имя пользователя и пароль и отвечающий да или нет.
Точные адреса, форма каждого ответа и коды ошибок находятся в репозитории, в
docs/engine-player-accounts.md. Этот файл, это контракт; эта страница, это
карта.
Заметка: ничего из этого не работает в нативном экспорте. Команды
Player*нуждаются в сервере, к которому можно обратиться, и в браузере, через который к нему обращаются, а у нативной сборки нет ни того, ни другого.
Команды#
| Команда | Что она делает |
|---|---|
PlayerAvailable() | Настроен ли сервис учётных записей для этой игры |
PlayerLoginAsync() | Открывает окно входа. Возвращает задачу |
PlayerLoginAsync(user, pass) | Входит напрямую, только на вашем собственном сервере. Возвращает задачу |
PlayerGuestAsync() | Создаёт учётную запись без email. Возвращает задачу |
PlayerLoggedIn() | Вошёл ли кто-то в систему прямо сейчас |
PlayerName() | Его отображаемое имя |
PlayerID() | Его непрозрачный id, свой для этой игры |
PlayerIsGuest() | Является ли это учётной записью без email |
PlayerSaveAsync() | Записывает сохранение. Возвращает задачу |
PlayerLoadAsync() | Считывает его обратно. Возвращает задачу |
PlayerRefreshAsync() | Обновляет токен до того, как он истечёт |
PlayerLogout() | Выходит из системы на этом устройстве |
PlayerLastError() | Почему последний вызов завершился ошибкой |