Добавление рекордов с OAuth 2: Laravel Passport + Unity. Часть 2

в 17:38, , рубрики: C#, laravel, oauth2, ratwall, unity, unity3d, разработка игр

Добавление рекордов с OAuth 2: Laravel Passport + Unity. Часть 2 - 1

Продолжение статьи про добавление рекордов из игры на сайт от конкретного пользователя. В первой части мы сделали страничку рекордов на Laravel и подготовили API для их добавления — как анонимным, так и авторизированным пользователем. В этой части будем дорабатывать готовую игру на Unity про Крысу на Стене, заходить за свой аккаунт и отправлять рекорды на сайт на Laravel с использованием токена авторизации.

Подготовка

В качестве примера предлагаю воспользоваться моим раннером про крысу с простейшим функционалом — крыса ползёт по стене, а сверху падают сковородки. Скачать проект для Unity 2017.1 можно с гитхаба. При желании можно использовать и любой другой проект, здесь рассматривается только принцип и один из вариантов его реализации.

Также в туториале используется готовый сайт на Laravel из первой части. Скачать его можно здесь. Чтобы сайт был доступен по адресу http://127.0.0.1:8000/, нужно воспользоваться командой:

php artisan serve

Откроем проект в Unity. Базовый игровой процесс выглядит следующим образом.

Добавление рекордов с OAuth 2: Laravel Passport + Unity. Часть 2 - 2

При нажатии на 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, или же просто перетащив скрипт мышкой на объект.

Добавление рекордов с OAuth 2: Laravel Passport + Unity. Часть 2 - 3

Теперь отредактируем скрипт GameController.cs, добавив туда вызов сопрограммы.

void Update () {
	if (gameover){
		// Действия, выполняемые только один раз после конца игры до рестарта
		if (!gameoverStarted) {
			gameoverStarted = true; // Существующий код
			restartText.SetActive(true); // Существующий код
			// Отправляем рекорд
			StartCoroutine(GetComponent<WWWScore>().addRecord(score));
		}
		// ...
	} else {
		// ...
	}
	// ...
}

Сопрограмма вызывается один раз в тот момент, когда игра была закончена — сразу после включения интерфейса рестарта игры. При нажатии на R сцена будет перезапущена, и можно будет опять дойти до конца игры, вызвав добавление рекорда.

Сохраним скрипт и проверим работу игры. Через некоторое время после окончания игры в консоли появится сообщение Рекорд добавлен!

Добавление рекордов с OAuth 2: Laravel Passport + Unity. Часть 2 - 4

Можно открыть табличку рекордов на сайте и убедиться в том, что запрос действительно был отправлен.

Добавление рекордов с OAuth 2: Laravel Passport + Unity. Часть 2 - 5

Анонимное добавление рекордов работает. Перейдём к авторизации.

Код авторизации

Добавим функцию авторизации 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());

Напишем и эту функцию.

Полный код функции login

[Serializable]
public class TokenResponse
{
    public string access_token;
}

public class WWWScore : MonoBehaviour {
	// ...

