📜 ⬆️ ⬇️

Adding records with OAuth 2: Laravel Passport + Unity. Part 2



Continuation of the article about adding records from the game to the site from a specific user. In the first part, we made a page of records for Laravel and prepared an API for adding them - both an anonymous and an authorized user. In this part, we will finalize the finished game on Unity about the Rat on the Wall, go for your account and send records to the site on Laravel using an authorization token.

Training


As an example, I suggest using my runner about a rat with the simplest functionality — the rat crawls along the wall, and the pans fall from above. You can download the project for Unity 2017.1 from github . If you wish, you can use any other project, here we consider only the principle and one of the options for its implementation.

The tutorial also uses a ready-made Laravel site from the first part. Download it here . To make the site available at http://127.0.0.1:8000/ , you need to use the command:
')
php artisan serve 

Let's open the project in Unity . The basic gameplay is as follows.



When you click on Play, we will be able to control the rat, moving along the wall within certain limits and evading falling pans. At the left above there is a point counter, below - the rest of lives. When you press Esc , the pause menu is displayed - an empty socket, to which we have to add an authorization form. After the end of the game, you can restart the button R.

First of all, let's add anonymous records.

Anonymous records


Create a new script in the Scripts folder using the Create -> C# Script command on the Project panel. Let's call it WWWScore and open the resulting WWWScore.cs file in your Unity editor ( Visual Studio , MonoDevelop ).

First, add a field to store the server address. We specify [SerializeField] in order to be able to change this private variable through the Inspector panel in Unity .

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

By default, the address is set to the same as that of our site on Laravel. If desired, it can be changed.

We now turn to the function of adding a record from an anonymous user. This function will send a POST request to the server and wait for a response. As a variant of processing such requests, we use the coroutine ( Coroutine ) to start the function in parallel. The function to use in the coroutine will look like this:

 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(" !"); } } 

We add data for the POST request (the value of the score variable, which we will transmit when calling the coroutine from the GameController class), form a request at http://127.0.0.1:8000/api/anonymrecord and wait for the result. As soon as a response comes from the server (or expires the request timeout), the Record Added! Message will be displayed in the console ! , or error information in case of failure.

Add the WWWScore.cs script to the Game Controller object via the Add Component button on the Inspector panel, or simply by dragging the script with the mouse on the object.



Now edit the GameController.cs script, adding the coroutine call there.

 void Update () { if (gameover){ // ,          if (!gameoverStarted) { gameoverStarted = true; //   restartText.SetActive(true); //   //   StartCoroutine(GetComponent<WWWScore>().AddRecord(score)); } // ... } else { // ... } // ... } 

Coroutine is called once at the moment when the game was over - immediately after the game restart interface was turned on. When you click on R, the scene will be restarted, and you can again reach the end of the game, causing the addition of a record.

Save the script and check the work of the game. After some time after the end of the game in the console, the message Record added!



You can open the plate of records on the site and make sure that the request was actually sent.



Anonymous addition of records works. Let's go to the authorization.

Authorization code


Add the Login(string email, string password) authorization function Login(string email, string password) to WWWScore.cs , which we will later transmit to the coroutine. Similar to the function of adding records, it forms a POST request to our site on Laravel , passing the data set to it at http://127.0.0.1:8000/oauth/token . We considered the necessary data for authorization in the first part of the article.

 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", "*"); 

After receiving the result of the query, you need to convert the data from json . This can be done using the JsonUtility by converting json to an object. We describe the class of the object in the same WWWScore.cs file before describing the WWWScore class.

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

As we remember, in the resulting json object there will be 4 fields, but we only need the access_token field, which we describe in the class. Now you can add the json conversion to the object itself.

 TokenResponse tokenResponse = JsonUtility.FromJson<TokenResponse>(w.text); 

After receiving the authorization token, we need to save it. For simplicity, we will use the PlayerPrefs class, which is intended just for saving user preferences.

 PlayerPrefs.SetString("Token", tokenResponse.access_token); 

After we saved the token, you can use it to add a record from this user. But before that, we can also request information about the current user in order to display in the game which user is logged on. To do this, call the coroutine with the corresponding function, which is not yet available.

 StartCoroutine(GetUserInfo()); 

Let's write this function.

Full code of the Login function
 [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()); } } } } 


Getting user information


We need to perform a GET request at http://127.0.0.1:8000/api/user , registering the authorization data in the Headers of the request and without passing any other data in the request ( 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); 

Similar to the previous function, as a response we get json , for parsing which we need to create a separate class with the only field we need from the whole json structure - the name.

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

Convert json to an object of this class.

 UserInfo userInfo = JsonUtility.FromJson<UserInfo>(w.text); 

Save the username in the settings.

 PlayerPrefs.SetString("UserName", userInfo.name); 

Full code of the GetUserInfo function
 //  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("  !"); } } } } 


Changes in the code for adding records


To add records from an authorized user, we will slightly change the function code AddRecord(int score) . Add a check if the authorization token is filled in the settings, and if so, we will add it to the Headers in the same way as when receiving information about the user, with the only difference that we are still passing the record in the POST request data.

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

