undo Back to Articles Index

Advanced C# Concepts for use in Unity Part 2

By: John Kidd Jr on 12/02/2022 09:38 PM

Loading Data from JSON in Unity

Games are big. Very big. It can be hard to keep all that data in the designer of Unity, and even harder to manage it. Think about a big retail game you've played... if all the assets in that game were prefabs and only stored in the editor, could you imagine how difficult it would be if you needed to make a change?

That's where loading your data from a source external to Unity can help! There are many ways to accomplish this task, you can use a flat formatted file, a database, the web... or any number of other sources!

Arguably one of the better way to store data locally and load it is the JavaScript Object Notation, or JSON for short, as it stores objects and their data in a very easy to read format for both humans and computers. Even better Unity has it's own JSON library built in we can use.

For a confusing and not super helpful look, check out the official spec here but a more helpful and easier to understand way to learn would be from W3Schools.com here.

This is what a JSON object would look like:

{
    "exampleData": "this is some example data",
    "exampleNumber": 12
}

So how is this all done in practice? It's actually easier than you might expect. We take any class at all (yes, really, any class) and we just tell the JSONUtility to encode it to JSON for us, which gives us a string with the class data encoded in it. We then save that string out using whatever method we find appropriate (which is beyond the scope of this article). Let's look at an example of this in action.

Here's an example class:

public class ExampleClass
{
    public string ExampleData;
    public int ExampleNumber;
}

Now in whatever code we are using to export this data to a JSON file, we would make sure we have an instance of this class with the data in it we wanted to save, then:

var exampleData = new ExampleClass();
// imagine we fill the example data variable with data here
var exampleJsonString = JsonUtility.ToJson();
// now we just save this string somewhere

This is very helpful not only for creating objects, but also for saving player data. All you'd need is a class containing all the data you want to save in the save data and just export it like above. To load it back in, you would use the JsonUtility.FromJson() function, but this is a little special. This function is a generic, so we tell it what class the data we pass in should be from, and it will create an object for us of that type! Here it is in example form:

var loadedJsonData = JsonUtility.FromJson<ExampleClass>(jsonDataString);

The last function of note in the JsonUtility is JsonUtility.FromJsonOverwrite() which will overwrite the fields in an object with data from the JSON string. Meaning any data that exists in the existing object that is not in the JSON string is not overwritten. Here is a quick example of how to use that one:

JsonUtility.FromJsonOverwrite(jsonDataString, existingObject);

You can even use the overwrite method effectively in a LoadData function or similar in the class you want to load itself, saving yourself the steps of needing to create an instance of an object in the first place, just replace the existingObject with the this keyword!

Loading Data from Resources and the Web

Now that we know how to load JSON data into an object... where do we get that JSON string from? There are many methods we can use to load that data, but in short the 3 I'm going to mention here are using a Unity TextAsset, utilizing regular text files in the Resources folder, and loading them from the Web.

Loading from a TextAsset

This one is pretty easy. On the Unity script we want to use to load our data, we just make a variable and expose it to the designer with the type TextAsset then we can just drag and drop any text file onto that and we can grab the raw string with the text property.

using UnityEngine;

public class TestLoader : MonoBehaviour
{
    // a public variable is exposed to the designer
    public TextAsset characterData;
    // if the variable doesn't need to be used publicly,
    // it's better to use [SerializeField] and keep it private
    [SerializeField] private TextAsset levelData;
}

From the designer it would look like this:

Text assets in Unity Designer

Once loaded in, you can then process your data like normal!

Loading from the Resources Folder

