Unity With Irish T. – Part 1: Scriptable Objects

While I have many years of experience programming, working with Unity is still a relatively new experience for me.

As such I’m constantly learning and finding new ways to do things.

One new thing I’ve started working with is the ScriptableObject class.

I put these off for awhile, not really understanding what they were and how they worked, making the silly assumption that they were something too complex to bother with right now.

However once I eventually gave them a chance, I discovered that they were everything I didn’t know I needed in my life.

ScriptableObjects solved many problems I had when it came to designing clean and modular data driven systems in Unity.

To give an example of this I’m going to cover two ways to create a database, one using regular classes, and one using the ScriptableObject class.

Alternatively you can use GameObjects and MonoBehaviours to store your data, but that comes with a whole host of issues I’d rather not get into right now.

So we’ll start with a database making use of regular classes, which anyone that learned to program outside of Unity(such as myself) should be more familiar with:

using UnityEngine;

[System.Serializable]
public class StandardWeapon{

    public string name;
    [Multiline(5)]
    public string description;
    public Sprite icon;
    public int attackPower;
    
}

This is the class for weapons in our database.

It simply stores data(that’s what databases do) on the name, description, icon and attack power of a single weapon.

The [System.Serializable] is necessary to allow Unity to serialize this class, allowing it to be editable in the Inspector.

Now that we’ve defined the data, we need a place to store it:

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

public class StandardDatabase : MonoBehaviour{
    
    public List<StandardWeapon> weapons;
    
}

This small MonoBehaviour consisting of nothing but a List will take care of that for us.

Now we can freely add and edit weapons in our database making use of Unity’s inspector:

Some programmers out there might argue for adding weapons via code with something like:

Weapon sword = new Weapon();
//Set the weapons data…
weapons.Add(sword);

Which is fine if they’re the only one that will ever be editing the data, in which case go for it if that’s your preference.

However in many cases someone else will be editing the data, and they may not necessarily be a programmer.

And so, it would be better done through a UI such as Unity’s inspector.

One or two items like this is pretty manageable, however as the size of the database increases, using the inspector like this quickly becomes a messy and tedious process.

You can make this a bit cleaner by writing your own inspector, but speaking from my own personal experience doing so the cleaner you try to make it, the more work you have to put in.

As you add new types of data to the database such as armour and other items, this work load grows and some compromises have to be made to save time.

With this approach, you might also notice that changing the order of weapons in the database, or removing a weapon is not so simple.

There are ways to solves this however I won’t cover them, as this post is about using ScriptableObjects to avoid such problems in the first place.

Now that we have a database making use of standard classes, let’s move on to a ScriptableObject implementation:

using UnityEngine;

[CreateAssetMenu(menuName = "Weapon")]
public class ScriptableWeapon : ScriptableObject{
    
    public string name;
    [Multiline(5)]
    public string description;
    public Sprite icon;
    public int attackPower;
    
}

A few differences to notice here, first, we’re inheriting from ScriptableObject to create our weapon class this time, which makes the class serializable, meaning we don’t need the [System.Serializable] attribute anymore.

We do however have a new attribute [CreateAssetMenu(menuName = “Weapon”)]

This allows us to add our weapon to the “Create” context menu inside Unity.

We can now use this to create “Weapon Assets”. Individual files which will store the information about our weapons:

These assets each have their own inspector, making the process of editing them much more manageable.

We still need a way to get this data into the game however, so:

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

public class ScriptableDatabase : MonoBehaviour{
    
    public List<ScriptableWeapon> weapons;

}

A practically identical structure to our last database, once again, just a List containing our weapons.

This time however, we’re presented with Object fields instead of a place to enter our data as before, which is now handled by the asset files:

We can simply drag our weapon assets in to the fields to add them to the database.

This is much easier to work with and data is much easier to rearrange or remove, though with larger databases, this could still prove a little tedious, it’s a huge improvement.

There are many other advantages to ScriptableObjects:

  • They’re easier to write custom inspectors for; There are many features missing when writing custom inspectors for regular classes that can make it troublesome.
  • The asset files can be moved to other Unity projects, in case you wanted to use the same data in other games.
  • By writing a simple parser it’s possible to convert the data into other formats such as json for easy use outside of Unity.
  • Having data as individual files will help reduce the likelihood of source control conflicts when working in a team and are better at storing data for custom tools/editors.
  • Among many others I’m sure.

A word of caution though, there is a quirk to these ScriptableObjects that’s important to address.

When editing their values at runtime, it WILL edit the value of the asset itself.

Meaning that any changes made will persist after you’ve stopped the game.

If this isn’t the behaviour you want, you can instantiate the data before it’s used.

You will then be working with a copy of your data rather than the original.

void Awake(){
    for(int i = 0; i < weapons.Count; i++){
        if(weapons[i] != null){
            weapons[i] = Instantiate(weapons[i]);
        }
    }
}

Naturally there’s more to cover, but that will have to do for now.

Till next time.

– Irish T.

Published
Categories Uncategorized
Views 390

Comments

No Comments

Leave a Reply