“How to Harness the Power of Scriptable Objects in Unity”

santosh parihar
7 min readSep 14, 2023

--

Let’s break down what a ScriptableObject is in easy words:

Imagine you’re making a video game, and you want to create different types of items like weapons, potions, or armor. Each item has properties like a name, damage value, icon, or description. Instead of creating a separate script or class for each item, you can use ScriptableObjects to store the common data.

A ScriptableObject is like a blueprint for an object or piece of data in your game. It’s a way to store and manage data without the need for an instance of the object in your scene. You create a ScriptableObject for each type of item you want, like a “Weapon” ScriptableObject, a “Potion” ScriptableObject, etc.

Scriptable Objects are assets that can be created and customized directly within the Unity Editor.
Unlike traditional MonoBehaviour scripts, Scriptable Objects don’t attach to GameObjects and don’t have a presence in the scene.

They are stored as assets in your project folder and are typically used to store data or functionality that is not tied to a specific game object.

NOTE : Scriptable Objects can be used for both read-only and read-write data. However, they are most commonly used for read-only data because they offer a number of advantages over other data storage methods, such as:

They are serializable, they are lightweight, which means that they do not take up a lot of memory. This is important for games that have a lot of data to store. They are easy to manage, as they can be created and edited in the Unity Editor.

If you need to store read-only data in your game, then Scriptable Objects are a good option. However, if you need to store read-write data, then you may want to consider using another data storage method, such as a database.

Here’s why ScriptableObjects are handy:

1.Data Management: You can define all the properties and data for your items in one place.

2.Reusability: You can reuse the same ScriptableObject for multiple items of the same type. For example, you can create several “Sword” objects using the same “Weapon” ScriptableObject blueprint.

3.Easy Editing: You can edit and tweak the properties of your items in the Unity Editor without needing to change the code.

4. Organization: It helps you keep your game data organized and separate from code, making it easier to manage.

Here’s a basic explanation of how ScriptableObjects (SOs) work in Unity:

1.Creation: You create a ScriptableObject by defining a new class in your Unity project that derives from the ScriptableObject class.

2.Data Definition: Inside your ScriptableObject class, you define fields or properties to store the data you want to encapsulate. For example, if you’re creating a ScriptableObject to represent a weapon, you might have fields for the weapon’s name, damage, and icon.

3.Editor Configuration: You can now create instances of your ScriptableObject directly in the Unity Editor. Unity provides a nice interface for configuring the data within these instances. You don’t need to write code for this part; it’s all done in the Unity Editor.

