Inventory System using Scriptable Objects
Recently, I happened across two absolutely brilliant talks (which I highly suggest checking out) about Scriptable Objects. One is by Richard Fine, colorfully called “Overthrowing the MonoBehaviour Tyranny in a Glorious Scriptable Object Revolution” and the other by Ryan Hipple, from his Unite Austin 2017 talk called “Game Architecture with Scriptable Objects”.
Both these talks elaborate on the role Scriptable Objects could play in our game’s architecture. Ryan especially talks about creating “A Generic Inventory System using Scriptable Objects”. Well, I had some time and figured I’d put those concepts into use with a demo about it. What’s described below is an Inventory System that’s built using Scriptable Objects and its merits.
Before we get into the nitty-gritty, there are a couple of things to note. One, since this is a demo, the inventory system built here is rather primitive. At most, it has a simple Inventory Manager and a UI component that displays the items on screen. Demonstrating the use of Scriptable Objects was the main aim here. Two, this approach can be used to build other systems as well. A Skill Tree System perhaps? Third, I compare the use of Singletons and Scriptable Objects to build the system.
The two core ideas explored in the demo are:
- Make the system modular
- Eliminate hard references in code as much as possible
The source code for the demo is available on Github.
First, here’s the final product.
Second, let's briefly define what we are working with.
A scriptable object is:
- A class that is inheritable (much like MonoBehaviour)
- They need not/cannot be added to game objects in a scene as components and exist pretty much on their own.
Indeed, this means they are not baked into the scene directly. We only reference them on demand from other scripts
- Used to store common data and behavior
If this makes no sense, I would suggest going through the official docs and the prior mentioned talks, to understand the basics of Scriptable Objects.
The structure is straightforward, as shown below.
There are two gameobjects. “Inventory Screen” is the UI component responsible for displaying the inventory item and their amount on screen. InventoryManagerSingleton houses the InventoryManager script which uses the singleton pattern.
In this approach, we essentially specify what items are present in the inventory via code in the InventoryManager script.
There then exists a hard reference of this class in the UI component where we access the inventory.
Let's look at the limitations of this approach:
- Let’s say we have a different scene, for example, a tutorial scene, where only a subset of the total items is needed in the inventory. To achieve this, we need to go into the InventoryManager script and change the items present in the inventory, like so…
It’s always a good idea to build our games in a way that minimizes changes to code when accommodating various scenarios.
2. The UI component is no longer isolated. It has a dependency on InventoryManagerSingleton game object. This makes it harder to just pull out the UI component and test it.
The above problems might not seem to be a big deal in this small scale demo. But when we deal with games involving multiple scenes with multiple scenarios, the lack of scalability that arises from this approach can be felt painfully.
Using Scriptable Objects
This uses pretty much the same structure as the Singleton one.
Again, there are two gameobjects. “Inventory Screen SO” is the UI component responsible for displaying the inventory item and their amount on screen. InventoryManagerSO houses the InventoryManagerSO script which uses a scriptable object to represent the inventory.
We create separate scriptable object assets for the entire inventory and for each inventory item, as shown below.
There is a single scriptable object which represents the entire inventory. And each item in the inventory is itself a scriptable object. In essence, the entire inventory is just a list of individual scriptable objects.
For example, the main game inventory comprises 20 Coin Bags, 2 Health Potions, 5 Mana Potions, 10 Stamina Potions, 1 Bow, and 2 Swords.
Both, InventoryScreenSO and InventoryManagerSO scripts just refer to the same Inventory Scriptable Object asset to perform their respective jobs.
This eliminates both the problems we faced with the singleton approach:
- In a different scene, we can simply create a new Inventory Scriptable Object. Assign the relevant inventory items. Swap out the main inventory scriptable object with this new scriptable object asset in InventoryScreenSO and InventoryManagerSO
We have essentially handled a new scenario without any code changes. And lack of code changes reduces the chances of bugs creeping in.
2. Since there is no hard code reference of InventoryManagerSO in InventoryScreenSO, we are able to isolate the UI component and test it.
One last thing before we wrap this up. Richard Fine talks about using Scriptable Objects in place of enums. That is a really good idea. And one which is used here as well.
A certain dictionary in the code uses the individual item scriptable objects as the key to map to the assets (which would otherwise use enum values)
Scriptable Objects, themselves have a really simple concept. They just act as data containers. But we can use them to create modular and scalable game architectures. This is what makes them so powerful and satisfying to use.