Another method of loading data into your game would be to place text files into a folder named "Resources" in your Unity project. To do so, you just need the name of the file to load, which can be something entered in the designer, something hard-coded (please don't hard code stuff), or a filename based on the scene name or something similar... so long as the file actually exists. Then you'd use the Resources.Load() function to load it in. Here's a quick example:

// load this from somewhere, use the format:
// foldername/filename
// Remember this file path will be relative to the Resources folder
// DO NOT INCLUDE the extension of the file
string path; 
var loadedJsonString = Resources.Load<TextAsset>(path);

That example will load the data from the file into a TextAsset variable, meaning we can process it in the same way as we did in the previous sections.

Notice too that the Load function is a generic, meaning we can load other types of files into our program. Things like Textures, Sprites, and sound data can all be loaded so long as you have the correct import settings applied to the resource you're trying to load.

You can even load all the files in a directory at once (they all need to be the same type) using the Resources.LoadAll() function. This function is also a generic, so we can load any type of file in with it just like before.

// the string named "path" in this example would be just a folder path
// relative to the root of the Resources folder, a blank string would
// load everything at the root level
var allTextFilesInDirectory = Resources.LoadAll<TextAsset>(path);

Loading from the Web

In much the same way as loading from a file, you can load assets from the web as well. Unity has some nifty built in libraries to do the heavy lifting for us, which makes this easier than having to learn all about REST requests.

All the built in methods are in the UnityEngine.Networking namespace, so add that to your usings first. Because sending a request of the internet can take some time, we also want to yield program control when we send the request so we don't lock up our current thread waiting for a response from the internet. Here's a quick example of a function that gets a text only response over the internet:

private IEnumerator GetStringFromUrl(string requestUrl)
{
    // create a request object by specifying the
    // URL we want to get data from
    var request = UnityWebRequest.Get(requestUrl);
    // send the request, yielding control back
    yield return request.SendWebRequest();

    // once control is returned, check the result for problems
    switch (request.result)
    {
        case UnityWebRequest.Result.Success:
            // because this is a test, we just log it
            // but you can handle this like we did with JSON
            // earlier here
            Debug.Log(request.downloadHandler.text);
            break;
        case UnityWebRequest.Result.InProgress:
        case UnityWebRequest.Result.ConnectionError:
        case UnityWebRequest.Result.ProtocolError:
        case UnityWebRequest.Result.DataProcessingError:
        default:
            // if we get anything else other than success
            // back, log it to the console
            Debug.Log(request.error);
            break;
    }
}

Unity also has some helper functions for grabbing Textures and Audio Clips from web resources as well, those follow the same pattern as the text request, but use the UnityWebRequestTexture and UnityWebRequestMultimedia helper classes to do so. You have to cast the Download Handler to the corresponding helper class to make it easy though, like in this example:

Texture texture = (textureRequest.downloadHandler as DownloadHandlerTexture).texture;

Interfaces

An interface is a coded structure that defines the required functions to be implemented in a class, sort of like a blueprint. You cannot define actual functionality inside an interface, it only contains definitions. It's similar in a lot of ways to an abstract class, but with a very important difference: a class can only inherit from a single class, but can inherit from as many interfaces as it wants.

It's important to note that you MUST implement every function inside an interface. None of them are optional. Implementing an interface can be considered a contract or a promise to provide a specific functionality in your class, similar to how a blueprint is a promise or contract made by a builder to build something in a specific way.

Let's look at a quick example of an interface:

public interface ITest
{
    public Data GetTestData();
    public string GetTestName();
}

As you can see, you define an interface with the interface keyword instead of the class keyword. You'll also notice that function definitions are ended with a semicolon, and there is no function body at all. That's because as mentioned, interfaces contain the blueprint but not the actual functionality. To add functionality, this interface needs to be inherited by a class, here's a quick example of the above interface being implemented:

public class Test : ITest
{
    public Data GetTestData()
    {
        return new Data();
    }

    public string GetTestName()
    {
        return "TestName";
    }
}

So just like with inheriting from a normal class, you add the interface name after the class name, but you'll notice that we don't use the override keyword on any of the functions. That's because we aren't modifying the functionality of a parent, but we are just implementing the functions as seen in the interface.

One really nice thing with interfaces is that you can store the object from the implementing class into a container defined by the interface. From the example earlier, that means you could store a Test object inside an ITest container. The same goes for any other class that inherited from ITest.

Namespaces

A namespace is an organizational tool in programming. Using a namespace provides you with a space that you can name your classes and functions anything you want without having to worry about clashing the name with some other library. Think of it like folders on your computer. You can have a file named anything you want inside a folder, but if you move that file to another folder it can conflict with another file with the same name.

For example, say you were making your own web request library and for some reason you really wanted to call it UnityWebRequest. You can (but still probably shouldn't)! All you'd need to do is define your new class inside a namespace of your own, and anywhere you want to use your version instead you'd use a using statement pointing to your namespace instead of Unity's.

namespace BetterThanUnity.Web
{
    public class UnityWebRequest
    {
        // regular class stuff goes here
    }
}

From the example you can see we enclosed the class with namespace BetterThanUnity.Web, which is the way you define a namespace. You can call your namespace anything you want, and "sub-folders" are designated by the . between words in the name. If you were working in a class without a namespace or in a different namespace, you can access your code inside the namespace by calling out the specific namespace and class using regular dot notation just like you would anything else:

var webRequest = new BetterThanUnity.Web.UnityWebRequest();

Properties

A property in a class is a lot like a regular public variable, but it has special functions for getting the value and setting the value. Properties are defined by simply adding the get and set accessors on to a variable like this:

public bool isEnabled { get; set; }

In this simple example you'll see that we put the words get and set after the variable, but that's all we did. This by itself doesn't give us any additional functionality, but we can expand the get and set clauses to add additional processing, or formatting, or even just calling functions. It can do pretty much anything that any other function can do. Here is the expanded version of the first example, showing the code for actually setting the variable:

private bool _isEnabled;
public bool isEnabled
{
    get
    {
        return _isEnabled;
    }
    set
    {
        _isEnabled = value;
    }
}

The above code is basically what the compiler is doing automatically for you when you just include the get/set clauses without any definition. As you can see though, they have a full function body that you can do additional actions in and the new value is stored in a special keyword, value.

Let's look at a more complicated example, like formatting a phone number:

private string _phoneNumber;
public string phoneNumber
{
    get
    {
        return _phoneNumber;
    }
    set
    {
        if(!EnsurePhoneNumberValid(value))
        {
            // unless you have a good reason, don't
            // throw exceptions from a property like this
            throw new InvalidOperationException("Input not valid");
        }
        _phoneNumber = $"({value.SubString(0,3)}) {value.SubString(3,3)}-{value.SubString(6)}";
    }
}

private bool EnsurePhoneNumberValid(string phoneNumber)
{
    //for the sake of brevity we'll only check that it's 10 characters long
    return phoneNumber.Length == 10;
}

As you can see from that, we can call functions to validate the data or even to mutate the data if we wanted to. If you're going to do something weird or conditionally ignore the value being set... do me a favor and at least use XML comments so other programmers can see in the Intellisense what you are doing and why something doesn't always work.

To bring it all back home though, imagine using this in the context of a game. There are many times when you may want to clamp a value or maybe even trigger additional functions when you change a value. Think about things like health or armor, you'll want to clamp them to not go above the max health or below the minimum health, and you may want to conditionally call another function when they hit max or minimum health. Using properties, you can quickly include that functionality and not need to make a series of functions that do it for you!

Encapsulation

The first of the 4 basic principals to Object Oriented Programming (OOP), Encapsulation is the process of hiding direct access to data in a way that prevents the exposure of implementation details.

Abstraction

The second of the 4 basic principals of OOP, Abstraction is the process of hiding complexity and other unnecessary details from the user.

Inheritance

The third of the 4 basic principals of OOP, Inheritance is the ability for classes to gain behaviors, data, and other structures from another class without the need for duplication.

Polymorphism

The last of the basic principals, Polymorphism is the ability of an object to pass as another object. This is most often seen when casting data from one type to another, but is also an inherit property of inherited classes, where any object can be stored in a container of a specific type so long as it's type inherited from that original type.

undo Back to Articles Index