In the previous article, we explored three of my favorite ScriptableObject patterns:
Data Assets
Runtime Sets
Event Channels
If those patterns help organize your project, the next four we're covering today help organize your gameplay. They're all about making your game easier to balance, extend, and maintain without constantly diving into your code.
As with every design pattern, these aren't solutions looking for problems. They're practical techniques that shine in the right situations, and as always, can become overkill if used everywhere.
Let's jump in!
4. Variable Assets
This is one of those patterns that initially seems a little strange. After all, variables already exist. So why would one put it inside a ScriptableObject?
The answer is simple:
Sometimes multiple systems need access to the same value without knowing about each other.
Let's say your game has a difficulty multiplier.
Several systems need to read it.
Enemy spawning
Enemy health
Loot drops
Experience rewards
Boss damage
Instead of each system referencing a GameManager, they can all reference the same asset.
using UnityEngine;
[CreateAssetMenu(menuName = "Variables/Float Variable")]
public class FloatVariable : ScriptableObject
{
public float Value = 1f;
}
Now your enemy can simply read it.
[SerializeField] private FloatVariable difficultyMultiplier;
private void Start()
{
health *= difficultyMultiplier.Value;
}
Simple, clean and no manager required.
A Word of Caution
This pattern is often misunderstood.
Some developers begin creating ScriptableObjects for every single variable in their project. The point is that Variable Assets are intended for shared values, not values unique to individual objects. A player's current health usually belongs only to the Player. The game's global difficulty multiplier? That's a perfect candidate.
Great Uses
Variable Assets work well for:
Difficulty multipliers
Global volume
Mouse sensitivity
Time scale modifiers
Experience multipliers
Day/night speed
World gravity settings
Notice the pattern? They're all values shared across multiple systems.
5. Ability Definitions
If you're making an RPG, action game, or anything involving player abilities, ScriptableObjects can save you an incredible amount of duplicated work.
Imagine every spell stores:
Mana cost
Cooldown
Icon
Description
Animation
Particle effect
Without ScriptableObjects, every ability component quickly becomes bloated.
Instead, create an Ability asset.
using UnityEngine;
[CreateAssetMenu(menuName = "Abilities/Ability")]
public class AbilityData : ScriptableObject
{
public string abilityName;
public Sprite icon;
public float cooldown;
public int manaCost;
public GameObject effectPrefab;
}
Your player simply references the asset.
public class AbilityCaster : MonoBehaviour
{
[SerializeField] private AbilityData ability;
public void Cast()
{
Debug.Log($"Casting {ability.abilityName}");
}
} Now adding a new spell is easy, just create another asset. Done.
Why Designers Love This
Imagine balancing twenty abilities. Without ScriptableObjects you'd need to edit twenty different prefabs.
With ScriptableObjects you simply tweak the asset. Even better, designers can adjust cooldowns and mana costs without touching gameplay code.
Everyone wins!
Well...
Except perhaps the overpowered Fireball you've just nerfed...
Extending the Pattern
As your project grows, AbilityData can include:
Casting animations
Audio clips
Unlock requirements
Cooldown categories
Targeting rules
Status effects
Notice something?
The MonoBehaviour barely changes.
Only the data grows.
That's exactly what you want.
6. Item Databases
Here's a question. How do you look up item number 47?
Many beginners create huge switch statements.
switch(itemID)
{
...
}
Or dozens of if statements, both become difficult to maintain.
Instead, create an Item Database.
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(menuName = "Items/Item Database")]
public class ItemDatabase : ScriptableObject
{
public List<ItemData> Items = new();
}
Now every item lives inside one central asset.
Looking Up Items
Need an item?
ItemData sword = itemDatabase.Items[0];
Or perhaps search by ID.
public ItemData GetItem(int id)
{
return Items.Find(item => item.ID == id);
}
Suddenly your inventory system becomes much cleaner. Every system references one database.
No duplicated information.
Why This Pattern Scales
Imagine your game eventually contains:
300 weapons
200 consumables
150 crafting materials
80 quest items
Would you rather maintain hundreds of switch statements, or just one searchable asset? I know which one I'd pick. My present self has enough problems already.
Bonus Tip
If your database becomes large, consider storing items inside a Dictionary at runtime for faster lookups and keep the serialized List for the Inspector. Build the Dictionary automatically during initialization.
Best of both worlds.
7. Spawn Tables
Let's finish with one of my favorites! Spawn Tables. Suppose your game randomly spawns loot.
A beginner solution might look like this.
if(Random.value < 0.5f)
{
SpawnPotion();
}
else
{
SpawnSword();
}
Now add:
Shields
Armor
Rare weapons
Legendary items
Coins
Gems
Scrolls
Your spawning code quickly starts resembling an accountant's spreadsheet. Instead...
Move the probabilities into a ScriptableObject.
using UnityEngine;
[System.Serializable]
public class SpawnEntry
{
public GameObject prefab;
public float weight;
}
Then create a Spawn Table.
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(menuName = "Spawning/Spawn Table")]
public class SpawnTable : ScriptableObject
{
public List<SpawnEntry> Entries = new();
}
Now your spawning logic simply reads the table, the data determines what appears. The code only performs the selection.
Why This Is Better
Let's say you're balancing loot drops. Instead of modifying code, you simply change the weights inside the asset. Want legendary swords to become rarer? Adjust one value. Need a Halloween event? Create a different Spawn Table. No code changes required.
More Than Loot
Spawn Tables work beautifully for:
Enemy waves
NPC encounters
Ambient sounds
Weather events
Random dialogue
Treasure chests
Dungeon generation
Anywhere something needs to be selected randomly, a Spawn Table is worth considering.
Let's wrap up
Alright, the patterns we've covered today all share one important goal: Move game configuration out of your code. Variable Assets centralize shared values and Ability Definitions separate gameplay logic from ability configuration.
Item Databases provide a single source of truth for your game's items, and Spawn Tables make balancing randomness a matter of changing data instead of rewriting code.
As your project grows, you'll likely find yourself using these patterns more and more, not because they're trendy, but because they naturally reduce duplication and make your game easier to maintain.
And perhaps that's the biggest lesson ScriptableObjects have to teach. They're not just data containers, they're a way of thinking about your architecture.
When your data and your behavior each have a clear responsibility, your project becomes easier to understand, easier to balance, and much less likely to collapse into an unholy bowl of spaghetti code.
In the final part of this series, we'll look at some of the more advanced ScriptableObject patterns, including the Factory Pattern, AI Behaviour Assets, and Global Configuration Assets, as well as discussing when ScriptableObjects are not the right tool for the job.
In the meantime, ~happy coding!

No comments:
Post a Comment