Welcome back to the VV Eekly Update! It’s hard to believe that we’re getting into October, ~spooky season~. Fall is perhaps my favorite season. The weather gets just a little chillier, meaning I can wear my favorite comfy sweaters, and I can drink my favorite hot drinks comfortably! I love hot tea, hot chocolate, hot cider, hot everything!
It’s a little ironic that fall doesn’t feature in Vyn and Verdan very much… but hey, if Vyn and Verdan becomes a surprise indie hit, then maybe a DLC could feature fall?? Here comes Vaut Vaumn Vall character-to-be-named-later!
In today’s VV Eekly Update, I want to continue from VV Eekly Update #11 and continue telling you about patterns in Godot.
Duck Typing
What do ducks have to do with Godot? Let’s start with some background.
Godot’s two main supported languages are Gdscript, a custom-built scripting language for Godot most similar to Python, and C#, a common language similar to Java. I’ve chosen to use Gdscript, so I’ll be focusing on that.
Gdscript is a dynamically typed language. The first consequence of this that you’ll face is that variables don’t need to have a type associated with it.
var thing = 5
thing = "Hello"
thing = Object.new()
Most people who’ve worked in scripting small programs will say “Great, I can move fast.” Most people who’ve worked in a large codebase will immediately tell you that that’s terrible and that they hate it.
Fortunately, Gdscript also allows to give variables static types.
var thing : int = 5
thing = "Hello" # This doesn't work!
There are settings in the Godot editor where you can turn on and enforce static typing throughout your project. Whew!
I highly recommend turning on static typing and (almost) never using dynamically typed variables.
But you might be saying: that’s cool, but how does this relate to ducks? There’s another interesting aspect of dynamic typing.
class_name Animal
...
class_name Duck extends Animal
func quack():
...
...
var dog : Animal = Dog.new()
# Dogs can't quack.
var duck : Animal = Duck.new()
duck.quack() # But ducks can quack!
One of the most difficult concepts to grasp when moving from a statically typed language to a dynamic one is duck typing. Duck typing makes overall code design much simpler and straightforward to write, but it’s not obvious how it works.
https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_advanced.html#duck-typing
Duck typing allows you to call methods on objects without knowing that those methods exist. In the previous example: the duck object is an Animal, but Animals don’t have quack. However, you can still call quack on duck!
“When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck”
As a firm proponent of static typing, I’m not quite sure how I feel about duck typing. Honestly, I avoid it whenever I can. However, in the world of game dev, taking your time to structure everything properly the first time is usually a luxury. No one will leave a Steam review giving you a 1 star review for your code choices (haha or will they??). Duck typing is a useful shortcut to know about and carefully use.
Code-as-content
Often times, when making a video game, you’ll need to add content. These are things like “this enemy has this name, this amount of HP, and this description” multiplied by 100 enemies, or “this item has this name, this description, and this behavior” multiplied by 200 items. Naturally, if you don’t have a cohesive strategy to organize content, then your project can quickly become a mess.
A lot of people who use Godot (or other game engines, like Unity), will recommend using simple containers for each piece of content. In Godot, these are resources; in Unity, these are ScriptableObjects. (This is a blog based on Godot, but I’m telling you about Unity to demonstrate that this is a common pattern.)
Resources tend to work well for simple use cases, but I haven’t been able to figure out one problem: how do you attach code logic to a resource? For example, say you have a Slime enemy – how do you tell the Slime that it should chase the player around?
One method for doing this is to have a EnemyStats resource, which says “This enemy is called a Slime. It starts with 10 health, and it can do 1 damage.” Then, you would have an EnemyBehavior class, which reads from the EnemyStats resource to know how much damage it can do. But, if you do this, then why not just hardcode the EnemyStats resource into the EnemyBehavior class? Why do you need two different things here?
I think resources work well if you have relatively boring pieces of content that need to be represented somehow. For example, if you have 50 different hats that you can put on your character that are purely cosmetic, then that’s fine. I would still strongly consider a JSON file instead since resources can only be edited one-by-one and that’s annoying, but sure. (There are plugins to fix this, and I’m sure there’s a PR somewhere to incorporate that into Godot itself)
For anything more complicated, I would recommend using code-as-content. Instead of putting the content into a separate readable object like a resource, you just put the content directly into the code. Instead of having 100 different resources to represent 100 different enemies, you instead have 100 different classes. Theoretically, this is a tad less performative, since classes require more memory than resources, but I doubt that you’ll see a realistic difference.
It’s not just me that thinks this! I had been leaning towards this for a while, but then I was validated from the outside too. Casey Yano, one of the lead designers of Slay the Spire, wrote a blog post about MegaCrit’s testing of Godot about a year ago. In the “Game Content Pipeline” section, he talks about how they, as a team of technically comfortable designers, found it much easier to put the content directly into the code, where it can be easily edited, easily searched, and easily manipulated with a powerful IDE. I borrowed the “code-as-content” term from him!
tl;dr: If you’re technically comfortable with it, put your content into your code! Be comfortable not listening to the many people recommending only putting content into resources.
Well, that’s it for today! If you’ve made it this far, congrats! You’ve read through a bunch of techno garble that you probably won’t ever use, but at least now your brain is a little stretchier. Whoo, stretchy brains! If you have any comments about today’s Godot patterns, please drop by the Discord!