Defold: The Good, The Bad, The Ugly
I’m experimenting with new technology to help me develop games faster. I want to use a visual editor to organize assets in the game world, develop animations and collision objects visually, and generally skip the framework code I find myself writing every time inspiration strikes.
In short: I’m shopping for a game engine! Unfortunately, I’m also a picky eater.
Defold?
Defold is an engine for 2D and 3D games originally developed by King (think Candy Crush Saga), then transferred to the independently registered Defold Foundation. It doesn’t appear to be anywhere near as popular as Unity, Unreal, or even Godot, but I thought it had some truly unique features - enough so that I gave it a serious shot as my first foray into game engines.
After completing three tutorials included with the engine as template projects, I watched an introductory YouTube series on Defold presented by David Chadwick. This was a world-class on-boarding, and I highly recommend it if you’re interested in learning Defold.
This article is my personal review of Defold after a week of full-time usage.
The good parts are nirvana. The ugly parts are deplorable. Let’s do this.
The Good
The ECS
I am a huge fan of using an entity-component system (ECS) for games. I find that it separates concerns incredibly well, letting complexity grow linearly with the size of the project, rather than exploding exponentially. Guess what? Defold’s architecture is an ECS. The entities are called “game objects” and components are called just that.
Once I internalized the mapping between Defold’s concepts and my previous ECS experience, editor interactions became extremely straightforward. The other Defold constructs, mainly collections and factories, are natural extensions of operations a game would expect on top of its entities: grouping them together, instantiating them, moving them, and so on. In total, I believe the components are absurdly well designed.
This is the first time I have ever opened up a game engine’s editor and understood how to accomplish my task. Without a doubt, the ECS is to thank.
The Messaging System
Defold features a messaging system in which any game object may post messages to any other game object. With great power comes… yeah, yeah. But look. The worst feeling when you’re deep in a project is when you realize you need to break your own abstraction because you failed to properly decouple a handful of layers.
In Defold, all game objects are essentially independent at the code level, but they can communicate using messages with properties. Each game object has an address, so as long as you know where the target lives, you can talk to it freely. It also means you’re extra careful to avoid assumptions about who is sending you a message, or even why.
I think this is all rather brilliant. It forces your game objects to live in the same addressable namespace, no matter the scale. Ultimately a speck of dust under a rock could message your character’s nose for chance to initiate a sneeze. Defold will not get in your way when messages need to come from new and unforeseen features, you just need to act a little responsibly.
The Bad
The Script Editor
Once you’ve set up all the game objects in the editor, it’s time to make the actual game happen. That’s where the script editor comes in.
My script editor consistently failed to show in-line documentation for global Defold functions, much less Lua functions, on hover, highlight, or prompt. I was driving blind and I didn’t like it. I couldn’t adjust the font size, I missed my usual multi-line editing commands, and input lagged quite frequently.
About an hour in, I bit the bullet and set up a VSCode environment that had everything I needed. Much, much better. As it stands, that script editor is definitely not worth using.
I don’t actually have anything else in the “bad” category, so it’s time to get juicy!
The Ugly
Lua
I have written a few lines of Lua here and there over the years, so I knew a smidgen. However, I was unpleasantly surprised.
My primary issue with the language at this time is the lack of expressiveness for tables. Insert and delete operations only operate on numeric indices. Attempting to use it as a queue naively is incredibly inefficient; you’d be better off manually managing a linked list of tables instead. And it’s not well-defined to compute the size, due to some features allowing the null value to be a key or value.
Guys, it’s 2023! I fully expect a formidable arsenal of performant operations implementing all basic data structures in the standard library. I’m not going to store an extra count
variable and manage it every time I add or remove something from the table - you do it! Tables are Lua’s bread-and-butter, so why are they so hard to use concisely and efficiently?
Putting tables aside, Lua seems to do various things a little differently from C or Java: indexes start with one, comment syntax is different, we got some of that Ruby-like then
-end
bullshit running around, except for that one keyword that demands a do
. I’m just not sure any of these modifications serve a higher purpose.
Either way, I don’t see myself writing a big project singularly focused on such an unexpressive data structure, especially not in a dynamically-typed language.
All Strings Must Be Hashed
And here’s the nail in the coffin.
The scripting API in Defold has a limitation in which values sent in messages or used as arguments to construct game objects cannot be strings. You read that right. Instead, you must call a hash
function to obtain an integer every time you want to pass a string through Defold. Naturally, you must then use hash
to compare against it on the other side as well.
To recap, if you want to pass a string, you must:
- Declare the string somewhere globally, keyed by the hash of that string.
- Pass the hashed string through Defold’s API (ie. a message or factory).
- Compare the hashed string with the same hashed string on the other side.
- Finally, look up the actual string in the global location.
This solution litters the global namespace and pollutes your cognitive load with unautomatable type conversion requirements that only surface at runtime.
Say it with me: what the fuck?
I tried to write Snake in Defold and ran into this issue multiple times already. The game is only 500 lines of code to begin with, GUI and all. I can’t imagine the monumental struggle for a game of any real complexity.
Here’s the relevant discussion on the issue. Several other users have brought up the fact that this nukes Defold’s usability from orbit. The maintainer’s reasoning is surrounding the performance characteristics of allowing strings into the game engine. They might be right, as strings can absolutely be dangerous to space, memory, and compute resources.
You know what else is dangerous? Having a game engine where you can’t pass text in a message, spawning monstrous amounts of extraneous redirection.
Conclusion
I went the extra mile and re-implemented my Snake game in Haxe, allowing me to completely bypass my issues with Lua while sacrificing any semblance of live debugging. I was preparing to write a general mechanism to automatically translate strings to hashes and back implicitly… and then the corner of my left eye started twitching involuntarily.
All that work just to make it semantically palatable? We can do better.
I’m thinking no other engine even comes close when it comes to the simplicity of the ECS or the messaging system. Now I have a gold standard to look down condescendingly on the other engines as well. “Unity: The Ugly, The Ugly, The Ugly” coming soon to a blog near you.
The drawbacks were just too much. I really wish Defold was the right choice for me, but I’m going to give the other engines a chance.