Most of what we're doing at the moment is documentation, so development has slowed a little. But blog posts about budget building feel like they would be even more awful than the process itself, so let's talk a bit about the design things I'm thinking about, if not actively enacting right now. I've been thinking more and more about trying to add myself to the list of people trying to teach Godot programming on the internet, so let's see how that goes.
In computing, a tree is a structure for storing objects, which we call nodes. A tree has a node to start at, called the root, which has connections to other nodes, which can have connections to other nodes in turn. Each node only gets connected once, so there are no loops. This structure has a lot of nice properties, especially when you're using it for searching, but it's also very unambiguous - every node is in exactly one place, you don't have multiple paths from one part of the tree to the other.
In Godot, everything in your game "world" is represented by a node in a tree. Complex things are represented by multiple nodes; Godot has hundreds of different node types, so the usual pattern is to build big things out of smaller, simpler parts. A rock has a node which displays an image, so you can see it, and a node which defines a shape for things to collide with, so you can walk into it and push it around. An enemy has those things, and also a sword, which has those things and another shape to mark the parts that hurt when they hit you. The world as a whole has collision shapes and images and light sources - and it also has rocks, and enemies, and probably an object that is the player character.
In Godot, nodes which are connected to the root through another node are called children of that node. A complete configuration of the tree - a game level, or a menu, or a tree that is just a rock - is called a scene. You can modify the tree whenever you want, assembling whole new branches mid-game and grafting them on as children, or hacking off entire branches by deleting the single node that connects them to the rest of the tree. And when you tell the game to switch to a different scene, it just destroys the entire tree, then builds a new one from an existing scene.
Let's go back to your enemy object. At some point you want it to do things, like chase the player and stab the player with a sword and have hopes and dreams, and structurally you can put code almost anywhere in the tree so where should you put it?
The nodes your enemy is built out of are pretty functional, so it's easy to nestle your code in with them. You can change the object's position to move it, and check its various collision shapes to determine what is happening to it, and all of the different parts are so close and easy and it just works. Bundling in the brain with the rest of the parts means you'll always get one when you generate a new enemy, and it won't be left behind thinking to itself when the body is destroyed.
But what about your player character? They have behaviour of their own, which mostly means responding to input, but they also have the player inventory to keep track of. And your inventory system is cool, you push a button and a menu pops up with all of the player's items, and there's a little picture of the character in the corner like in Minecraft. Also, your player should be able to enter a house.
Neither of these things are particularly unusual game features, but they present quite a problem with our current system. You want the character to appear in the menu, so you move it in the tree to be part of that window - and now your enemies are struggling, because their target has just vanished (or worse, they keep following where your character has moved to on *screen*, walking behind your menu and stabbing you through the fourth wall). Plus, you need to remember to put them back where they were when you closed the menu, and what if that has changed somehow? What if they were on a moving train? And then when you go into a building, what happens? You change to the building scene, and your current tree is destroyed, including the player character and all of their customisation and hard won inventory. Your new scene loads up, complete with its own default character, and your player gets annoyed. How do you solve that?
This was, in fact, a problem we faced numerous times in the previous version of Anvilheart at the start of this year. Godot makes it easy and simple to intermingle the reality of a thing, the code and data that makes it "exist", with the representation of the thing, the parts that appear on screen and in the world. But representations are often finicky and fragile, when you don't want your reality to be. So instead, you build puppets.
Your code can be anywhere - as long as you're sure a node exists exactly where you think it does in the tree, you can touch it as easily as if it was right next to you (this is NOT recommended over long distances, for a lot of reasons but especially because where the other node is will likely change a lot, and that path will change more frequently the longer it is). You could, if you wanted to, have a single massive code object that was controlling every object in the game - this is close to a game design pattern called ECS, which I don’t really have personal experience with but vaguely plan to try out some time in the future. ECS conceptually handles large numbers of objects with the same behaviour really well, though Godot doesn’t really have much support for the actual efficiency gains on offer.
And look! All of a sudden, we’ve solved the problems we had previously. All of your player data is stored up in the Big Brain Object, so when you open menus and such it’s easy to make a little sprite object and hook it into all of the same wires that your normal player connects to - it can probably ignore the moving around ones, and the ones that determine what the player looks like should work just fine plugged into a brainless mannequin. And when you move to another area, your player character on screen goes away, and another one replaces it, but the new one also just connects to the same place the old one vanished, and everything works fine and seamlessly. Everything important about your character is safe, outside of the normal scene tree, but wrapped around it like a web.
The actual design we’ve been working with for Anvilheart is a kind of midpoint - everything self contained handles itself, but anything that needs to be recorded or affect (or be affected by) the broader state of the game needs to go through a higher level game object called GameState (in theory this should be multiple objects to handle different tasks, we just haven’t broken it up yet).
This alleviates a lot of problems with keeping things consistent; having one object responsible for the general game state means it has everything it needs to ensure state correctness at hand. It also simplifies the design process, in that it provides a single correct answer to any question that involves “how do I get this information/make this thing true outside of this screen”. But it still leaves a bit of a mess at the lower level.
So what I’ve been thinking about (what I might spend a bit of time doing, now, because I started this article when we were doing documentation but that’s all submitted now!) is rebuilding some of our more complex scenes into a kind of multilayered puppet structure. All of the code is moved into one or a cluster of objects in a scene that perform their own puppetry to handle the local game behaviour, and one of them is in charge of talking to GameState if needed. This, again, makes it easier to keep everything consistent, because the components that care have more information available to them; it also lets us separate code by what it’s responsible for, rather than what literal object in the game world it applies to.
The main target here is expandability; the last few weeks and the very messily hacked in game features have really highlighted in my mind that our current design approach is too much of a mess to easily expand on. Consolidating all of the behaviour code into a couple of points also makes it more maintainable. There’s one particular bug that we’ve had coming up as an annoyance for a while now, and while we can stick a bad fix on it, fixing it properly would require rebuilding a lot of the forging stress system; but if I resign myself to rebuilding every system anyway, I already know how to design the replacement such that that problem will never be one we have to think about again.
Attempts to actually do this have run up against the unfortunate barrier that is my complete inability to think about work for long periods right now, but this is to be expected. We’ll see how it goes.