Making a RTS game #5: Transforming our data into Scriptable Objects (Unity/C#)

Let’s continue our RTS game project by exploring Unity’s Scriptable Objects!

This article is also available on Medium.

Today, we’re going to see how to store data into Scriptable Objects, rather than defining them directly in our scripts.

At the moment, our data is defined in our global variables – and those global variables are of two types:

  • some are “external” data that we want the game to be aware of and use during the loading phase when it starts
  • others are utilities shared across of all scripts and internal to the game logic

The second type of variables should stay as is – there is no point in loading the reference to the “Terrain” layer from an external data source, for example. On the other hand, our building types could be loaded from another source: they are not determined by the game state. The list of in-game resources is somewhere in-between: though the notion of “gold”, “wood” and “stone” as resources could be loaded up, remember that our global variable actually holds the current amount of each, so it is linked to the game current state.

To keep things simple, and because this list of resources should not evolve a lot, we are going to focus on using Scriptable Objects to represent our building types (our building data).

Why use Scriptable Objects?

Throughout the previous tutorials, we gradually added more and more data in our Globals class. In particular, think of our list of building types: for now, there are only two; but when we start to have a few more, and perhaps other unit types, and relations between the two, this file will get too big to remain easily maintainable.

Scriptable Objects were introduced in Unity. As stated in the docs, they are very useful for:

  • saving and storing data during an editor session
  • saving data as an asset in our project to use at runtime

Scriptable Objects are saved as assets in the project’s folder. You basically a class of pure data, like our current BuildingData, but have it inherit Unity’s ScriptableObject class. It is then possible to create an asset in the project for it.

They avoid useless memory copies for shared data because each created asset can then be referenced in your scripts without re-instantiating it each time. Also, since they show up in the inspector with all their (serializable) fields, they are super easy to edit and tweak – even at runtime, they’ll save all changes when you exit the preview!

Note: for more details on the power and utility of Scriptable Objects, you can check out this talk at Unite Austin 2017 by R. Hipple on how his game company started to use Scriptable Objects as a core feature of their game architecture and design.

Transforming our BuildingData class into a Scriptable Object

Changing the class itself

So far, we’ve coded up the following BuildingData class:

To transform it into a Scriptable Object, we have to:

  1. have it inherit from the ScriptableObject class
  2. replace all “private” accessors to “public” so they show up in Unity’s inspector
  3. make sure all fields are serializable… so they show up in Unity’s inspector! 😉

The first two points aren’t hard to take care of; let’s talk about the third point instead.

Unity’s script serialization is a vast topic. Basically, serializable data is a type of data that can be saved and reloaded later by Unity. In our case, it’s mainly related to the fact that only serializable class fields are shown in the inspector.

For example, we currently have a Dictionary for the building’s cost in game resources. This won’t appear in the inspector if we make an asset from our Scriptable Object. Instead of this basic Dictionary, we’re going to use a super basic class:

Note that I’ve marked the class as “serializable” and I’ve made all of its fields public. This way it will show up properly in the inspector. And we can now finally use it in our new BuildingData class to create our Scriptable Object:

You see that I also added a unitName field that will be used for pretty display and a direct reference to the game object prefab.

The line above the class definition (using the CreateAssetMenu() tool) allows Unity to add a menu associated with this Scriptable Object so it’s quicker to create assets from the class.

Updating the references to the class in other scripts

In the Building class, we have to update some fields – in particular, we don’t need to load the prefab from the “Resources” folder anymore since it is stored in the BuildingData asset:

Also, in the UIManager class, the Awake() function goes through the available building types and create buttons for each in the building menu; we need to change some fields with our new class:

Creating assets from our class

At that point, our Scriptable Object is ready and can be used to create one or more assets. It’s quite easy: we just need to go to the Create > Assets > Scriptable Objects menu and click on the “Building” menu:

This creates a new asset in the project – let’s name it “House” and setup its parameters:

In order to access it easily in our scripts, we will move this asset to a subfolder of the “Resources” folder. I created a hierarchy with three subdirectories: “Scriptable Objects”, “Units” and “Buildings”. So in the end I got:

Loading up the assets at runtime

We now have assets in our project’s folder – but we haven’t loaded them in our game yet! We want to load our building assets dynamically when the game starts to get our list of available building types.

Adding a DataHandler class

First things first: we will create a new DataHandler static class to manage all data loading and saving. At that point, we only need to load the building types. This will only take one line, so let’s directly put in a LoadGameData() function – the central hub for game data initialization.

The buildings data loading simply relies on Unity’s built-in Resources.LoadAll() method: thanks to this function, you can automatically list and read all the assets at a given subpath of the “Resources” folder in your project’s assets. Here, we read our building assets from the folder we just created.

Since we are now assigning this value, we can edit our Globals.cs file to only keep the BUILDING_DATA variable declaration and remove the variable initialization:

Calling the data load function

