This lil’ guy is…not all that impressive, neither from the perspective of someone who stumbled upon this post looking for…I dunno, ducks in smoking jackets, maybe…nor from that of someone who knows Godot well. Everything is new to someone, sometime, and this is my time; as usual, I am cataloging my successes and failures in near real-time in the hopes that someday I might need to remember what I did, and to serve as a help or a warning for those who come after me (not in the Liam Neeson sense, but you get the idea). This post comes one day after my post on custom resource use in my poor space trading game, and I am happy to say that the debug output you see above is the culmination of at least some of that. Let’s take a look.
Custom resources in Godot are built from two things: a script, and an object. The script exposes properties and may have some internal logic that deals with those properties, but that second part isn’t strictly necessary. The object is built on top of that script and surfaces the properties so we can work with them in the Inspector, and so we can use that resource by attaching it to another object that needs to use those properties. As we can inherit objects from other objects, it’s often useful to create base versions of objects which hold properties common to many different types of other objects, and to branch off when the needs diverge.
Exhibit A: A Starship Hull
Space games are hard, man. There’s the player, who is basically the spaceship, flying around in space. If I wanted to limit the player to buying off-the-rack ships (which is possible, but also doesn’t preclude this next bit) I could gin up some hard-coded ships — a lot of ships — and call it a day. But that’s no fun, so I’m going with the idea of modular ships.
Players begin with “a hull”. Hulls are the attachment points for other components like engines, shield generators, cargo holds, and weapons. With this concept, I started my resource journey with a hull component called, unimaginatively, ComponentHull.
As this is intended to be associated with a resource, we extend from the base type of Resource and then give it a name ComponentHull. The script contains three sections: a signal (currently unused), a buttload of properties, and an event handler which should fire whenever certain properties are updated. I am soliciting advice on whether or not this is the best way to handle the immense task that is modeling a modular spaceship, but for now we’ll run with this.
Next is to create a resource object that uses this ComponentHull script. I have my files semi-organized in a test hierarchy on disk:
Here you can see in the Scripts folder, we have general purpose ComponentHull.gd, and the more specific resource object built on that general purpose script, Astro_Frames_001.tres, which lives in the Hulls directory. That .tres file is an object we can drag and drop into other properties via the Inspector panel in order to provide info and functionality stored and defined in our ComponentHull script.
Exhibit B: The Player
Unfortunately, at this point there’s not a lot of visible reaction to the script or resource object. I could wire it so that it “works” in the editor, but it’s not a hardship to push on to get this all connected where it needs to be which is, in this one test case, with the player.
I have a Player Scene already, as I am working within my “movement test” project. The player structure isn’t particularly pretty, consisting mainly of a Player.gd script and a Player.tscn scene which contains the minimap scene, the ship sprite, the camera that follows the player, and the collider for physics detection.
As I have a Player.gd script which currently handles keyboard movement and is kind of the de facto player implementation right now, I needed to amend it to add a new resource property, player_entity.
Record Scratch: Wait, Where’s The Hull?
OK so…sidebar! As I am going resource-crazy with this test, I understand based on other projects I have seen that it’s a good idea to start granular and build up to something more specific. A player is often referred to as a type of “entity” or “actor” in a lot of the code examples I have seen. But then again, the same could be said about NPCs. When you think about it, both the player and NPCs share a lot of similar properties, like names, portraits and — wait for it — starships. It makes sense to create a common base for “entities” that exposes properties that both players and NPCs would need.
EntityBase.gd contains these properties. As I haven’t gotten anywhere near working with an actual entity, this is just spitballin’ but I know there are some absolutely necessary properties that I will need in the end.
From EntityBase, I extend to another, more targeted resource called EntityPlayer.
In the end, this script will get the movement controls, probably, but for now, this is just a way to hang the ComponentHull on to the actual player representation in the game.
What does this actually mean, all of these scripts? Why not just throw this all into the Player.gd and be done with it? Good question, theoretical reader. What this is doing is called loose coupling. When we define the actual visible player (Player.tscn) if we were to attach the ComponentHull direct, then we could never have a player who wasn’t a ship. Maybe I might find I love building this game so much I allow some feature creep so that the player could walk around a station; do I really need to drag a starship around with the player at that point? No, I do not, so the player shouldn’t be dependent upon having a whole bunch of starship data attached. Plus, by separating the visual player from the processing player, there’s opportunity to swap out the Entity-based resource should I want or need to. I could, for example, switch from a starship to a ground vehicle, where the resource for the starship handles movement in one way, and the resource for the ground vehicle handles it a completely different way. The core player wouldn’t need to know how to determine which one to work with because the functionality is encapsulated in the resource which is currently attached and active.
The above image is the Inspector for the existing Player.gd and scene. I have added a Player_Entity property to that script and have selected the Player.tres resource built on top of PlayerEntity.gd as the contents. If you look down at the bottom, you’ll see the property Current Ship which has a value of Astro_Frames_001.tres, which is the resource object built on top of ComponentHull.gd.
Here’s the complete Inspector as it stands now:
Here’s a hacky diagram showing the hierarchy at this point.
- Player.tscn – Player.gd
- EntityPlayer.tres – EntityPlayer.gd
- EntityBase.tres – EntityBase.gd
- (Resource) Current Ship
- Astro_Frames_001.tres – ComponentHull.gd
- EntityPlayer.tres – EntityPlayer.gd
What About That Debug Output?
To show the real benefit of this Russian nesting doll structure, I added that debug output to the Player.gd script that currently controls the movement of the player.
Waaaay down at the bottom there’s a function called _debug which is spitting out the string. It’s being pulled from the chain of inherited relationships starting with the resource assigned to property player_entity, which looks to its own property current_ship, which looks into the ComponentHull resource for the property name.
Resources are designed to allow the developer to populate objects with presentable or actionable property data and functions during design time, and to save that data locally to where it’s needed. For someone who is used to working with databases like myself, this can seem like an arduous task, instantiating new objects based on a template and filling in the blanks by hand over and over again, but it does give me a way to test the game without having to rely on first setting up a data source, generating data, and populating the game from that data. It also gives me the opportunity to construct, see, and tweak the definitions of key game objects; while I have created these resource-based objects based on what I think I need, I know that in the course of development those needs will change. Using resources is a lot easier to manage than having to rekajigger a data source, with its schemas and getters and setters and intermediary functions, before I can validate if the change was even worthwhile. The work up to this single point wasn’t simple and, to be quite honest, is getting confusing, and I’ve only just started, but it’s all in the service of creating reusable templates that can be quickly deployed and updated en masse if needed. It’s also easy to get access to the properties in the tree, which is really where the rubber meets the road; none of this would be worth a thing if there was more code written to access the data than there was to create the data in the first place.
SslaxxJanuary 6, 2023 - 7:01 PM
For some of the debug stuff, putting it within a “if (OS.is_debug_build ())” might make it easier to separate debugging code?
ScopiqueJanuary 7, 2023 - 8:20 AM
Ah! I didn’t know Godot could do conditional code. I will put that in the “useful things I learned today” file. Thanks!