4.Reference in Scripts: In your game code (C# scripts), you can create variables of your ScriptableObject type and assign specific instances of it that you’ve created in the Editor. This allows you to access and use the data stored in the ScriptableObject.

using UnityEngine;

// Create a new ScriptableObject class

[CreateAssetMenu(fileName = "NewWeapon", menuName = "Game/Weapon")]
public class WeaponScriptableObject : ScriptableObject
{
public string weaponName;
public int damage;
public Sprite icon;
}

// In your game script
public class PlayerController : MonoBehaviour
{

public WeaponScriptableObject currentWeapon; // Reference to a WeaponScriptableObject

private void Start()
{
// Access data from the ScriptableObject
Debug.Log("Damage: " + currentWeapon.damage);
// You can also use currentWeapon.icon for visuals, etc.
}

}

let’s take the same example and add events to make our code more loosely coupled. We’ll use events to notify other parts of the code when the values in our ScriptableObject change.

you can use the OnValidate() method to trigger events when values change in a ScriptableObject without creating separate methods. Here’s how you can modify the WeaponScriptableObject class to achieve this:

using UnityEngine;
using UnityEngine.Events;

[CreateAssetMenu(fileName = "NewWeapon", menuName = "Game/Weapon")]
public class WeaponScriptableObject : ScriptableObject
{

public string weaponName;
public int damage;
public Sprite icon;

[SerializeField, Range(0, 100)]
private int maxHealth;

// Define a UnityEvent with no arguments
public UnityEvent valueChanged;

#if UNITY_EDITOR
private void OnValidate()
{
// This method is called in the Unity Editor whenever a value is changed.
// Invoke the UnityEvent when values change.
if (UnityEditor.EditorApplication.isPlaying)
{
valueChanged.Invoke(); // Fire the UnityEvent
}
}
#endif

}

In this script, we define a UnityEvent called valueChanged. This event doesn’t take any arguments.

Then, in the OnValidate() method, we invoke the valueChanged. This means that any listeners attached to valueChanged event will be notified whenever the OnValidate() method is called (which happens whenever a value is changed in the Unity Editor).

Keep in mind that using this approach, you would need to add listeners to valueChanged event in other scripts, This way, you can react to the event being fired in the WeaponScriptableObject.

We’ll create a second class called UIManager that listens to the valueChanged event and updates a UI element (e.g., a text field) with the current damage value:

using UnityEngine;
using UnityEngine.UI;

public class UIManager : MonoBehaviour
{
public Text damageText;
public WeaponScriptableObject weapon;

private void OnEnable()
{
// Listen to the valueChanged event
weapon.valueChanged.AddListener(UpdateDamageUI);
}

private void OnDisable()
{
// Unsubscribe from the event when the object is disabled or destroyed
weapon.valueChanged.RemoveListener(UpdateDamageUI);
}

private void UpdateDamageUI()
{
// Update the UI with the current damage value
damageText.text = "Damage: " + weapon.damage;
}

}

In this example, we have a UIManager class with a Text field called damageText (which represents the UI element displaying the damage). We also have a reference to the WeaponScriptableObject called weapon.

In OnEnable(), we subscribe to the updatedEvent using AddListener(). This means that when the updatedEvent is invoked, UpdateDamageUI() will be called.

In OnDisable(), we unsubscribe from the event to prevent any potential memory leaks.

The UpdateDamageUI() function simply updates the UI text to display the current damage value from the WeaponScriptableObject.

Now, whenever the updatedEvent is invoked in the WeaponScriptableObject, the UpdateDamageUI() function will be called, and the UI will be updated with the current damage value.

REMEMBER : In Unity editor , when you change the values of a ScriptableObject during play mode and then stop the play mode, those changes will persist. Unity does not automatically revert the changes made during play mode. The modified values will remain as they were set during the play session until you manually reset them.

So, if you change the values of a ScriptableObject during play mode and then stop the play mode, those changes will indeed persist, and the ScriptableObject will retain the modified values when you enter play mode again or even if you exit the Unity Editor.

NOTE :

  1. In an Android build (or any standalone build for that matter), the OnValidate() method won’t be called because it’s an editor-only callback. Therefore, you cannot rely on it to trigger events in a standalone build.
  2. To trigger events and handle changes to your ScriptableObjects in a build, you should consider an alternative approach.

3. Scriptable Objects can be used for both read-only and read-write data. However, they are most commonly used for read-only data

Use Cases:

1. “Suppose in your game, you have stored the player’s XP in a ScriptableObject. In level 1, it is 100, and after completing the first level, it becomes 200. Should you update the ScriptableObject or not?”

No, you should not update the ScriptableObject in this case. The ScriptableObject is intended to store read-only data, such as the XP required to level up, the number of lives, and the time limit. If you need to store data that can change at runtime, such as the player’s XP, then you should use a different data storage method, such as a database or a custom class.

In this case, you could create a custom class to store the player’s XP. This class could have a property called XP that can be accessed and modified at runtime. You could then create a ScriptableObject that stores a reference to this class. This way, the ScriptableObject would always store the latest value of the player’s XP, even if it changes at runtime.

Here is an example of how you could do this:

public class PlayerXP {
public int XP = 100;
}

public class PlayerData : ScriptableObject {
public PlayerXP PlayerXP;
}

In this example, the PlayerXP class is a custom class that stores the player’s XP. The PlayerData class is a ScriptableObject that stores a reference to a PlayerXP object.

To update the player’s XP at runtime, you could use the following code:

PlayerData playerData = GetComponent<PlayerData>();
playerData.PlayerXP.XP = 200;

This code would update the player’s XP to 200.

2. This is how you should use this SO:

[CreateAssetMenu(fileName = "Scriptable Object", menuName = " Character Data")]
public class CharacterDataSO : ScriptableObject
{

public Sprite characterSprite; // CharacterSprite for the character selection scene
public Sprite characterVechileSprite; // The character vechile sprite for the character selection scene
public Sprite iconSprite; // Sprite use on the player UI on gameplay scene
public Sprite iconDeathSprite; // Sprite use on the player UI on gameplay scene for his death
public string characterName; // Character name
public GameObject vechilePrefab; // Prefab of the vechile to use on gameplay scene
public Color color; // The color that identifies this character, use for coloring sprites (laser)
public Color darkColor; // Dark color user for the gameplay UI to set the player name color
[Header("Client Info")]
public ulong clientId; // The clientId who selected this character
public int playerId; // With player is [1,2,3,4] -> more in case more player can play
public bool isSelected; // Use for locking this character on the character selection scene
[Header("Score")]
public int enemiesDestroyed; // The enemies defeat by the player for the final score
public int powerUpsUsed; // The power ups used by the player for the final score

void OnEnable()
{
ResetData();
}

public void ResetData()
{
isSelected = false;
clientId = 0;
playerId = -1;
enemiesDestroyed = 0;
powerUpsUsed = 0;
}

Support My Work ☕️

If you’ve found value in this article and would like to support my writing efforts, consider buying me a coffee. Your contribution helps me continue creating content like this.

Buy me a Coffee

Thank you for your support!

--

--