Hey guys,
As some of you may have noticed lately, I’ve been working on a small animation app called SpriteLoop and, more recently, a Defold extension/runtime for it.
Why?
The initial idea was simple:
export a spritesheet or image sequence from the app, put it into a Defold tilesource/atlas, and use it with a normal sprite component for things like run cycles, idle animations, etc.
That works fine… until you forget your monitor resolution exists.
At home I work on a 4K monitor, and I often forget to resize the images I’m animating. That results in massive spritesheets.
For example:
- my robot test animation is authored on a 952x791 canvas
- 24 animation frames
- exported spritesheet size: 4760x3955
- around 8 MB on disk
So I added resize/export scaling to SpriteLoop.
Now I can export:
-
smaller spritesheets
-
custom aspect ratios like 4:3
-
predefined sizes like:
- 256x192
- 512x384
- etc.
That reduced the output to under 1 MB in some cases, and with proper preparation you can even fit animations nicely into atlas-friendly sizes like 1024x1024.
But…
this is a pretty big compromise on image quality if you want to support higher resolutions in your game.
Smaller textures on larger screens quickly become pixelated and unpleasant to look at.
The Rabbit Hole Begins
SpriteLoop was already working using separate character parts:
- head
- hands
- weapons
- body pieces
- etc.
You animate those parts by:
- moving them
- rotating them
- scaling them
- adjusting pivots/center points
The interesting thing is that those individual images are actually pretty small, even when the final assembled character is large.
So I decided to create a new export format.
Instead of exporting one huge spritesheet, SpriteLoop now exports:
- all individual PNG parts
- a JSON file describing animation frames
Example:
- where part X is during frame Y
- its transform
- rotation
- scale
- pivot
- visibility
- etc.
The format is basically just a ZIP file containing all images + JSON metadata.
For the same large robot animation described above:
- the exported runtime package is under 500 KB
- and that’s without aggressive PNG compression
At that point the problem became:
“Cool… but Defold can’t do anything with this ZIP file.”
Enter C++
So I decided to create a small C++ runtime library that can:
- load the package
- decode images
- parse animation data
- play the animation
With some help from my synthetic LLM friend™, I managed to create a small SDL preview application to verify that the whole thing actually worked.
My C/C++ experience mostly comes from university a very long time ago. I haven’t seriously touched it in 10+ years.
More recently I worked mostly with web tech and Ruby, and I vaguely remembered that every second Ruby gem seemed to compile something with CMake.
After researching build systems a bit, I ended up choosing Xmake because it felt more approachable for someone coming from web development, and it made cross-platform builds easier for Defold targets.
Learning Defold Extensions
The next step was learning how to create a Defold native extension.
The documentation was actually pretty clear for the basics, but my goal was more ambitious:
I wanted a proper custom component similar to the built-in sprite component.
Meaning:
- load my custom animation format
- visualize it directly in the editor/runtime
- eventually behave like a “real” Defold component
I couldn’t find much information about creating custom renderable components, but I knew the Spine and Rive extensions were doing something similar.
So once again, my synthetic LLM friend scanned those repositories and tried to teach me what was going on.
Honestly, for large parts of the process I was completely lost, so I mostly let it generate the extension skeleton and then I slowly reverse-engineered what was happening.
After enough back-and-forth — and probably one or two lakes drying next to the nearest datacenter — I finally had a working Defold extension rendering my ugly robot animation at runtime.
Witchcraft.
Performance Testing
Naturally, the web developer inside me — who almost never benchmarks JavaScript properly — immediately wanted to know:
“How bad is this compared to normal sprites?”
So I started building benchmark scenes.
I learned about:
- quads
- vertices
- render lists
- batching
- culling
- GPU submissions
- and many other things I previously ignored very successfully
My PC renders an empty Defold scene at around:
- 5000–6000 FPS
Adding:
- 100 SpriteLoop robots
cuts that roughly in half.
Surprisingly:
normal Defold sprites behaved similarly at those numbers.
One important detail:
each robot is still inside its own game object, which appears to add a pretty significant cost by itself.
Also:
the SpriteLoop runtime version uses much higher resolution textures than the sprite benchmark version.
Current Results
Right now my PC can render:
- 5000 animated robots at around 450 FPS
The equivalent sprite version:
- runs at around 650 FPS
Considering I’m completely new to low-level rendering and native engine work, I’m honestly pretty happy with that result.
On mobile:
- my 2020 high-end phone can render around 1600 runtime animations before dropping below 60 FPS in Web builds (the same with sprites)
Important note:
if I continuously update transforms for all objects, performance drops much harder (~230 FPS on desktop), so this definitely isn’t ideal for giant zombie armies constantly recalculating transforms every frame.
I also implemented basic culling:
- if 5000 objects exist
- but only 500 are visible
- invisible ones stop updating/submitting
That improved performance.
Another interesting thing:
the original giant spritesheet animation cannot even render properly on mobile due to atlas limitations.
Meanwhile the runtime version works because it generates smaller atlases internally from the individual robot parts.
So ironically:
the custom runtime handles large-resolution animations better than the classic spritesheet workflow in some cases.
Final Thoughts (For Now)
Originally I hoped for numbers closer to the famous Defold bunny benchmarks where tens of thousands of sprites are animated.
But after seeing how much overhead game objects themselves introduce — and after comparing against normal sprite-based implementations — I’m pretty satisfied with the results so far.
And honestly, I’m still learning most of this stuff as I go.
If anyone has:
- benchmark ideas
- optimization suggestions
- extension architecture advice
- or wants to test the extension on macOS/Linux/Windows later on
I’d really appreciate the feedback.
And if you’re wondering how I ended up in this rabbit hole…
This is what joining game jams does to people.
Be aware.
Links
Right now the extension supports:
- Windows
- Linux
- macOS
- Web (WASM)
SpriteLoop:
Defold extension:
Web Stress Test:


















