When developing some sort of character that can be controlled, there is a tendency to want to create one script, say player.gd1, which contains all logic required in an update loop to move the character up, down, left, right, etc. This script will then end up also containing responses to detected collisions; the effects of gravitational, perpendicular, and frictional forces; logic determining damage and death; and the tracking of collectables and achievement. Eventually this turns into one massive magical monolithic mess.

What if you ended up writing very specific mechanics that determine a distinct set of rules governing how the character moves? Bravo! Please basque on your podium because no one has done that before! Once the applause ends, someone in the crowd shouts “build a boss!” At that point, the clever developers will say “I will build a a nemesis who behaves the same way as the character and tries to outsmart it. That way I won’t need to design any new behavior, and the player will be met with something familiar and rewarding. It’s a win-win!”

Hypothetical dialogs aside, starting on a task like this quickly exposes the flaws of our tendency to create one script. Nestled deeply and awkwardly in that beautiful and specific mechanics logic are if-statements checking for inputs and button presses. Gross! Does this boss character have a controller? There is an implicit need for any NPC to make it’s own decisions. So I guess that means we need to copy all the player.gd code and paste it into boss.gd, swapping out button press checks with… some other decision making process? I shouldn’t have to spell out that there will probably be more logic added to the player in the future, which means you will have the privilege of re-implementing (pasting) these updates into your boss script later.

So imagine if we all lived in the real world, where the constraints of our own motion, health, and success aren’t directly influenced by our own behavior and thoughts (I’ve been told). If we apply the separation between behavior and mechanics to the process of developing playable and non-playable characters, we gain a great deal of flexibility.

Specifically, imagine if we could factor out the logic that governs how characters move in the world — what happens when they touch something, and what can damage or strengthen them — from whatever drives they might have to move and react the way they do. This can easily be achieved by creating a mechanics script, say crawler_mechanics.gd, which describes all of ways that a “crawler” can move and interact with the world without giving it a brain. (I don’t know what a “crawler” is, but it sounds non-threatening). We are essentially creating a species which the player may or may not control.

For points where decisions could be made, we leave hooks that can affect the outcomes of the character’s mechanics. These are decision points that will decisively affect the next action the character might do. Some examples might be “I am going to jump”, “I will move left”, “I will eat several plain slices of bread.” The mechanics doesn’t care how or why that decision was made; it just knows that it was made, and knows how to govern the future of the character because of it.

That means that the purpose of a character’s “behavior” is to implement these decision functions. Each specific character can now have its own script. Let’s say that jessica.gd provides the specific decision implementation for the playable character. Since this is a playable character, implementing decisions is simple. The character will decide to jump when you press “A”. The character will decide to eat bread when you press “Space”. Of course, these decision functions are called by the underlying mechanics script, which controls that species’ frame-by-frame process. Is that like real life? You tell me.

If you are a fan of over-engineering, like me, you can create a separate behavior.gd module, which serves as a model to keep track of the current state of all possible decisions that are being made by the character at any given frame. That way you can carve out a nice friendly class containing all possible decision points without having to muck through the 1000+ LOC mechanics script (which will inevitably become a black box at some point).

Now let’s say that we want to implement Jessica’s rival, Bryan, who is a boss character. In bryan.gd, we “simply” need to implement the decision functions for jumping, moving left, bread, etc. Oh no! Now we need to implement some kind of AI. We no longer have the luxury of pressing buttons to control the character. However, the point of all this is that now the complex and potentially expensive process of making decisions can be completely separated from the ugly mechanics script (which was “beautiful” 6 paragraphs ago). We can rest assured that all the motion and reaction that makes up the “crawler” species will work the same as it does with the playable character.

For future posts, we will dive into the non-playable character’s decision making process. I think some more abstractions may be in order to help this out. Please note that I am not particularly educated in the topic of AI. Sure, I have a textbook, and sure, I’ve read it before. But I’d much rather just come up with my own algorithms anyway. And also I forgot much of the dreadful terminology. Stay tuned for an algorithm and architecture that involves throwing a whole buncha graph nodes in the trashcan.

  1. The gd extension refers to the Godot scripting language (GDScript). Godot is a game engine geared toward 2D games. GDScript looks a bit like Python on the surface, but there are some tedious differences lurking about. ↩︎

Leave a comment