Продолжение статьи про добавление рекордов из игры на сайт от конкретного пользователя. В первой части мы сделали страничку рекордов на Laravel и подготовили API для их добавления — как анонимным, так и авторизированным пользователем. В этой части будем дорабатывать готовую игру на Unity про Крысу на Стене, заходить за свой аккаунт и отправлять рекорды на сайт на Laravel с использованием токена авторизации.
Подготовка
В качестве примера предлагаю воспользоваться моим раннером про крысу с простейшим функционалом — крыса ползёт по стене, а сверху падают сковородки. Скачать проект для Unity 2017.1 можно с гитхаба. При желании можно использовать и любой другой проект, здесь рассматривается только принцип и один из вариантов его реализации.
Также в туториале используется готовый сайт на Laravel из первой части. Скачать его можно здесь. Чтобы сайт был доступен по адресу http://127.0.0.1:8000/, нужно воспользоваться командой:
php artisan serve
Откроем проект в Unity. Базовый игровой процесс выглядит следующим образом.
При нажатии на Play мы сможем управлять крысой, перемещаясь по стене в определенных границах и уклоняясь от падающих сковородок. Слева вверху идёт счётчик очков, внизу — остаток жизней. При нажатии на Esc отображается меню паузы — пустая панелька, на которую нам предстоит добавить форму авторизации. После окончания игру можно перезапустить кнопкой R.
Первым делом займемся добавлением анонимных рекордов.
Анонимные рекорды
Создадим новый скрипт в папке Scripts
при помощи команды Create -> C# Script
на панели Project
. Назовем его WWWScore
и откроем получившийся файл WWWScore.cs
в используемом вами редакторе для Unity (Visual Studio, MonoDevelop).
Первым делом добавим поле для хранения адреса сервера. Укажем [SerializeField]
для того, чтобы можно было изменять эту приватную переменную через панель Inspector
в Unity.
[SerializeField]
private string serverURL = "http://127.0.0.1:8000/";
По-умолчанию адрес зададим тем же, что и у нашего сайта на Laravel. При желании его можно будет изменить.
Теперь перейдём к функции добавления рекорда от анонимного пользователя. Эта функция будет отправлять POST-запрос на сервер и дожидаться ответа. Как вариант обработки таких запросов, воспользуемся сопрограммой (Coroutine) для запуска функции параллельно. Функция для использования в сопрограмме будет выглядеть следующим образом:
public IEnumerator AddRecord(int score)
{
WWWForm form = new WWWForm();
form.AddField("score", score);
WWW w = new WWW(serverURL + "api/anonymrecord", form);
yield return w;
if(!string.IsNullOrEmpty(w.error)) {
Debug.Log(w.error);
} else {
Debug.Log("Рекорд добавлен!");
}
}
Мы добавляем данные для POST-запроса (значение переменной score
, которую мы будем передавать при вызове сопрограммы из класса GameController
), формируем запрос по адресу http://127.0.0.1:8000/api/anonymrecord и ждем результата. Как только приходит ответ от сервера (или заканчивает срок ожидания запроса), в консоли будет выведено сообщение Рекорд добавлен!, или же информация об ошибке в случае неудачи.
Добавим скрипт WWWScore.cs
объекту Game Controller
через кнопку Add Component на панели Inspector
, или же просто перетащив скрипт мышкой на объект.
Теперь отредактируем скрипт GameController.cs
, добавив туда вызов сопрограммы.
void Update () {
if (gameover){
// Действия, выполняемые только один раз после конца игры до рестарта
if (!gameoverStarted) {
gameoverStarted = true; // Существующий код
restartText.SetActive(true); // Существующий код
// Отправляем рекорд
StartCoroutine(GetComponent<WWWScore>().AddRecord(score));
}
// ...
} else {
// ...
}
// ...
}
Сопрограмма вызывается один раз в тот момент, когда игра была закончена — сразу после включения интерфейса рестарта игры. При нажатии на R сцена будет перезапущена, и можно будет опять дойти до конца игры, вызвав добавление рекорда.
Сохраним скрипт и проверим работу игры. Через некоторое время после окончания игры в консоли появится сообщение Рекорд добавлен!
Можно открыть табличку рекордов на сайте и убедиться в том, что запрос действительно был отправлен.
Анонимное добавление рекордов работает. Перейдём к авторизации.
Код авторизации
Добавим функцию авторизации Login(string email, string password)
в WWWScore.cs
, которую потом будем передавать сопрограмме. Аналогично функции добавления рекордов, она формирует POST-запрос к нашему сайту на Laravel, передавая в нём набор данных по адресу http://127.0.0.1:8000/oauth/token. Необходимый набор данных для авторизации мы рассматривали в первой части статьи.
WWWForm form = new WWWForm();
form.AddField("grant_type", "password");
form.AddField("client_id", "<Client ID>");
form.AddField("client_secret", "<Client Secret>");
form.AddField("username", email); // Параметр функции
form.AddField("password", password); // Параметр функции
form.AddField("scope", "*");
После получения результата запроса необходимо преобразовать данные из json
. Это можно сделать с помощью JsonUtility, преобразовав json
в объект. Опишем класс объекта в том же файле WWWScore.cs
до описания класса WWWScore
.
[Serializable]
public class TokenResponse
{
public string access_token;
}
Как мы помним, в получаемом объекте json
будут 4 поля, но нам нужно только поле access_token
, его мы и описываем в классе. Теперь можно добавить само конвертирование json в объект.
TokenResponse tokenResponse = JsonUtility.FromJson<TokenResponse>(w.text);
После получения токена авторизации нам нужно сохранить его. Для простоты воспользуемся классом PlayerPrefs, предназначенном как раз для сохранения пользовательских настроек.
PlayerPrefs.SetString("Token", tokenResponse.access_token);
После того, как мы сохранили токен, можно воспользоваться им для добавления рекорда от этого пользователя. Но перед этим мы можем также запросить информацию о текущем пользователе, чтобы отобразить в игре, за какого пользователя осуществлен вход. Для этого вызываем сопрограмму с соответствующей функцией, которой пока ещё нет.
StartCoroutine(GetUserInfo());
Напишем и эту функцию.
Получение информации о пользователе
Нам нужно выполнить GET-запрос по адресу http://127.0.0.1:8000/api/user, прописав в Headers запроса данные авторизации и не передавая при этом никаких других данных в запросе (null
).
Dictionary<string, string> headers = new Dictionary<string, string>();
headers.Add("Authorization", "Bearer " + PlayerPrefs.GetString("Token"));
WWW w = new WWW(serverURL + "api/user", null, headers);
Аналогично прошлой функции, в качестве ответа мы получаем json
, для разбора которого нужно создать отдельный класс с единственным нужным нам полем из всей структуры json
— именем.
[Serializable]
public class UserInfo
{
public string name;
}
Конвертируем json в объект этого класса.
UserInfo userInfo = JsonUtility.FromJson<UserInfo>(w.text);
Сохраняем имя пользователя в настройках.
PlayerPrefs.SetString("UserName", userInfo.name);
Изменения в коде добавления рекордов
Чтобы добавлять рекорды от авторизированного пользователя, мы немного изменим код функции AddRecord(int score)
. Добавим проверку, заполнен ли токен авторизации в настройках, и если да — будем добавлять его в Headers аналогично тому, как это было при получении информации о пользователе, с тем лишь отличием, что мы всё ещё передаём рекорд в данных POST-запроса.
WWW w;
if (PlayerPrefs.HasKey("Token"))
{
Dictionary<string, string> headers = new Dictionary<string, string>();
byte[] rawData = form.data;
headers.Add("Authorization", "Bearer " + PlayerPrefs.GetString("Token"));
w = new WWW(serverURL + "api/record", rawData, headers);
} else {
w = new WWW(serverURL + "api/anonymrecord", form);
}
Код выхода
Чтобы выйти за пользователя из игры, необходимо удалить все данные о нем в настройках. В нашем случае у нас нет никаких других настроек, поэтому мы просто очищаем все настройки. Будьте аккуратнее с этим в своих проектах.
public void Logout()
{
PlayerPrefs.DeleteAll();
}
Основной контроллер
Теперь подготовим основной контроллер игры (GameController.cs
) для работы с авторизацией пользователя. Нам будут нужны объекты с панелью авторизации loginObj
и панелью выхода logoutObj
, чтобы можно было переключать их. На панели авторизации будут поля ввода для электронного адреса (inputFieldEmail
) и для пароля (inputFieldPassword
). Также нам будет нужна надпись userNameText
для отображения имени пользователя, который зашел за свой аккаунт.
// Объект авторизации
public GameObject loginObj;
// Объект выхода
public GameObject logoutObj;
// Поле E-mail
public GameObject inputFieldEmail;
// Поле Пароль
public GameObject inputFieldPassword;
// Надпись с именем пользователя
public GameObject userNameText;
Для авторизации мы создадим функцию Login()
, которая будет вызываться по клику на кнопке Войти, считывать адрес электронной почты с паролем и вызывать сопрограмму с одноименной функцией из WWWScore.cs
.
public void Login()
{
var email = inputFieldEmail.GetComponent<InputField>().text;
var password = inputFieldPassword.GetComponent<InputField>().text;
StartCoroutine(GetComponent<WWWScore>().Login(email, password));
}
Функция выхода очень проста — она будет вызываться по клику на кнопке Выйти и вызывать одноименную функцию из WWWScore.cs
без каких-либо параметров.
public void Logout()
{
GetComponent<WWWScore>().Logout();
}
Для переключения видимости панелей авторизации и выхода мы будем проверять, сохранена ли соответствующая настройка в PlayerPrefs и в зависимости от этого отображать нужную панель.
public void SetLoginVisible()
{
if (PlayerPrefs.HasKey("Token"))
{
loginObj.SetActive(false);
logoutObj.SetActive(true);
}
else
{
loginObj.SetActive(true);
logoutObj.SetActive(false);
}
}
Аналогично, для отображения имени пользователя проверяем настройку имени и если её нет, пишем Аноним.
public void SetUserName()
{
if (PlayerPrefs.HasKey("UserName"))
{
userNameText.GetComponent<Text>().text = PlayerPrefs.GetString("UserName");
} else
{
userNameText.GetComponent<Text>().text = "Аноним";
}
}
Последние две функции следует вызывать только при изменении соответствующих настроек (и в процессе инициализации), но в рамках этого туториала можно делать это и в функции Update()
:
void Update () {
// ...
// Подсчет результата
// ...
SetUserName();
SetLoginVisible();
}
Теперь переходим к визуальной составляющей.
Интерфейс авторизации
Добавим интерфейс авторизации. Поставим галочку Enable панельке Pause
, вложенной в объект Canvas
. Создадим новый пустой объект (Create Empty), назовём его Login
и поместим внутрь панели Pause
, на одном уровне с Title
(надпись Пауза). Добавим ему компонент Graphic Raycaster
(для корректной работы со вложенными элементами).
В этот объект Login
добавим два поля ввода, InputFieldEmail
и InputFieldPassword
(UI -> Input Field), поменяв им текст плейсхолдера для наглядности. Компоненту Input Field у объекта InputFieldEmail
сменим тип данных в поле Content Type на Email Address, а у объекта InputFieldPassword
— на Password. Добавим кнопку ButtonLogin
(UI -> Button) в этот же объект Login
. Интерфейс будет выглядеть примерно так (если поиграться со шрифтами и размером компонентов).
Привяжем созданную ранее функцию к событию клика по кнопке ButtonLogin
. У компонента Button на панели Inspector
нажмём на плюсик у события On Click (), выберем Editor and Runtime из списка (для корректной работы в процессе отладки) и перетянем туда объект Game Controller (мышкой или же выбрав его при клике на кружок выбора у поля объекта). В появившемся после этого выпадающем меню выберем компонент GameController
и функцию Login()
в нём.
Снимем галочку Enable у объекта Login
— его отображение регулируется в GameController.cs
.
Интерфейс выхода
Создадим новый объект Logout
аналогично объекту Login
(не забыв про компонент Graphic Raycaster
) вложенным в Pause
. Добавим объекту Logout
только кнопку ButtonLogout
. Аналогично прошлой кнопке, привяжем к событию клика функцию Logout()
компонента GameController
одноименного объекта.
Снимем галочку Enable у объекта Logout
и у самой панели Pause
.
Отображение имени пользователя
Добавим текстовый элемент User
(UI -> Text) в главный Canvas
до элемента Pause
, написав в нём Аноним (либо оставив пустым, т.к. надпись будет назначаться в GameController.cs
) и поместив в верхний правый угол. Здесь будет отображаться имя авторизированного пользователя.
Назначение объектов контроллеру
Выберем объект GameController
. На панели Inspector у компонента Game Controller
есть несколько пустых полей, которые мы добавляли в коде ранее. Назначьте им соответствующие объекты, перетащив мышкой из панели Hierarchy или выбрав из списка после нажатия на кружок выбора у поля.
Тестирование
Мы подошли к заключительной части — проверки, что всё работает так, как надо. Запустим игру и нажмём на Esc. Перед нами откроется панель авторизации. Наберём данные зарегистрированного на сайте пользователя (в прошлой статье мы использовали habr@habrahabr.ru / habrahabr).
Нажмём на кнопку Войти. В случае успеха через некоторое время панель авторизации пользователя сменится на панель выхода, оставив только соответствующую кнопку, а вместо Аноним справа вверху будет написано Habr — имя пользователя с сайта.
Теперь, если снова нажать на Esc и поставить рекорд, он будет отправляться от авторизированного пользователя, а не от анонимного.
Это можно проверить, зайдя на страницу рекордов на сайте.
На этом мой первый туториал завершается. Буду рад ответить на вопросы по нему!