Custom Resources
Ah, Custom Resource, ScriptableObject, whatever the name, it’s the modern game engine’s key to data driven development, a gateway to easy modification without hardcoding everything. This video was long coming, with it being one of the biggest question marks a lot of newer devs in my Discord were asking for.
Although I didn’t cover absolutely everything I wanted to about resources and there are things I think I could’ve shown better, I’m very happy that there are people who have been introduced to resources through that video.
Data Driven Development
Although resources can be used for quite a large amount of things (including lots of built in nodes and effects in the godot editor), my favorite usage is creating a setup where resources are used entirely as data for systems with little input outside of defining the resources.
A basic example, and what I used in my video, is a system that automatically registers item and crafting recipes entirely using resources.
This means if I want to add a new item, all I need to do is create a new resource of type Item
, and fill out whatever fields (name, texture, etc.) are needed. Then a recipe is just a list of crafting ingredients, which is just an item and how many of that item it needs, and an output, which is just what item and count we’re outputting.
Resource Loader
One of the most important autonomous parts of this system is the ability to load any recipes in without having to manually specify them anywhere.
That’s where a resource loader comes in:
class_name CraftingRegistry
var resources : Array[CraftingRecipe]
static func _static_init() -> void:
load_resources("res://Resources/Recipes")
static func load_resources(path: String) -> void:
if !path.ends_with("/"):
path += "/"
var dir = DirAccess.open(path)
if dir:
dir.list_dir_begin()
var file_name = dir.get_next()
while file_name != "":
recipes.append(load(path + file_name))
file_name = dir.get_next()
I’d recommend bookmarking this one, as it’s a super useful bit of code that you can utilize to load in an arbitrary amount of resources.
A couple key points I’d like to mention:
_static_init()
is called at the start of the game automatically as part of Godot registering theCraftingRegistry
class for use in the game.- Technically this doesn’t account for the possibility of files that aren’t recipe .tres files in the folder so either don’t do that, or adapt it to expect those.
- It also doesn’t call subdirectories recursively, but there’s a way to do that by calling
dir.current_is_dir()
afterdir.get_next()
to check if we’re currently looking at a directory, and passing that into a recursive call with the path.
Crafting Recipes & Items
And of course, it would be impossible to show off this system without mentioning the items and recipes. Thankfully, they are super simple and adaptable to whatever system you use.
class_name Item
extends Resource
@export var item_name : String
@export var sprite : Texture2D
Here we define the most basic item - a name and texture. We can also extend this later if we want an item with behavior, imagine a ToolItem
or PotionItem
class.
And time for a two parter:
class_name CraftingIngredient
extends Resource
@export var item : Item
@export var count : int
class_name CraftingRecipe
extends Resource
@export var ingredients : Array[CraftingIngredient]
@export var output : CraftingIngredient
This is my preferred way of handling item counts in a recipe, but defining a CraftingIngredient
class lets me define both the item and amount of that item in one field inside of a CraftingRecipe