    public IEnumerator login(string email, string password)
    {
        WWWForm form = new WWWForm();
        form.AddField("grant_type", "password");
        form.AddField("client_id", "3");
        form.AddField("client_secret", "W82LfjDg4DpN2gWlg8Y7eNIUrxkOcyPpA3BM0g3s");
        form.AddField("username", email);
        form.AddField("password", password);
        form.AddField("scope", "*");

        WWW w = new WWW(serverURL + "oauth/token", form);

        yield return w;

        if (!string.IsNullOrEmpty(w.error))
        {
            Debug.Log(w.error);
        }
        else
        {
            TokenResponse tokenResponse = JsonUtility.FromJson<TokenResponse>(w.text);

            if (tokenResponse == null)
            {
                Debug.Log("Конвертирование не удалось!");
            }
            else
            {
                // Сохраняем токен в настройках
                PlayerPrefs.SetString("Token", tokenResponse.access_token);
                Debug.Log("Токен установлен!");
                // Запрашиваем имя пользователя
                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);

Полный код функции getUserInfo

// Класс TokenResponse
// ...

[Serializable]
public class UserInfo
{
    public string name;
}

public class WWWScore : MonoBehaviour {
	// ...
	
	// Функция login
	// ...

    public IEnumerator getUserInfo()
    {
        Dictionary<string, string> headers = new Dictionary<string, string>();
        headers.Add("Authorization", "Bearer " + PlayerPrefs.GetString("Token"));
        WWW w = new WWW(serverURL + "api/user", null, headers);

        yield return w;

        if (!string.IsNullOrEmpty(w.error))
        {
            Debug.Log(w.error);
        }
        else
        {
            UserInfo userInfo = JsonUtility.FromJson<UserInfo>(w.text);

            if (userInfo == null)
            {
                Debug.Log("Конвертирование не удалось!");
            }
            else
            {
                // Сохраняем токен в настройках
                PlayerPrefs.SetString("UserName", userInfo.name);
                Debug.Log("Имя пользователя установлено!");
            }
        }
    }
}

Изменения в коде добавления рекордов

Чтобы добавлять рекорды от авторизированного пользователя, мы немного изменим код функции 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);
}

Полный код изменённой функции addRecord

public IEnumerator addRecord(int score)
{
	WWWForm form = new WWWForm();
	form.AddField("score", score);

	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);
	}

	yield return w;

	if(!string.IsNullOrEmpty(w.error)) {
		Debug.Log(w.error);
	} else {
		Debug.Log("Рекорд добавлен!");
	}
}

Код выхода

Чтобы выйти за пользователя из игры, необходимо удалить все данные о нем в настройках. В нашем случае у нас нет никаких других настроек, поэтому мы просто очищаем все настройки. Будьте аккуратнее с этим в своих проектах.

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 (для корректной работы со вложенными элементами).

Добавление рекордов с OAuth 2: Laravel Passport + Unity. Часть 2 - 6

В этот объект Login добавим два поля ввода, InputFieldEmail и InputFieldPassword (UI -> Input Field), поменяв им текст плейсхолдера для наглядности. Компоненту Input Field у объекта InputFieldEmail сменим тип данных в поле Content Type на Email Address, а у объекта InputFieldPassword — на Password. Добавим кнопку ButtonLogin (UI -> Button) в этот же объект Login. Интерфейс будет выглядеть примерно так (если поиграться со шрифтами и размером компонентов).

Добавление рекордов с OAuth 2: Laravel Passport + Unity. Часть 2 - 7

Привяжем созданную ранее функцию к событию клика по кнопке ButtonLogin. У компонента Button на панели Inspector нажмём на плюсик у события On Click (), выберем Editor and Runtime из списка (для корректной работы в процессе отладки) и перетянем туда объект Game Controller (мышкой или же выбрав его при клике на кружок выбора у поля объекта). В появившемся после этого выпадающем меню выберем компонент GameController и функцию login() в нём.

Добавление рекордов с OAuth 2: Laravel Passport + Unity. Часть 2 - 8

Снимем галочку Enable у объекта Login — его отображение регулируется в GameController.cs.

Интерфейс выхода

Создадим новый объект Logout аналогично объекту Login (не забыв про компонент Graphic Raycaster) вложенным в Pause. Добавим объекту Logout только кнопку ButtonLogout. Аналогично прошлой кнопке, привяжем к событию клика функцию logout() компонента GameController одноименного объекта.

Добавление рекордов с OAuth 2: Laravel Passport + Unity. Часть 2 - 9

Снимем галочку Enable у объекта Logout и у самой панели Pause.

Отображение имени пользователя

Добавим текстовый элемент User (UI -> Text) в главный Canvas до элемента Pause, написав в нём Аноним (либо оставив пустым, т.к. надпись будет назначаться в GameController.cs) и поместив в верхний правый угол. Здесь будет отображаться имя авторизированного пользователя.

Добавление рекордов с OAuth 2: Laravel Passport + Unity. Часть 2 - 10

Назначение объектов контроллеру

