Unity3D: ScriptableObjects overview

(Para a versão em português, clique aqui)

Introduction

In one of the posts that I've written here I've explained how to create singletons of ScriptableObjects, something very useful in some situations, but afterwards I was wondering if it would be interesting to write a post about some aspects of ScriptableObjects themselves. So, here we are. The information contained in this post was obtained from the video below, from Unity3D docs and my personal experience with the subject, and I suggest that you watch the video below (~1h long) when you have some time to spare:

If you already know what a ScriptableObject is and want to go straight to the examples, click here.

With that said, what is, exactly, an ScriptableObject (SO from now on)? This is the official explanation from the docs and a live training, but in simple terms we can imagine SOs as "classic" objects from the Object-Oriented Programming paradigm, instead of components, which we would normally use on Unity3D. Not being components, they don't need necessarily to be associated to a GameObject to be used.

For creating a SO, just create a subclass of ScriptableObject:

using UnityEngine;

public class MyScriptableObject : ScriptableObject {  
    public int someNumber;
}

For using an SO in the project, first we create an instance of it, where the public fields are initialized, and then you can just use it in any component you like. You can easily create a class hierarchy, as shown on the video: a class HealthPowerUp derivated from the PowerUp class, where some component could call PowerUp.apply() which in turn would call the derivated class method.

SO instances become project assets, in the same fashion as a texture or sound file also become assets when added to the project. They can be referenced through public variables of some component, turned into singletons or also be loaded at runtime from the Resources folder. For uploading to some repository, just store the .asset instance file and that's it.

There are other attributes on SOs like the OnEnable() function, which is called when the object is loaded, but I won't discuss them in detail; they are listed in the official docs here.

Use case: Tomato Hunter

I've spoken about Tomato Hunter in other post (in Portuguese, English version coming soon), but if you want to take a look, just click below:

At Tomato Hunter SOs were used for storing the configurable parameters of several game entities, in a way that that were easy to apply these parameters in the entities themselves and also accessible for the other team members to adjust anything needed without hitting the code. As an example, below is the SO with the player parameters:

using UnityEngine;  
using System.Collections;

// Adding an entry in the Assets menu
[CreateAssetMenu(menuName = "ScriptableObjects/PlayerConfig")]
public class PlayerConfig : ScriptableObject {  
    [Header("Movement")]
    public float runSpeed;
    public float stealthSpeed;
    public float groundDamping;

    [Header("Attributes")]
    public int healthPoints;
    public float invincibilityTime;
}

This class has the movement parameters and other basic attributes of the player, which directly affect gameplay. The menu entry for creating an instance is shown below:
Menu with the SOs

After being created through the Assets menu and properly configures, this is how it looks like in the Inspector:
PlayerConfig instance at the Inspector

The instance name can be anything you want, in this case it remained PlayerConfig for the sake of clarity. This asset can be configured freely in the editor, in any scene, with the game running or not. This means the game designer can easily adjust the parameters that s/he wants and afterwards you just have to save the only .asset file generated, and that's it.

Other SO that is used is the EnemyConfig, which holds enemies parameters. Below are two instances for the two game enemies. This time I had to give them different names:

In the end, for using these instances in the game, just create a public variable of the correct type in some component and drag the instance in the Inspector. As an example, we have the Player object, which has a variable of type PlayerConfig:

For accessing the parameters in the variable, just reference them directly, for example playerConfig.runSpeed or playerConfig.healthPoints.

This is just a small sample of what is possible to do with ScriptableObjects; using that tired old cliché, the only limit is your imagination. In the next projects I intent to explore these objects further and use them in different ways, and I'll come back afterwards to tell about it.