Full code of the modified AddRecord function
 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(" !"); } } 


Exit code


To leave the user out of the game, you must remove all data about him in the settings. In our case, we have no other settings, so we just clear all the settings. Be careful with this in your projects.

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


Main controller


Now we will prepare the main game controller ( GameController.cs ) to work with user authorization. We will need objects with the loginObj authorization loginObj and the logoutObj exit panel so that we can switch them. On the authorization panel there will be input fields for the email address ( inputFieldEmail ) and for the password ( inputFieldPassword ). We will also need the text userNameText to display the name of the user who logged in to your account.

 //   public GameObject loginObj; //   public GameObject logoutObj; //  E-mail public GameObject inputFieldEmail; //   public GameObject inputFieldPassword; //     public GameObject userNameText; 

For authorization, we will create the Login() function, which will be called by clicking on the Login button, read the email address with a password and call the coroutine with the same function from WWWScore.cs .

 public void Login() { var email = inputFieldEmail.GetComponent<InputField>().text; var password = inputFieldPassword.GetComponent<InputField>().text; StartCoroutine(GetComponent<WWWScore>().Login(email, password)); } 

The exit function is very simple - it will be called by clicking on the Exit button and calling the same function from WWWScore.cs without any parameters.

 public void Logout() { GetComponent<WWWScore>().Logout(); } 

To switch the visibility of authorization and exit panels, we will check whether the corresponding setting is saved in PlayerPrefs and display the required panel depending on it.

 public void SetLoginVisible() { if (PlayerPrefs.HasKey("Token")) { loginObj.SetActive(false); logoutObj.SetActive(true); } else { loginObj.SetActive(true); logoutObj.SetActive(false); } } 

Similarly, to display the user name, we check the name setting and if it does not exist, we write Anonymous .

 public void SetUserName() { if (PlayerPrefs.HasKey("UserName")) { userNameText.GetComponent<Text>().text = PlayerPrefs.GetString("UserName"); } else { userNameText.GetComponent<Text>().text = ""; } } 

The last two functions should be called only when the corresponding settings are changed (and during the initialization process), but within the framework of this tutorial you can do this in the Update() function:

 void Update () { // ... //   // ... SetUserName(); SetLoginVisible(); } 

Now go to the visual component.

Authorization interface


Add an authorization interface. Check the Enable checkbox of the Pause socket attached to the Canvas object. Create a new empty object ( Create Empty ), call it Login and put it inside the Pause panel, on the same level as the Title (the inscription Pause ). Add to it the component Graphic Raycaster (for correct work with nested elements).



In this Login object we will add two input fields, InputFieldEmail and InputFieldPassword ( UI -> Input Field ), changing the placeholder text for clarity. For the Input Field component, in the InputFieldEmail object InputFieldEmail we change the data type in the Content Type field to Email Address , and in the InputFieldPassword object, to Password . Add a ButtonLogin button ( UI -> Button ) to the same Login object. The interface will look something like this (if you play around with the fonts and the size of the components).



We will bind the function created earlier to the event of a click on the ButtonLogin button. On the Button component on the Inspector panel, click on the plus sign on the On Click () event, select Editor and Runtime from the list (to work correctly during the debugging process) and drag the Game Controller object there (with the mouse or by selecting it when clicking on the selection circle at the object field ). In the pop-up menu that appears after this, select the GameController component and the Login() function in it.



Uncheck the Enable checkbox on the Login object — its display is adjusted in GameController.cs .

Output interface


Create a new Logout object similar to the Login object (without forgetting the Graphic Raycaster component) nested in Pause . Add only the ButtonLogout button to the Logout object. Similar to the previous button, we will bind the GameController component's GameController same name to the click event of the click.



Remove the Enable checkbox from the Logout object and the Pause panel itself.

User Name Display


Add a text User element ( UI -> Text ) to the main Canvas before the Pause element, GameController.cs Anonymous in it (or leave it empty, since the inscription will be assigned in GameController.cs ) and placed in the upper right corner. The name of the authorized user will be displayed here.



Assigning objects to the controller


Select the GameController object. On the Inspector panel, the Game Controller component has several empty fields that we added in the code earlier. Assign the corresponding objects to them by dragging with the mouse from the Hierarchy panel or selecting from the list after clicking on the selection circle near the field.



Testing


We come to the final part - checking that everything works as it should. Run the game and click on Esc . Before us the panel of authorization will open. We will collect the data of the user registered on the site (in the last article we used habr@habrahabr.ru / habrahabr ).



Click on the Login button. If successful, after a while, the user authorization panel will change to the exit panel, leaving only the corresponding button, and instead of Anonymous , the Habr name of the user from the site will be written on the upper right.



Now, if you press Esc again and set a record, it will be sent from an authorized user, and not from an anonymous one.



This can be checked by visiting the page of records on the site.



This concludes my first tutorial. I will be glad to answer questions on it!

Full code 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(); } } 

Complete GameController.cs Code
 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 = ""; } } } 

First part
Ready project on Laravel
Unity base project ( master branch)
Ready project on Unity ( final branch)

Source: https://habr.com/ru/post/340362/


All Articles