One way you can implement this is with a collection of “Resource” and “Interpreter” pairs. Essentially, a “Resource” will be the in game representation of your loaded JSON and provides functionality for using it; it’s corresponding “Interpreter” is responsible for processing a JSON schema that you define into that “Resource”.
Consider the following simplified example:
SpriteSheet JSON Schema
This is a JSON schema you define to represent the expected input.
{
"Id": <string>,
"Schema": "SpriteSheet",
"Properties": {
"Hframes": <int>,
"Vframes": <int>,
"Texture": <string>,
}
}
and a filled out example (player.json
):
{
"Id": "Player",
"Schema": "SpriteSheet",
"Properties": {
"Hframes": 8,
"Vframes": 2,
"Texture": "res://path_to_texture.png",
}
}
SpriteSheet Resource
This resource could represent everything you need for a Sprite2D.
class_name SpriteSheetResource extends Resource
@export var hframes: int = 1
@export var vframes: int = 1
@export var texture: Texture2D = null
func _init(data: Dictionary) -> void:
for field: String in data:
match field:
"Hframes":
hframes = data[field]
"Vframes":
vframes = data[field]
"Texture":
texture = load(data[field])
# Returns true if the resource is valid. You can update this with any logic you need to validate the resource.
func validate() -> bool:
return hframes > 0 and vframes > 0 and texture != null
# Loads this resource into the specified Sprite2D.
func assign_to_sprite(sprite: Sprite2D) -> void:
sprite.hframes = hframes
sprite.vframes = vframes
sprite.texture = texture
SpriteSheet Interpreter
This interpreter is responsible for taking in a dictionary matching your JSON schema and loading the resource into storage to be referenced throughout your project. This could be a global as you likely only need one in your project.
class_name SpriteSheetInterpreter extends Node
const BASE = {"Id": "", "Schema": "SpriteSheet", "Properties": {}}
var resources: Dictionary[StringName, SpriteSheetResource]
func register(id: String, data: Dictionary) -> Error:
data.merge(BASE)
var properties: Dictionary = data["Properties"]
var res: SpriteSheetResource = SpriteSheetResource.new(properties)
if not res.validate():
printerr("Resource '%s' is not valid." % id)
return FAILED
resources[id] = res
return OK
func find(id: String) -> SpriteSheetResource:
if not id in resources:
printerr("Resource '%s' does not exist." % id)
return null
return resources[id]
Now that you have these set up you can load the player.json
at the start of your game and reference it in your player node.
Main Scene Script
func _ready() -> void:
# Load the JSON file:
var json_data: Dictionary = <load json code here>
# Register it with the Interpreter:
sprite_sheet_interpreter.register("Player", json_data)
Player Script
func _ready() -> void:
# Grab the resource from the Interpreter.
var res: SpriteSheetResource = sprite_sheet_interpreter.find("Player")
# Assign the resource to the Sprite2D node of your player.
res.assign_to_sprite($Sprite2D)
One of the benefits for this is that you can actually build your game “as a mod”, so to speak. All you would need to do to allow someone to mod your game is to give them the ability to “override” the “Player” data with their own JSON.
You can expand this concept to practically any type of resource you want to define. I’m using this in one of my projects to make AnimationLibrary resources from user defined JSON that allows a user to define a full custom AnimationTree and AnimationNodeStateMachinePlayback.