Well, it’s been a while, huh?
Life’s had its ups and downs throughout the last couple of years and, for a long time, I didn’t really have much that I thought I could talk about. This all changed in recent times, though.
I WAS pretty excited to show off some good old heterogeneous stuff on my new Intel ARC A770 GPU but it died in a power surge… along with the Radeon VII too, so for now, being only on the Titan limits some of what I could be playing with.
But that doesn’t mean I’ve been just idling, instead I’ve been playing around with some new stuff. After a lot of platonic admiration and behind the scenes stuff, I’ve finally got properly started on the Godot engine. In addition, I’ve been working on some smaller projects here and there. I’ve written a bash script to manage multiple sites on a webserver and an application for parsing conditional game text with good old Qt. And even some new ToyBrot related work. I also set a Ko-Fi so I can beg for money, like some sort of content creator. If I ever get some, maybe I’ll put it towards replacing those dead GPUs
I plan to talk more about those projects in the near future, there’s some backlog to post through all of a sudden. For today, though, I’d like to talk about…
The Godot Engine
So, the Godot engine is an open-source game engine on which I’ve had my eyes for a long time now. It’s gained some prominence recently after Unity was kind enough to remind everyone why you should avoid at all costs becoming dependent on proprietary software stacks (even though there are still some who maintain unwarranted faith that another company like, say, Epic, would NEVER betray them in a similar manner)
Godot has been floating on the background for quite a while. Recently, the release of their major version 4 has seen it gain a lot of strong features and improvements. Internals, scripting, usability…. it’s got touches up basically everywhere. Now, I myself DO come from a game dev background. I still keep around my own toy game engine which I’ve built on top of C++ and SDL2 for school and then just kept coming back to every once in a while later on. But ended up just kinda circling the space for over a decade.
In spite of that cowardice, I DO still have ambitions of releasing my own game projects and long thought I had to take the dive and stop working on an engine so I could, at some point, start working on actual games (though, will not lie, coding engines is super super fun. part of the problem, really). And, at last, I’ve finally done it. Got down and started actually making stuff.
This post is about my general impressions diving in. Though I have that background in games, my professional career has been mostly as an application developer so a lot of how I think about software is in that realm I guess. For now I want to talk a bit about what I liked, what I didn’t like and how it’s going, just at a general overview, through the lens of my experience with 3 tutorial projects.
The first steps
A while back, Humble had a bundle of Godot Courses from Zenva (it wasn’t quite the linked pack, but it had all but one of those). Seeing that as a good place to start, I decided to go through these and, honestly, had a really good time. The tricky thing, as a seasoned developer jumping into something new like this is that there is a certain feeling, a compulsion, even, to just kind of assume you know the basics and you get it, “yeah, yeah yeah” but there’s always something new.
This is where I find that having some sort of structure like this is helpful at this stage, even if, yeah, there IS a lot that will be very familiar. Making sure you don’t skip over the basics can help you make sure your bases are covered. My one complaint about these courses is that, after the basics, they’re meant to be standalone. If you don’t care about 2D, you CAN just go follow the 3D platformer tutorial. This means if you DO go through them all, a lot of those basics end up being repeated.
I’d definitely recommend you following SOME sort of basic tutorial initially. If you have a chance to grab these, they’re smooth and easy to follow, but in general there’s a LOT of Godot material out there these days, both written and video. Heck, if you keep reading, you might suddenly find you’ve slipped into one of those
As part of these tutorials, I’ve also ended up making some tiny demos which I have built for WebAssembly and uploaded to the site. I’ll be linking them further down below, but I’ve been keeping a list of these tiny demos and whatnot in a Demos and Toys page
So what's Godot like
All right, boys, girls, neither, both, other, foxes, puppies and everything in between. Grab a cup of coffee or 17, it’s finally time for some nerd talk.
I’m going to quickly go over some of the core concepts here but this isn’t really a tutorial so I won’t go too much into details. Think of this section as a bit of a glossary, it may help you follow proper tutorials and understand the rest of this post.
So Godot is a general purpose game engine based on inheritance. There is a Node class and, aside from some base types, everything derives from it into progressively more specialised types. Just, good old OO stuff.
Nodes are organised in trees which are called Scenes. The name can be a bit confusing at first. You’d think this is like an application page or game state but nah, not necessarily. If you got a character sprite and then added, as a child, a hat sprite and saved it as a “character with hat”, that’s a scene, which you can now instantiate like it was a node inside some other scene.
Most nodes in Godot inherit from one of three important base nodes:
- Node2D
- Node3D
- Control
Node2D and Node3D are pretty self explanatory, adding the basic spatial information to insert them in spaces. Control nodes are Godot’s GUI elements: Labels, Buttons, Containers, etc… They are versatile enough that the Godot Editor itself is built on Godot and it’s a pretty powerful IDE.
All in all, the built in Node library is pretty comprehensive
Each Node in a scene has a script that determines its behaviour. Implicitly this is the base code that describes that node type but you can add scripts which “extend” that base behaviour to each node which give them specialised behaviour to define your application’s logic.
Though you can access elements through the scene tree itself, as shown in the screenshot above, a lot of the actual communication between elements happens through a system of signals which should be very familiar to those coming, for instance, from the likes of Qt.
A signal is a special function that represents an event. It can be connected to a different function, often in a different object. Whenever the original object emits the signal, the connected function will be called. So, for example, your “New Game” button will have its _on_pressed() signal connected to a function which loads the initial map on your level manager or what have you.
Scripts in Godot are traditionally written in Godot’s own scripting language, gdscript. That’s what I’ve been using exclusively. It’s quite inspired by Python so people familiar with that language should settle in without too much friction. One thing that initially put me off was dynamic typing but, it turns out, you can define explicit proper types for things, so that’s a relief. There is also support for C# and a lot of effort into making it as much of a first-class citizen in Godot as gdscript. Personally, there’s many years since I’ve messed with C# and, as such, I’ve stuck to the native script. For other languages, Godot has a system called GDExtension which aims to provide bindings to other languages. I haven’t personally messed with it YET but it’s a matter of time honestly. C++ has official support and both Rust and Swift have community support. This system is new to Godot 4. Previously there was a system of C++ modules which required recompiling the entire engine. It’s still there but undersirable, for obvious reasons
The final core component of Godot are Resources. These are simply your plain data types. Your sounds? Resources. Your images? Resources. You application configuration? Resource. A save file? Another resource. If it’s a thing that has logic to it, it’s a node. If it’s data that is used by other components, it’s a resource. And if it’s just plain code, that’s a script.
And that’s the gist of the basic framework. You create a scene from a root node. The type of root node indicates what type of scene it is (2D, 3D, UI…) and hook things up with scripts that talk through signals. So what’s that in practice?
Making games with the game engine
Babby's first run and jump
One thing that is immediately evident is how well tailored Godot is to its primary purpose. The nodes available are many and represent basic common game elements really well. If you know the engine and have an idea of what you want to try, you can get some basic prototypes in no time. Each of the Zenva tutorials took me about an afternoon to go through and a lot of that time was spent on me polishing stuff by adding sounds and animations and whatnot or just trying to push the demos a little bit further.
You need to place a character on the screen? Okay, start with a CharacterBody2D node and that already has hookups for your sprite and physics. Need to draw your level? Well, look at that, there’s a TileMap system already right there. And it can have the physics info attached and whatnot…
The first “real” mini project I did after the initial introduction tutorials was a 2D platformer. This was also the first project I uploaded here, just for fun. You can check it out if you’d like
It’s quite cute, even if it is the one where I was the most “behaved” in terms of sticking to the source material. And, while I don’t have the full projects on gitlab or anything of the sort for these projects from the course, the full extent of my character script is this
extends CharacterBody2D
var accel : float = 10.
var max_speed : float = 150.
var jump_force : float = 250.
var gravity : float = 500.
var friction : float = 10
@onready var initial_score : int = Globals.total_score
var score : int = 0
@onready var score_label : Label = get_node("CanvasLayer/ScoreLabel")
@onready var position_zero : Vector2 = global_position
@export var kill_plane : Line2D
func _ready():
set_score(0)
func _physics_process(delta):
if (kill_plane != null) and \
(global_position.y >= kill_plane.global_position.y):
game_over()
if not is_on_floor():
velocity.y += gravity*delta
if Input.is_key_pressed(KEY_LEFT):
velocity.x -= accel
elif Input.is_key_pressed(KEY_RIGHT):
velocity.x += accel
elif is_on_floor():
#need to stop moving
velocity.x = move_toward(velocity.x, 0, friction)
if Input.is_key_pressed(KEY_SPACE) and is_on_floor():
velocity.y = -jump_force
get_node("JumpSound").play()
velocity.x = clamp(velocity.x, -max_speed, max_speed)
move_and_slide()
func game_over():
Sounds.get_node("PainSound").play()
get_tree().reload_current_scene()
func set_score(value):
score = value
score_label.text = str("Score: ", initial_score + score)
func collect_coin(value):
Sounds.get_node("CoinSound").play()
set_score(score + value)
Behind the scenes, signals end up doing a lot of the heavy lifting. This game_over() function gets called by enemies and obstacles when the player hits them.
You may have wondered a while back where did the non-visual nodes fit in. An use case can be seen here through this Sounds global object. That’s what it is, just a regular Node with a script attached to it that tells AudioStreamPlayer nodes it owns to play their respective sounds. The reasoning here is that the game_over() function I pointed out reloads the current scene. Nodes belonging to the scene get destroyed and recreated which would interrupt sound. The whole thing is pretty straightforward
Working WITH your tools
The next project was probably my favourite of the bunch. The proposed project is kind of a very basic pokemon clone but in my version it ended up as a very basic card battler instead. This was courtesy of the assets I had at hand from many years of stockpiling asset bundles and whatnot. I started what has since become a trend in rummaging through the assets I have for ideas of what I could then put together.
I DID kind of end up making the mistake of not really paying any attention to asset size so this ended up rather on the large side for such a tiny web app. So just keep in mind it may take quite a while to load if you decide to check it out.
Ultimately, there were three things I like about this project. The first one is that, with this being a turn-based scenario it suddenly was very script and event driven. You’d think making a real time game would be way harder but it’s the other way around, actually.
Because of how powerful the built in components and behaviours are, a lot of the things are given to you more or less semi-done. But if you’re yourself having to manage things then there are a lot more communication and sync to take into account, you need to make sure your game is paced correctly, that the player gets correct feedback for things…..
This ended up making it a more involved thing than the previous one
The second thing that was interesting was playing around with @tool scripts. This is an annotation that, when added to the top of a script, will make it so that the editor is running this script while you edit, and you can even have different behaviour for when your script is in the editor or in game.
In my case, I had the cards load their picture dynamically depending on what “type” they were. But this made it annoying on the editor because they would then be all equal or blank. After finding out about @tool I was able to leverage it to make my life easier by adding some stuff to my Card script
# Called when the node enters the scene tree for the first time.
func _ready():
turn_manager.begin_turn_for.connect(_on_begin_turn)
turn_manager.end_turn_for.connect(_on_end_turn)
$Back.scale.x = 1
$Front.scale.x = 0
health_bar.max_value = max_health
_update_image()
_update_health_bar()
if is_player:
$Front/SkillList.populate(combat_actions)
else:
var enemy_type : EnemyType
match randi_range(0,2):
0:
enemy_type=load("res://enemies/crab.tres")
1:
enemy_type=load("res://enemies/barrel.tres")
2:
enemy_type=load("res://enemies/bird.tres")
combat_actions = enemy_type.skill_list
type = enemy_type.type
_initialize_health(enemy_type.max_health)
phys_def = enemy_type.physical_defense
spec_def = enemy_type.special_defense
_update_image()
_update_health_bar()
#For editing convenience, we want these expanded on the editor
#But upon running, hidden should be the default state
if !Engine.is_editor_hint():
$Front/SkillList.scale.y = 0
$Front/ListBG.self_modulate="#ffffffb5"
func _process(_delta):
if Engine.is_editor_hint():
_update_image()
This adds an extra layer where you can extend functionality of the editor itself so it can better help you get your things done just the way you want.
And the final thing was using custom Resources. Once you start fiddling with it, it’s quite surprising how many things you can do through it in a reusable and modular way when, a lot of the time, the instinct is just writing things in a script.
For these project, each attack and each enemy type is a resource, and then I just assemble them by essentially composing things.
Make sure you look pretty doing it
The final project of this batch was a real time strategy demo where you command some units around. I think more than anything, on this one, I ended up spending a lot of time in polish. Animations for the sprites and selection cursors, finding out how to use TileMap layers and animated tiles…
I ended up having less of a good time mostly because of how much I struggled with the pathfinding system. I think in the end I was not completely satisfied, but things still worked and they for sure looked real dang cute.
At this point I think, as comfort set in, I started drifting a bit from what the exercises actually were about and was all the time, instead, thinking about my own projects and what I wanted to do myself. Ultimately, a project that gave me that little reminder of how deep the “polish” rabbit hole can go. Part of this polish was abusing the power of the AnimationPlayer node. Again, for the Qt folks in the crowd, this is the same calibre of stupidity that is opening up an Animation (or, for the truly disrespectful, SequentialAnimation) item in QML. You will point the node at various things and they will submit to your bidding, no matter how depraved.
But what about the annoying stuff, tho?
If Godot was perfect, I wouldn’t be writing this. I cannot dream for nearly as long as it would take to write this post. So, of course, it has it’s own annoying bits.
While I’m more comfortable with it, there is still some resistance to gdscript from my own part. It’s the curse of C++, you look at everything suspiciously when it’s not telling you exactly what it is doing. But make no mistake, gdscript is super handy. I DO wonder if I would feel more at home with C# instead, but haven’t tried that out, at least not yet. Kind of forcing myself to internalise the “environment standard” before I go off doing random stuff.
Once I started dealing with more complex code I kind of felt like I wanted to be in a more code-centric environment and decided to try out the language server support and code from either VS Code or Pulsar. That DID work but it would keep crashing and taking the Godot editor with it so no such luck so far. Kind of hoping the wrinkles there get ironed out soon.
And speaking of code-centric environment, Godot is, at least normally, quite married to the editor. While you CAN go behind its back for a lot of stuff, instantiating and adding nodes on your own, connecting to signals manually through scripts…. this ends up also causing you to lose some visibility and functionality from the editor. In my card battler, I connect signals from the script and even though they are @tool scripts, the editor doesn’t really know and doesn’t show the indicator for “this node has signals connected to other stuff”. This can make it a bit harder to understand the macro of your program, especially since the nature of how scenes are organised and functionality split in scripts, it’s quite common to have loads of scripts all around, each with only a tiny bit of code. That make each file simple, but can make it a hassle to remember what talks to what in what way
So, is it fragrant and dark or just utter trite?
Overall I loved working with Godot. Definitely a tool I want to make a standard in my belt.
Waiting for Godot 4 also turned out to be a thing I was happy with. I used 3 a little bit and from that version to what’s currently out there was some polish in key areas of the engine. In particular the code editor is the one that stood out to me. I mentioned before how I wanted to use an external IDE but, really, the built in editor is good enough. It’s solid, amazingly so. You can even split it off from the main editor into its own separate window. This alone already makes it better than a lot of “real and professional IDEs, guise” that I’ve worked with. I’m seriously in awe of just how nice it is to code within Godot.
There’s a lot more exploration that I want to do of Godot, both as a game engine as well as a generic application framework. Some of that is already underway. But if you’re wondering if you should give it a go, the answer is a resounding yes. Find yourself a tutorial, grab an asset pack from Kenney or whatnot and go crazy. You’ll have a good time.
Coming next
I’ve mentioned way up top that I already worked on some more stuff since these little demos and I’m going to talk about these other projects next.
The first one is a two for one. I wrote a library in pure C++ to parse conditional text, mostly for games. And also, a demo application in Qt to parse and play with conditional text. The library is called Cartomancer and the application is called Foreteller. You can check them out already if you want spoilers. The application has pre-built binaries for Windows and Linux available from here (sorry Mac folk, Apple says I can’t build software on my Mac Pro 2.1 from 2007)
The second one is I’ve made an interactive version of toyBrot with Godot. This one raymarches through a fragment shader, like it’s pretending to be a sane application and you can tweak parameters and fly the camera in real time. I’ve done very little in terms of optimisation but it’s still really cool. If you want to have a go, you can drive with WASDEQ and rotate the camera by holding mouse-right and moving the mouse (watch out for gestures on your OS or browser). Just a pretty cool toy for now
Both of these have interesting things to talk about and I’ll do it. But pacing these out, one post per week, so you’ll have to hold on a bit.