Выберем объект GameController. На панели Inspector у компонента Game Controller есть несколько пустых полей, которые мы добавляли в коде ранее. Назначьте им соответствующие объекты, перетащив мышкой из панели Hierarchy или выбрав из списка после нажатия на кружок выбора у поля.

Добавление рекордов с OAuth 2: Laravel Passport + Unity. Часть 2 - 11

Тестирование

Мы подошли к заключительной части — проверки, что всё работает так, как надо. Запустим игру и нажмём на Esc. Перед нами откроется панель авторизации. Наберём данные зарегистрированного на сайте пользователя (в прошлой статье мы использовали habr@habrahabr.ru / habrahabr).

Добавление рекордов с OAuth 2: Laravel Passport + Unity. Часть 2 - 12

Нажмём на кнопку Войти. В случае успеха через некоторое время панель авторизации пользователя сменится на панель выхода, оставив только соответствующую кнопку, а вместо Аноним справа вверху будет написано Habr — имя пользователя с сайта.

Добавление рекордов с OAuth 2: Laravel Passport + Unity. Часть 2 - 13

Теперь, если снова нажать на Esc и поставить рекорд, он будет отправляться от авторизированного пользователя, а не от анонимного.

Добавление рекордов с OAuth 2: Laravel Passport + Unity. Часть 2 - 14

Это можно проверить, зайдя на страницу рекордов на сайте.

Добавление рекордов с OAuth 2: Laravel Passport + Unity. Часть 2 - 15

На этом мой первый туториал завершается. Буду рад ответить на вопросы по нему!

Полный код WWWScore.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[Serializable]
public class TokenResponse
{
    public string access_token;
}

[Serializable]
public class UserInfo
{
    public string name;
}

public class WWWScore : MonoBehaviour {

    [SerializeField]
    private string serverURL = "http://127.0.0.1:8000/";

	public IEnumerator addRecord(int score)
    {
        WWWForm form = new WWWForm();
        form.AddField("score", score);

        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);
        }

        yield return w;

        if(!string.IsNullOrEmpty(w.error)) {
        	Debug.Log(w.error);
        } else {
        	Debug.Log("Рекорд добавлен!");
        }
    }

    public IEnumerator login(string email, string password)
    {
        WWWForm form = new WWWForm();
        form.AddField("grant_type", "password");
        form.AddField("client_id", "3"); // Пример заполнения
        form.AddField("client_secret", "W82LfjDg4DpN2gWlg8Y7eNIUrxkOcyPpA3BM0g3s"); // Пример заполнения
        form.AddField("username", email);
        form.AddField("password", password);
        form.AddField("scope", "*");

        WWW w = new WWW(serverURL + "oauth/token", form);

        yield return w;

        if (!string.IsNullOrEmpty(w.error))
        {
            Debug.Log(w.error);
        }
        else
        {
            TokenResponse tokenResponse = JsonUtility.FromJson<TokenResponse>(w.text);

            if (tokenResponse == null)
            {
                Debug.Log("Конвертирование не удалось!");
            }
            else
            {
                // Сохраняем токен в настройках
                PlayerPrefs.SetString("Token", tokenResponse.access_token);
                Debug.Log("Токен установлен!");
                // Запрашиваем имя пользователя
                StartCoroutine(getUserInfo());
            }
        }
    }

    public IEnumerator getUserInfo()
    {
        Dictionary<string, string> headers = new Dictionary<string, string>();
        headers.Add("Authorization", "Bearer " + PlayerPrefs.GetString("Token"));
        WWW w = new WWW(serverURL + "api/user", null, headers);

        yield return w;

        if (!string.IsNullOrEmpty(w.error))
        {
            Debug.Log(w.error);
        }
        else
        {
            UserInfo userInfo = JsonUtility.FromJson<UserInfo>(w.text);

            if (userInfo == null)
            {
                Debug.Log("Конвертирование не удалось!");
            }
            else
            {
                // Сохраняем токен в настройках
                PlayerPrefs.SetString("UserName", userInfo.name);
                Debug.Log("Имя пользователя установлено!");
            }
        }
    }

    public void logout()
    {
        PlayerPrefs.DeleteAll();
    }
}

