how do you play
On using Godot for Gardenia
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.
Disclaimer / scope
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.
Programming in GDScript
On the language
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.
_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.
Using the editor
OK, so here are some notes on parts of the workflow / use of the Godot editor tools.
Importing images and sounds
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!
Log in with itch.io to leave a comment.
Great post, thanks for sharing. I like Godot a lot from what I used.
You mentioned using a custom draw function to draw tiles instead of nodes. Did you benchmark this at all? I would expect gdscript would be a bottleneck when looping over a large number of items to do manual draw calls. Maybe a faster way would be to dynamically generate nodes so then looping and drawing can still be done by the engine?
Anyway, thanks for the post.
Hey, thanks for the comment!
I did benchmark a larger world (200x200 tiles) when deciding to go down the custom drawing route. To clarify, I didn’t re-implement the low-level drawing functions using native code or outside of the Godot engine: I just shifted from using AnimatedSprite nodes that rendered the tiles to drawing the sprite textures in a centralized way using the
_draw function. So, in a sense, this optimization was more about when/where to store and manage info than about what/how to draw at an API level.
The AnimatedSprite nodes were giving me 1) an overhead that I wasn’t using (of transforms, signals, animations management); 2) for some reason were not being camera-culled properly (which might be related me mis-using the camera / world system? I don’t know). So, I moved the tile drawing to one central class (the Map class), which handled camera culling, dirty flags, and the sprite-drawing/Y-sorting. A cool thing is that Godot automatically caches the
draw_texture if they are drawing the same thing at the same place, so this was even easier to setup.
uso o godot a tempos e ele é tão melhor pra tanta coisa. As questões de localização e de controle são muito boas. Inclusive, seria super simples criar controles diferentes pra aquela sua oficina de controles alternativos usando o Godot :)
Total! É muito legal de usar mesmo. Pra controles alternativos é bem legal, tem até suporte direto pra MIDI! Ainda não testei, mas tá na lista. E queria também ver como fazer pra usar ele com Arduino / Serial… Vamos ver! Abraço!!