The last step is to actually call this data loading pipeline. It’s time to define another global manager script to add on our “GAME” object: the GameManager class. Ultimately, this script will take care of the overall game state management at a very global scale. But for now, in this class, we can just call the LoadGameData() method in the Awake() function:

And that’s it! If you start the game again, you should only get as many building types in your building menu as you have building assets in your data folder. Of course, you need each asset to have a corresponding prefab in the Resources folder, otherwise there will be errors when you try to create the “phantom” building.

Go ahead and play with the values in the Scriptable Objects: these will immediately influence the game the next time it is used (even at runtime) 🙂

Note: if you happen to have some null reference when iterating through the BUILDING_DATA, it might be because the scripts GameManager and UIManager aren’t executing in the proper order, so the BUILDING_DATA hasn’t been filled when it is accessed. To insure the scripts run in the correct order, you can set in the Project Settings > Script Execution Order panel:

Taking advantage of this format to add a description to our buildings

Just to make sure we get how this works, let’s add a simple string field to our BuildingData class: the description of the building. To extend our building info, we only need to do two things:

  1. add the field (and a getter) in the BuildingData class
  2. edit it in our building assets

So we just need to add a public string description; to the BuildingData class, and we’re done! The new fields will automatically show in the inspector and we can edit it in our asset:

Isn’t it an easy workflow? Even someone who’s not a coder could edit this building data and update the game values – Scriptable Objects are an essential feature of modern collaborative game design that requires programmers to discuss and work together with non-coder designers 🙂

Alternate solution: loading data from files

Another idea would be to extract pure data from the code and load it from files on the system. We would keep a class like the one we had before this tutorial and use data loaders to parse a file containing the data for each building. This way, changing the cost of a building or the requirements for producing a unit could be as quick and easy as changing a line in a file somewhere. And the game would automatically reload this new data next time it is launched – without having to recompile it from scratch!

An “extreme” version of this data-driven code philosophy is the Bytecode programming pattern where you don’t really code anything but an interpretor for your data files… and then all the meat of your game comes from the content of your files. Bob Nystrom gives a brilliant explanation of it in his “Game programming patterns” book (among lots of other gems) – I really encourage you to check this book out, it’s a must-read for any game developer!

Unity provides either raw parsing using the basic C# XML library or an XML.Serializer that allows you to directly map an XML file to a C# serializable class.

The idea is to tell the XML parser where each class field is located in the XML file (is it a node inner text? an attribute?) and then to let it recursively dive into your structure to associate the values in the new instance of the class. In my experience, it is very powerful for “flat” classes, i.e. data types where you have lots of fields of a basic type (string, integer, float, bool…) and it handles arrays or lists pretty well. On the other hand, it’s not as easy to use with associative structures like Dictionaries that rely on key-value pairs.

Conclusion

This tutorial is an important stepping stone for future improvements – it will be easier to extend and update those Scriptable Objects now that we’ve gotten the hang of it on a simple example.

Next time, we’ll improve our game UI by adding healthbars above selected units and an information panel to see our building types’ data when we hover the buttons in the building menu.