Полный код GameController.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

// Класс сковородки
[System.Serializable]
public class PanClass
{
    // Префаб сковородки
    public GameObject panObj;   
    // Пауза до начала падения сковородок
    public float start;       
    // Пауза между сковородками  
    public float pause; 		
}

public class GameController : MonoBehaviour {

    // Объект сковородки
    public PanClass pan;
    // Точка спавна
    public Vector2 spawnValues;
    // Объект с интерфейсом результата
    public GameObject scoreText;
    // Объект с интерфейсом рестарта игры
    public GameObject restartText;
    // Объект с интерфейсом панели паузы
    public GameObject pausePanel;
    // Время между повышениями результата
    public float scoreRate = 1.0F;
    // Значение, на которое повышается результат
    public int scoreAdd = 10;   
    // Результат
    public static int score;
    // Признак завершения игры
    public static bool gameover;
    // Время до следующего результата
    private float nextScore = 0.0F;
    // Признак того, что единоразовые действия после конца игры были выполнены
    private bool gameoverStarted;

    // Объект авторизации
    public GameObject loginObj;
    // Объект выхода
    public GameObject logoutObj;
    // Поле E-mail
    public GameObject inputFieldEmail;
    // Поле Пароль
    public GameObject inputFieldPassword;
    // Надпись с именем пользователя
    public GameObject userNameText;


    void Start () {
        // Инициализация значений (для рестарта)
        gameover = false;
        score = 0;
        gameoverStarted = false;
        // Запустить падение сковородок
        StartCoroutine(panSpawn());
    }

    void FixedUpdate()
    {
        if (!gameover)
        {
            // Обновить результат
            scoreText.GetComponent<Text>().text = score.ToString();
        }
    }

    void Update () {
        if (gameover){
            // Действия, выполняемые только один раз после конца игры до рестарта
            if (!gameoverStarted) {
                gameoverStarted = true;
                // Отобразить интерфейс рестарта
                restartText.SetActive(true);
                // Отправляем рекорд
                StartCoroutine(GetComponent<WWWScore>().addRecord(score));
            }

            // Рестарт по R
            if (Input.GetKey(KeyCode.R))
            {
                // Перезапуск сцены
                SceneManager.LoadScene(0);
            }         
        } else {
            if (Input.GetKeyDown(KeyCode.Escape))
            {
                if (Time.timeScale != 0) {
                    // Поставить на паузу
                    Time.timeScale = 0;
                    pausePanel.SetActive(true);
                } else {
                    // Снять с паузы
                    Time.timeScale = 1;
                    pausePanel.SetActive(false);
                }
            }
        }

        // Подсчет результата
        if (!gameover && (Time.time > nextScore))
        {
            nextScore = Time.time + scoreRate;
            score = score + scoreAdd;
        }

        setUserName();
        setLoginVisible();
    }

    // Падение сковородки
    IEnumerator panSpawn()
    {
        // Пауза до начала падения сковородок
        yield return new WaitForSeconds(pan.start);

        // Бесконечный цикл, до конца игры
        while (!gameover)
        {
            // Генерировать крутящуюся сковородку в случайном месте на определенной высоте
            Vector2 spawnPosition = new Vector2(Random.Range(-spawnValues.x, spawnValues.x), spawnValues.y);
            Quaternion spawnRotation = Quaternion.identity;
            Instantiate(pan.panObj, spawnPosition, spawnRotation);
            yield return new WaitForSeconds(pan.pause);
        }
    }

    // Авторизация
    public void login()
    {
        var email = inputFieldEmail.GetComponent<InputField>().text;
        var password = inputFieldPassword.GetComponent<InputField>().text;
        StartCoroutine(GetComponent<WWWScore>().login(email, password));
    }

    // Выход
    public void logout()
    {
        GetComponent<WWWScore>().logout();
    }

    // Поменять видимость формы авторизации
    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 = "Аноним";
        }
    }
}

Первая часть
Готовый проект на Laravel
Базовый проект на Unity (ветка master)
Готовый проект на Unity (ветка final)

Автор: KuzyT

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js