Posted December 21, 2019 by Enric Llagostera
#godot #tech
Hi folks, I have recently finished porting Gardenia to Godot. It was my first complete project using the engine and I wanted to share some notes and thoughts.
So, this is a recap and some notes on how I felt using the tools, its programming system and editor. It is a bit on the technical side and I ended up using a lot of programming jargon. I might write more in the future, focusing on use experience.
Gardenia is a 2D game, with no physics or movement at all. It was not a project started from zero: it was a porting / adaptation of an existing game that involved some redesign of implementations and some of the game logic. However, it means that I wasn’t thinking through systems from scratch, which is quite a different process when engaging with a tool. This was about a 20-something days process, so it is not the longest engagement or the most efficient use of Godot.
I worked with GDScript, and didn’t use the Mono / C# setup, which would be a goal for a future project. The export platforms were Windows, Linux, and Android. I didn’t get the Web export to work yet, in particular because I used external storage and threading.
I broke down these notes into two sections (programming / using the editor), but they are not a binary separation. Much of programming in Godot is also intertwined with using the editor. I tried to separate the text more in terms of the experience of doing one and the other and for readability.
GDScript is a Godot-specific dynamic language (that uses duck typing), so there were some breakdowns related to me being more used to static typing. There are some static typing tools for GDScript, but they are a bit brittle and ended up confusing me more than helping in the end. They will probably be developed further in future versions.
The language has plenty of useful built-in functionality for game dev, from randomization, to math, to literal declaration of objects. It feels a bit like using a game-specific version of JS or Python, which are languages I enjoy using. They often feel way less bureaucratic than C#, and it is a readable language with few organizing things, like brackets or curly braces.
A big advantage of using GDScript in Godot was to use the built-in Debugger in the editor. It has some usability issues (e.g. I’d have liked to pin the Monitor window separately from the Profiler, and conditional breakpoints would be nice), but it is a powerful tool.
The built-in code editor is not that great, though. It could have better find-and-replace tools, which would have been really helpful for renaming / finding things in a dynamic language like GDSCript. It could also let me edit files that aren’t in Godot formats, like JSON, there. It was annoying and often confusing having to keep another editor open to change variables on a JSON file when balancing the game.
Everything in the scene hierarchy are nodes, and nodes themselves can be scenes. So, you can breakdown a scene into smaller ones to work in isolation and then compose much larger things out of them. It is a bit abstract, but I find it does help to sketch / organize things.
Each node can only have one script attached to it. This was a break from things like Unity, where you are attaching a bunch of smaller scripts to a single GameObject
. However, I found that this helped me think the relationship between objects and keep them separate.
Because scenes are nodes, the process of moving from one scene to another and keep information / continuity in the game was modular. I could create a top-level scene to persist the audio player and game data without having to handle combinations of creation / destruction / different game events. Most of the time, the _ready
function (which is called once after all the children of a node are already in the scene) was more than enough for organizing things.
Referring to other nodes in the scene uses a path-like syntax (e.g. get_node("../GUI/StartBtn")
). This can both be used as absolute or relative paths, which can be cached and saved, and I found that intuitive to use. The downside is that often you end up using strings as access points, which can lead to broken references sometimes. There are ways around it (like using the “onready” var modifier in declaring variables with references) or export things to the editor.
Signals (GDScript’s way of creating events) is a very cool way of structuring code. By using it with coroutines (that let you “pause” a function), I could organize things in time. This way, I could let parts of code wait for other things to finish, and make things more declarative overall. It took some getting used to the code execution flow, but it was very quick to use afterwards.
Setting up signals with different objects via code was very helpful and I often did that instead of using the editor tools. This way it was quicker to reuse some of the wiring (e.g. all buttons doing similar logic / animation on mouse over).
Overall, using signals / events was efficient and helpful. It supported me in thinking through process in separate steps and to find clearer ways toe express them.
Here are some random highlights / notes related to different parts of the engine APIs.
The API for using the file system is very readable and effective. I could quickly organize much of the game balancing variables / constants in separate JSON files and load them on startup. So, the workflow for trying numbers out was 1) change a spreadsheet on GSheets / Excel; 2) export it as JSON; 3) Load it and use in-game.
Saving and loading the game in separate files was also quick to setup. It took me much longer to figure out how to break that process into separate frames (because very large worlds to save) than in actually using the file system API.
The input system has an InputMap
system that lets you define actions which map to different actual buttons / devices. This is quite helpful and good for implementing configurable controls.
The event-based _input(event)
function is where much of input handling happens. Unfortunately, some of the documentation about how inputs can be handled (in particular for mobile) are not very fleshed out yet.
This was a pleasant surprise. I had never got threads to work well on a project before. I wanted to use it to make the calculation of the tile updates in the worlds faster, because it was a read-only operation on a large dataset that could be broken-down in chunks.
Creating the threads was quick, but understanding how data flows in and out of them was trickier. In the end, combining the threads with signals was the way to go, and the project got huge increases in performance because of that. I’ll definitely keep this tool in mind in the future.
OK, so here are some notes on parts of the workflow / use of the Godot editor tools.
Gardenia has lots of tiles being drawn at the screen every frame, so I ended up using a custom way of drawing things that bypassed using nodes. The API for doing that (by extending a _draw
function) gave me a clear point of change for customizing that, which is great.
I did not use the import system for sprite atlas or spritesheets, but it didn’t seem particularly more cumbersome than other tools. Still a multi-step process though, dragging, dropping, renaming things. In the future I’ll probably try some middle tool or plugin, like an Aseprite importer.
Importing and using sounds was very quick, not that many issues. Godot supports both OGG and WAV files, and I used both without problems. It was also straightforward to make some randomization of pitch and volume to handle repetition.
I loved using the localization system. You can create a spreadsheet (csv) with ids in the first column and then a column for each language you want to support. Then, when importing this csv file in the project, Godot will convert it to translation files.
These files mean that Buttons and Labels on the GUI try to fetch the correct language text, just by having their default “text” property set to one of the ids in the spreadsheet (e.g. “START” button -> becomes “Começar” in pt_BR or “Start” in en_US). This is very quick to use and makes it very friendly to localize a game. There are also image-replacement tools by language in the editor, really useful.
You can also use .po
files and gettext
, which are systems for localization used a lot in free and open source software, and that have large communities of users and tools.
This internationalization system being so built into the tool, to me, is a great example on how tool development needs to be diverse. It greatly benefits from happening outside of the English-centric parts of the world. Godot’s community of developers centers and attends to supporting diversity of languages out-of-the-box. This is visible in both having this localization system and also the editor tool and docs are translated to a variety of languages.
Setting up export options in the project settings is a generally understandable process, but some options are a bit arcane. I quite like how you can override specific settings for different platforms, and I think that will be very helpful when dealing with more cross-platform games. Also, exporting things from the command-line is a nice option to have, but I haven’t used it yet.
Exporting and testing the game in Android was very quick and friction-less. Both the step-by-step debugging and output logging worked well when running remotely. Overall, a very nice system to use.
OK, so this is it. Working on this game was a great experience, and I believe an important step towards adopting Godot as my go-to game making tool, avoiding rent-based proprietary platforms. I definitely recommend trying it out. I hope this has been useful to you. Cheers!