I suffer from acute analysis paralysis. I am still in the “early” stages of this game dev project, which means that despite all of my best intentions I am working with data. Specifically, how to create it, save it, load it, and use it. The Big Bang was originally designed to generate arrays of data which were then written to disk as JSON, because JSON is so damn flexible. Once on disk, I could read it back into an array of individual objects, loop through it, parse it, search it, and apply it as necessary.

But Godot has a very effective community cheering section for Resources. A Resource is a kind of data transport object. It’s a script which derives from the Resource type and exposes properties to the world. That is the short version. The somewhat longer version is that when coupled with a Resource node, that object becomes a data container that behaves like any other Node object in Godot. It can be seen in the file browser, and it can be dragged over to a property in the Inspector panel where it can be expended to view the values of the properties stored in the script variables. Most importantly, properties can be set in the editor or at runtime, and the Resource can then be written to disk as an entity. Getting to the punchline, a Resource can be created in code, the properties filled with data, and the Resource written to disk, all in a way that Godot natively understands.

Although I had the Big Bang data writing to JSON, I had reached a point where I needed to consider how to structure my Player object, as the Player needs to be present in any conversation about loading and saving data. I draw the system using data from the Big Bang, but the Player holds the info on which system to draw. Like the Bang, I considered writing the data for the Player to JSON, but there’s a big of a snag in this case: I had committed to use Resources for building the player as an Entity Resource and had also decided that the modular spaceship system would rely entirely on Resources. As the ship is stored with the Player, that meant that my Player was already 3/4 of the way towards a Resource-based existence. I could still use JSON, reading Resource properties from the Player into a JSON structure which is written to disk, and which would then be loaded from disk and the data written back to the properties of the Player’s Resource objects. This is not as much work nor is it an anti-pattern, as I have seen it done in several locations by respected Godot developers. But is it is the best way to work with Player data, considering I was one-foot in the Resource camp already?

I had to choose not just how to deal with the Player, but whether or not I should convert my Big Bang system to write to Resources instead of JSON.

Recently, I branched the project again and started another refactor, this time to try pushing Big Bang data to Resources instead of JSON and you know what? It fucking worked. Not just worked but worked brilliantly.

Anatomy of a Big Bang Resource

extends Resource
### class_name
class_name StaticSystems

### --- Signals
### --- Enums
### --- Constants
### --- Exported variables
export(String) var game_version = ''
export(Dictionary) var data = {}

### --- Public variables
### --- Private variables
### --- Onready variables

### --- Lifecycle methods
### --- Public methods
func get_system_by_id(system_id: String):
	if system_id and data.has(system_id):
		var _r = data[system_id]
		if _r:
			return _r
		else:
			return null
	else:
		return null
		
### --- Private methods

Here’s the current code for a Resource that holds all of my Solar System data. I have unabashedly cribbed many ideas from GDQuest’s video on how to load and save games using Resources which basically means that I’d be stupid not to switch to Resources with such an elegant and flexible example made public.

The key here is the data property, a Dictionary type which will hold object definitions for each solar system that’s generated. Dictionaries use keys, and keys need to be unique, so I opted to use the ID property of the system as each entry’s ke. After the Big Bang generates systems, the array is used to build this Dictionary and assigns the outcome to the property of the Resource. The Resource is then written to the save game location as a .tres file, which is the Node-like object that can be moved around within the editor, and which is what actually puts the above script into a form where it can store the data. I also have versions for Planets, Stations, and Jumpgates which are structured in a similar manner.

One benefit is that I can move all of my parsing and searching functions from the DataManager singleton into the individual Resource files which puts them exactly where they are needed and doesn’t require me to make the data repo available through any other means; all of these functions can work against the content of the local Data property. In the code above, I can call get_system_by_id from the DataManager’s static_systems property (which holds the systems Resource), passing a system_id value, and I’ll receive a single data object in return, assuming it’s present (my error trapping needs some work).

Other Resource lookups are a bit more complicated in how I have to search through them:

func get_planets_by_system_id(system_id: String):
	var _dict = {}
	if system_id:
		for _p in data:
			if data[_p].system_id == system_id:
				_dict[data[_p].id] = data[_p]
		return _dict
	else:
		return null

Because looping through a Dictionary only exposes the Keys (stored in variable _p), I need to loop through Data, then use the Key to inspect the data associated with that Key. If the data’s system_id property matches the value I pass in — meaning this planet is in the system I want to query — I add that data to a new Dictionary object, but this time I am using the Planet object’s native ID property as the key, and not the system_id property associated with the Planet. When I consume the output of this function, it’ll be a Dictionary of Planet data, keyed by the Planet ID.

Answers Invite More Questions

One thing that I’m concerned about, though, is how well Dictionary objects play with more advanced methods of data manipulation. For example, the Big Bang continues to use Arrays when building the Universe because Arrays in Godot have a lot of convenience features that I am relying on. Dictionaries are a bit anemic in their options; I suspect there will be a lot of looping, inspecting, and separating of Dictionary data in the future, especially when I get to the point where I need to be able to figure a route from the current solar system to a distant solar system, using the Jumpgates Dictionary.

Although I haven’t actually gotten to the Player Resource object yet — which was the original impetus for this change — I have successfully gotten the Big Bang to transfer to Resources, those Resources written to disk, and those Resources loaded from disk, with the end result being that I now have the ability to run the Big Bang once and then continue to use the exact same Universe structure for further testing. Of course, it’s all bastardized — I don’t yet have a save game browser, nor do I believe I have a sustainable save game structure — but I think that, at least for the Universe data, the decision to move from JSON to Resources was sound, and that the bulk of the work in that regard can be merged back to the main Development branch.

Scopique

Owner and author.

Leave a Reply

Your email address will not be published. Required fields are marked *