16 thoughts on “Making a RTS game #5: Transforming our data into Scriptable Objects (Unity/C#)”

  1. “You see that I also added a unitName field that will be used for pretty display and a direct reference to the game object prefab.”
    No, I do not see a direct reference to the game object prefab 😉

  2. hi again
    after changing the building class. I got a error in my Place function on the foreach loop, where it says it can’t convert ResourceValue to that type.
    also, I am unsure whether the _material list inside the Building constructor is supposed to be removed or just let it stay?
    I’ve uploaded it to GitHub: https://github.com/Sakref00unholy/RTS-Builder-Game
    many thanks in advance. 😀

    1. Hey,

      nice catch! 🙂

      I’d forgotten to add a piece of code to the `Building.cs` snippet, you actually have to update the iteration through the `cost` in the `Place()` function:

      ...
      foreach (ResourceValue resource in _data.cost) {
         Globals.GAME_RESOURCES[resource.code].AddAmount(-resource.amount);
      }
      

      I’ve updated the snippet – thanks!

      For the `_materials` list: yup it should stay – it’s how you can revert your building back to its “normal” colour upon placement, and switch between the “valid”/”invalid” materials. Fixed in the snippet too, thanks again! 🙂

      1. hello again. 🙂
        thanks that fixed the problem with the foreach loop and the materials.
        also no problem, I’m really enjoying these tutorials. 😀
        but I have one last issue in the code. inside the UIManager script on the awake function. it says there is a missing a nullreferenceexception in the for loop that makes the building buttons. although I can’t see what should be making problems.

        1. hi again 🙂

          Mmh… I haven’t checked out your Github but my guess is that you forgot to assign a variable in the Inspector somewhere: are you sure all the slots in your “UIManager” component are properly filled? 😉

          1. I’ve looked at it in the GAME object and they are all filled. :/
            so i think its probably the code itself.
            this is how the for loop looks for the buttons on my program:
            _buildingButtons = new Dictionary();
            for (int i = 0; i < Globals.BUILDING_DATA.Length; i++)
            {
            BuildingData data = Globals.BUILDING_DATA[i];
            GameObject button = Instantiate(buildingButtonPrefab);
            button.name = data.unitName;
            button.transform.Find("Text").GetComponent().text = data.unitName;
            Button b = button.GetComponent();
            _buildingButtons[data.code] = b;
            _AddBuildingButtonListener(b, i);
            button.transform.SetParent(buildingMenu);

            if (!Globals.BUILDING_DATA[i].CanBuy())
            {
            b.interactable = false;
            }
            }
            many thanks in advance. i really appreciate the help 🙂

          2. hey – got it! It’s not an issue with the code but with the project’s settings actually! 😉
            What’s happening is that the UIManager is executed before the GameManager, so the BUILDING_DATA hasn’t been filled yet and it crashes when you try to iterate through it!
            To solve this, you need to go to the Project Settings > Script Execution Order panel and set the GameManager to execute first.
            I’ve added a few lines in the tutorial to explain this, with a little screenshot – feel free to tell me if it’s not clear/not enough to solve it on your end 😉

  3. Hello again.
    that seems to fix that issue. but it didn’t fix the issue with the buttons not getting added in. :/
    and it seems to complain about the event manager as well now. it is saying it isn’t active on a game object. but I have checked that it is still a component of the GAME game object and it is. 😉
    so I’m confused. xD

    1. Hello again! 🙂

      I’ve just taken a quick look at your repo and the reason you don’t get any buttons is because, in your DataHandler.cs file, you’re loading resources from the “ScriptableObject/Units/Buildings” folder… but in truth it’s called “ScriptableObjects” (with an “s”)! So you don’t load anything => so you don’t show any button 😉

      As for your EventManager error, it is very subtle and tricky to see 😀
      In the code, at line 56, you are checking to see if _eventManager is null… but you forgot a little = sign! So instead of having a check, you actually had an assignment (which returns true, so the check passes). Meaning that: you’d get through the if, but after the if, you had a null value for the _eventManager variable, ie the instance variable, so then the instance._events crashes.
      Hope it’s clear! 🙂

      Also, given our past exchanges, if I may, my advice to you would be to keep on coding and asking questions (I’m always happy to answer!) but try and have a second look at your code after a little break; try and do something else, then come back to the code with fresh eyes. I feel like most of your mistakes aren’t about not understanding the programming but little typing/spelling errors 😉

      1. Hi again.
        yes, could be right with the spelling errors. but I will take your advice to heart and try giving myself some time to take a break from the code 🙂
        it seems to have fixed the issue with not being able to spawn buttons. so now it spawns buttons depending on the number of objects I have. the only problem now is when I try and press the button I get an error. but will look through It later and see if I can solve the issue 🙂
        if I get stuck again I will probably ask for help again.
        but a big thanks for the help 🙂

        1. Sounds great! Good luck with the issue – and yup, definitely don’t hesitate to keep on asking questions, I’m by no means saying you should stay stuck on your own; but just have another look at it to check everything matches 😉

          Cheers!

          1. Hello again.
            okay, I’ve been trying to figure out what’s the problem since it won’t spawn in the prefab when I click the buttons. even went so far as to redo the tutorial to see if I could better see the problem that way but just ended up with new problems on that instead. xD
            so is it possible you could take a look at my code to see what is wrong? 🙂
            here is my github: https://github.com/Sakref00unholy/RTS-Builder-Game
            again many thanks in advance. 🙂

          2. Hi! So this is just a little mistake and actually it’s my bad – I fixed it too quickly the other day when I changed my code to add more details in the Building() constructor: the _buildingManager initialization has to go after the _transform initialization, otherwise you get a null reference when you do _transform.GetComponent...!

            By the way, I don’t know how familiar you are with Unity debugging: if you look at the console in Unity, where you have all your debug/warning/error messages, you actually see what script and what line the message came from. So for example, here, when I clicked on the button and no prefab appeared, I saw in the console that there was an issue with the Building.cs script, at line 23. That’s how I knew the “null reference” came from there, and a quick look showed me that it was because of the _transform variable not yet initialized!

            But so – the console is your friend 😉

            Hope that’s clear, cheers!

  4. Hello again.
    okay, that makes sense. I did see the error message when I used the buttons. I just didn’t understand what the problem was in the code. this may be due to me being still inexperienced in coding and still not sure about some of the aspects behind programming and coding. 🙂
    do you maybe have some recommendations for reading material in programming so maybe I can learn better coding? right now I’m reading Head First on C#, but I would love to know more about programming. 🙂

    1. Hi! Hum, that’s kind of difficult to answer as is: it depends on what your background is, what prior knowledge you have in programming, how much time you’re willing to spend on it… If you want to discuss this more, don’t hesitate to send me an email: mina.pecheux@gmail.com 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *