Sending REST API request is the simplest form of network communication in Unity. Of course, it requires you to have a server with endpoints to send requests there.
I’m not going to dig into server programming, but I’ll create simple REST API using Mocky.
Creating API
So first let’s make some simple responses from the server.
GetQuote
http://www.mocky.io/v2/5cb15fdf330000ee1557204f
{ "quote":"It’s not that we use technology, we live technology.", "author":"Godfrey Reggio" }
GetBlogInfo
http://www.mocky.io/v2/5cb28ba13000007b00a78c92
{ "blogAddress":"https://patrykgalach.com", "bloggerName":"Patryk Galach", "bloggerAge": 25 }
That should be enough. It’s also important to save generated URL so we can call this API later.
Client implementation
With API ready it’s time to implement logic in our Unity project.
The first thing that I really like to do is to create a class that contains all keys and strings for API calls.
/// <summary> /// This class store server config keys and urls. /// </summary> public class ServerConfig { // URL with place to put API method in it. public const string SERVER_API_URL_FORMAT = "http://www.mocky.io/v2/{0}"; // Mocky generates random strings for your endpoints, you should name them properly! public const string API_GET_QUOTE = "5cb15fdf330000ee1557204f"; public const string API_GET_BLOG_INFO = "5cb28ba13000007b00a78c92"; }
With that, if you would need to change paths or URLs for API, you will have to do it only in one place.
Next step will be to create some process that will handle sending requests and receiving responses from a server.
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using UnityEngine.Networking; /// <summary> /// This class is responsible for handling REST API requests to remote server. /// To extend this class you just need to add new API methods. /// </summary> public class ServerCommunication : PersistentLazySingleton<ServerCommunication> { #region [Server Communication] /// <summary> /// This method is used to begin sending request process. /// </summary> /// <param name="url">API url.</param> /// <param name="callbackOnSuccess">Callback on success.</param> /// <param name="callbackOnFail">Callback on fail.</param> /// <typeparam name="T">Data Model Type.</typeparam> private void SendRequest<T>(string url, UnityAction<T> callbackOnSuccess, UnityAction<string> callbackOnFail) { StartCoroutine(RequestCoroutine(url, callbackOnSuccess, callbackOnFail)); } /// <summary> /// Coroutine that handles communication with REST server. /// </summary> /// <returns>The coroutine.</returns> /// <param name="url">API url.</param> /// <param name="callbackOnSuccess">Callback on success.</param> /// <param name="callbackOnFail">Callback on fail.</param> /// <typeparam name="T">Data Model Type.</typeparam> private IEnumerator RequestCoroutine<T>(string url, UnityAction<T> callbackOnSuccess, UnityAction<string> callbackOnFail) { var www = UnityWebRequest.Get(url); yield return www.SendWebRequest(); if (www.isNetworkError || www.isHttpError) { Debug.LogError(www.error); callbackOnFail?.Invoke(www.error); } else { Debug.Log(www.downloadHandler.text); ParseResponse(www.downloadHandler.text, callbackOnSuccess, callbackOnFail); } } /// <summary> /// This method finishes request process as we have received answer from server. /// </summary> /// <param name="data">Data received from server in JSON format.</param> /// <param name="callbackOnSuccess">Callback on success.</param> /// <param name="callbackOnFail">Callback on fail.</param> /// <typeparam name="T">Data Model Type.</typeparam> private void ParseResponse<T>(string data, UnityAction<T> callbackOnSuccess, UnityAction<string> callbackOnFail) { var parsedData = JsonUtility.FromJson<T>(data); callbackOnSuccess?.Invoke(parsedData); } #endregion }
I’m using a lot of generic functions and callbacks to make it as universal as possible. It will be handy when we start making separate functions for each API request.
And using data serialization, we can prepare data models for API responses. That way we will receive already parsed response in data object which is much easier to work with. You can also notice that I’ve used one of the singletons for ServerCommunication class.
So let’s create our first API request using first server response.
/// <summary> /// Quote is data model for server response to GetQuote. /// </summary> [System.Serializable] public class QuoteData { public string quote; public string author; }
/// <summary> /// This method call server API to get a quote. /// </summary> /// <param name="callbackOnSuccess">Callback on success.</param> /// <param name="callbackOnFail">Callback on fail.</param> public void GetQuote(UnityAction<QuoteData> callbackOnSuccess, UnityAction<string> callbackOnFail) { SendRequest(string.Format(ServerConfig.SERVER_API_URL_FORMAT, ServerConfig.API_GET_QUOTE), callbackOnSuccess, callbackOnFail); }
Isn’t it easy to create API call? ?
Let’s make another one!
/// <summary> /// Blog Info is data model for server response to GetBlogInfo. /// </summary> [System.Serializable] public class BlogInfoData { public string blogAddress; public string bloggerName; public int bloggerAge; }
/// <summary> /// This method call server API to get an info about my blog. /// </summary> /// <param name="callbackOnSuccess">Callback on success.</param> /// <param name="callbackOnFail">Callback on fail.</param> public void GetBlogInfo(UnityAction<BlogInfoData> callbackOnSuccess, UnityAction<string> callbackOnFail) { SendRequest(string.Format(ServerConfig.SERVER_API_URL_FORMAT, ServerConfig.API_GET_BLOG_INFO), callbackOnSuccess, callbackOnFail); }
That’s why I like to make a universal code. ❤️
So the only thing that left is to integrate our server to the game.
Example
In this example, we will display the server response to the different requests.
Let’s start with a little component to handle displaying and clearing messages from the screen.
using UnityEngine; using TMPro; /// <summary> /// This class is responsible for label control. /// </summary> public class UIServerResponseViewer : MonoBehaviour { // Reference to label. [SerializeField] private TextMeshProUGUI label; /// <summary> /// Method used to display message on screen. /// </summary> /// <param name="message">Message.</param> public void ShowMessage(string message) { label.text = message; } /// <summary> /// Method used to clear label content. /// </summary> public void ClearMessage() { label.text = string.Empty; } }
Now as we already have 2 requests implemented, it’s time to use them!
I’m going to create 2 separate buttons scripts that will call these API methods to get server response.
using UnityEngine; /// <summary> /// This is Quote Button script. /// It's responsible for sending GetQuote request to the server. /// </summary> public class UIServerQuoteButton : MonoBehaviour { // Reference to label in scene. [SerializeField] private UIServerResponseViewer display; /// <summary> /// Method called on button click event. /// </summary> public void CallAPI() { display.ClearMessage(); ServerCommunication.Instance.GetQuote(APICallSucceed, APICallFailed); } /// <summary> /// Request was successful /// </summary> /// <param name="quoteData">Quote data.</param> private void APICallSucceed(QuoteData quoteData) { display.ShowMessage(string.Format("{0}\n- {1}", quoteData.quote, quoteData.author)); } /// <summary> /// There were some problems with request. /// </summary> /// <param name="errorMessage">Error message.</param> private void APICallFailed(string errorMessage) { display.ShowMessage(string.Format("Error\n{0}", errorMessage)); } }
using UnityEngine; /// <summary> /// This is Blog Info Button script. /// It's responsible for sending GetBlogInfo request to the server. /// </summary> public class UIServerBlogInfoButton : MonoBehaviour { // Reference to label in scene. [SerializeField] private UIServerResponseViewer display; /// <summary> /// Method called on button click event. /// </summary> public void CallAPI() { display.ClearMessage(); ServerCommunication.Instance.GetBlogInfo(APICallSucceed, APICallFailed); } /// <summary> /// Request was successful /// </summary> /// <param name="blogInfoData">Blog info data.</param> private void APICallSucceed(BlogInfoData blogInfoData) { display.ShowMessage(string.Format("Blog: {0}\nAuthor: {1}\nHis age: {2}", blogInfoData.blogAddress, blogInfoData.bloggerName, blogInfoData.bloggerAge)); } /// <summary> /// There were some problems with request. /// </summary> /// <param name="errorMessage">Error message.</param> private void APICallFailed(string errorMessage) { display.ShowMessage(string.Format("Error\n{0}", errorMessage)); } }
And now it’s just a matter of running game to test if it’s going to work! And of course it will!
See? I told you! ?
Of course IRL this will be much more complex, and you probably would like to authenticate users first before sending them internal data on which you would operate, but this is something for next time! ?
As always, this project is available on my public repository ?, so you can take a look on that.
I hope you liked it and see you next time! ?
Hey Patryk, excellent article!
Check this asset, using Promises instead of callbacks to avoid the callback hell => https://github.com/proyecto26/RestClient
Looks interesting! I’ll give it a try in the near future! ?
Hey it’s great! Thanks!
Using this how can we track the progress?
Well.. here you either get response or not. You can’t really track progress of those methods. You can track progress for downloading/uploading WebRequests but you need to specify handlers for them.