SpriteLoop + Defold Extension Development Log

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:

22 Likes

Great work, thank you for sharing this.

:rofl: Don’t worry, nature will eventually get rid of humanity and everything will go back to normal.

3 Likes

Impressive :heart_eyes:

3 Likes

Thank you guys!

In the excitement of writing the post, I completely forgot to add a proper thank-you section.

Even with all the modern tools available today, this project would not have been possible without the huge amount of open-source work already done by the Defold team and the community around it.

Thank you for building and maintaining all the things!

3 Likes

I am reading your code to try to understand how a full component extension is done, especially the editor preview part, but reading this spriteloop/editor/src/spriteloop-ext.clj is just a no for me :face_with_crossed_out_eyes: I can read cpp, even if I didnt write any code for 15 years, but who the hell invented Clojure needs to be interned :laughing:

you did a great work

4 Likes

I wouldn’t give myself credit for that particular file. :see_no_evil_monkey:

To be honest that was the most unpleasant part and building java plugins for it to work. But that’s skill issue. :sweat_smile:

It will be nice if the editor scripts or another feature allows to add custom components in more human friendly ways.

1 Like

Added skew animation, transform inheritance (including skew and scale), and improved loop interpolation for smoother repeating animations.

This makes it possible to use flat 2D images to fake simple perspective and isometric-style animations without requiring additional artwork. Thanks to skew and scale inheritance, effects such as rotating windmills, weather vanes, and other perspective-based animations are now much easier to create.

Interpolation between keyframes was already supported, but creating seamless loops required adding a duplicated keyframe at the end of the timeline. Now automatically interpolates from the last keyframe back to the first, making looping animations easier to set up.

10 Likes

Wow, this looks amazing! :sparkles:

4 Likes

Thanks,

Feel free to give it a try. Any feedback is appreciated!

The next roadmap item is implementing character skins and support for runtime part-swapping.

3 Likes

WIP: Basic skin support is now working in the editor app.


robotidle512 robotidleblue512

Also made some improvements to the export pipeline and atlas generation. The exporter now detects duplicate source images and stores them only once, reducing wasted texture space and export size.

Old vs New atlas:

In addition, atlas generation now supports image rotation during packing. The performance impact is generally small and highly dependent on the project. The example atlas is reduced from 4 MB (1024x1024) to 2 MB (1024x512) “texture bytes” which can win extra ~30 FPS with 5K objects.

The next step is part swapping inside Defold.

The editor-side skin system is useful for exporting different visual variants, but runtime swapping will make it much more flexible inside the engine. For example, a game could swap character parts, colors, outfits, or equipment without needing a separate exported animation for every combination.

I’m also considering some form of attachment points. The idea is to define named points in the animation, then use them inside Defold to attach weapons, tools, effects, or other items at runtime.

This would avoid having to animate every weapon or held item directly inside the app. Instead, the animation provides the socket/attachment position, and the game decides what object to attach there.

Just a few more features and I promise I’ll actually make a game. :grinning_face_with_smiling_eyes:

9 Likes

Excellent work. Looking forward to how you’re going to use this :grinning_face_with_smiling_eyes:

2 Likes

Yes THIS would be killing feature!
Plus runtime skinning, and you are closer and closer to a spine sibling! Congrats!
(Just add bones, IK, mesh deformation and events you are done :grin:)

I will try this new version for my game and give feedback, I am working with Krita for game assets, but animation is not that easy for cutout anim. This tool is the perfect companion.

1 Like

JFYI, the above (and below) changes are not live yet. I’m still working on the Defold side of the implementation.

Thanks! Maybe a distant cousin. :grinning_face_with_smiling_eyes:

Probably won’t have those. The whole concept was to not have bones, but never say never! :grin:

4 Likes

I am looking forward for the result!

Regarding this

If you implement it, I think the anchor point should also have an angle/rotation information - or a direction vector associated with it too. So that the animator can also design the orientation of the weapon at design time, then at runtime we can just attach sprites/game objects, but dont need to compute rotation for the animation. Maybe scale could be a good information too.

And maybe we could associate any metadata value to the animation and animate it (think color/tint for flashing stuffs, or parameters that will be passed to shaders). So you can design this data in the editor, for each frame, instead of building lua tables by hand. But this need a hole new api on defold side.

Just some ideas if you needed more work :grinning_face_with_smiling_eyes:

2 Likes

Version 0.7.0 is now available - Skins and Variants

Updated the README with the lua api for skins:

The web stress test is also updated:

I was planning to create a new character to properly showcase the skin system for the ScreenshotSaturday, but I didn’t quite manage to finish it in time.

6 Likes

Wouhou thanks I will test this!

I am curious: what’s you tech for the spriteloop editor itself?
Is it a Defold application (like Panthera editor I think it is) or C++ + gui system?

1 Like

The SpriteLoop editor is built as a Tauri app. Essentially Rust for the cross-platform OS integration and TypeScript/JavaScript with React and other front-end technologies for the UI. It’s very lightweight and allows me to leverage my existing web development experience.

The preview tool I use to share atlas screenshots is a simple C++ project built with xmake, SDL3, and ImGui. I also build the SDK/Lib that the Defold extension uses here, mainly for organizational purposes and so I can test features in the preview tool before integrating them into the extension.

4 Likes

Hello! This evening I am testing your tool, here is some takeaways, keep it or trash it, as you like :wink:
I am on Windows 10 for the record (yeah cant upgrade to windows 11.. need to go for linux now!)

  • You did a really great work! installation with no problems, the UI is great and simple. Importing parts, organizing them, moving/scaling/rotating etc. was really easy. For a first release this feels already polished
  • Took me some time to understand that you need to select a part to be able to add keyframes. The fact that you only see 1 timeline at the bottom was strange for me at first, because usually in animation tools you see all timelines at once (like one for each part). But it is totally usable like that, could be great to see all of them tho. Like to move all keyframes of many parts together.
  • The exports options are really complete, I tested spritesheet, image sequences, spla. Loved the many output size options, really useful.
    robotdogwalk
  • The guide frame is a bit hidden feature. I discovered it while exporting stuff. I dont grasp your use case about it, do we need to think about it as a camera? Like you create a big canvas to have space, you setup the guide frame as the target viewport, and then you can do animations that go out of view or something like that? What’s your use case? Anyways, it was usefull to export some part of animation. But I think it would be more usefull if you show the actual size of the frame, or if we can select it and set size in the inspector panel.
  • I didn’t understand how to use the parent-link feature…??
  • I had a hard time in order to move many parts at once in a key frame: if you select many parts together, then move them (like all my robot), it is ok in the frame you moved… but then when you go to next frame, you’ll have some parts moved (the one that didn’t had the first keyframe), but other parts that already had keyframes are still at the same place… so you broke your anim.
    • what I was trying to do was to animate my robot’s parts, them move all of it through my guide frame (like a travelling animation)… but could not do it …
    • how do you animate a group of parts? Is this the usecase for “parent-link” ?

Anyways, this will be a great tool!
Ok this is a first feedback, now I am trying the defold extension and lua API

4 Likes

Now for the Defold side:

  • your spriteloop.vp use a color vertex input, but your spriteloop.material doesn’t define a vertex attribute… this seems odd.. Why do you use this vertex attribute? what’s the input data?
  • I made a simple example with 2 animations:
local spriteloop = require "spriteloop.spriteloop"

local function animate(anim)
	spriteloop.play_anim("#spriteloop", anim, { loop = true })
end

local function animation_loop()
	animate("idle")
	timer.delay(5, false, function() 
		animate("walk")
		timer.delay(5, false, animation_loop )
	end)
	
end

function init(self)
	animation_loop()
	go.animate('.', 'scale', go.PLAYBACK_LOOP_PINGPONG, 3, go.EASING_INOUTSINE, 6)
	go.animate('.', 'position.x', go.PLAYBACK_LOOP_PINGPONG, (go.get_position('.')).x + 300, go.EASING_INOUTSINE, 5)
	go.animate('.', 'position.y', go.PLAYBACK_LOOP_PINGPONG, (go.get_position('.')).y + 200, go.EASING_INOUTSINE, 10)
end

This works!

Don’t have time right now to go further in Defold.

But using this extension made me think of some things:

  • as your .spla file contains the .png assets, does this mean you don’t use Defold atlas? So if I have all my sprites objects as different .spla files, this means it will be different textures loaded by Defold?
  • do you build a defold atlas at runtime inside your spriteloop component?
  • doesn’it affect performance to have multiple atlas… hence 1 by spla file? Or am I missing something ?
  • for defold especially, maybe you should add the possibility to just add all the image parts to an atlas, then the spla file only contains references to assets names. So we can pack all our assets parts into one big atlas. Then in the component we must set the atlas + the spla file (for animation data). Then at runtime you use the texture info from existing atlas. This seems a bit clunky, but at scale if I want to use spriteloop component for all my characters, this makes sense to me…?
  • I am also thinking that the .spla format is kind of duplicating my .png files - for example if I want to use some image parts also in another component (sprite, gui box…) I will duplicate .png file in the bundle.

Maybe you could have a “packaged spla format” like now, and a “externalized spla format” which is just a json file with data and only references to .png assets or atlas animation names.
This could be true also for the .spriteloop project file.
I would prefer to not have duplicating .png files into this file. I prefer have a directory tree with all my images. Then I just use them in SpriteLoop editor and then I just add them to Defold atlas.
So next time I need to change some images, I just overwrite them (like batch export all my layers from Krita), then everything is up to date everywhere. No need to reexporting animations or stuffs.

Some thoughts about the process of using this tool.

2 Likes

Hello! Thank you for the awesome test and feedback! Nice animation you have there! Let me grab my coffee and answer some questions. :face_with_hand_over_mouth:

First of all, a small disclaimer: SpriteLoop is very, very opinionated software. It doesn’t always follow industry standards. Instead, it follows workflows that make sense to me, which may not always be the best approach for everyone.

Timeline

I was very focused on the character creation workflow, where you work with each individual part, so a single timeline made sense to me. The whole workflow currently revolves around “parent links,” and once your setup is complete, you only need a few keyframes on a few key parts.

In traditional animation, you often need to keyframe every part when you move it. In a rig-based workflow, however, you move all connected parts together. The goal here was simplicity from the beginning. Dopesheet timelines are more powerful, but they are also more complex to manage and, for the most part, take up additional screen space.

Guide Frame

I originally added this feature when I wanted to export spritesheets and GIFs with a more compact size.

The idea is that you can work on a very large canvas (which can be resized by dragging the corners and edges) so you have more room to animate or organize parts. If you export the entire canvas, however, the resulting spritesheet can become unnecessarily large.

For example, if you’re working on an older laptop with a 1366×768 display and your canvas is close to that size, every frame in the exported spritesheet will also be that size. This creates images that are not very atlas-friendly.

The guide frame acts like a crop area. You can think of it as a camera, although it cannot be animated. It allows you to work with common aspect ratios such as 1:1, 4:3, or 16:9, which can then be exported at atlas-friendly sizes like 1024×512 after scaling.

Alternatively, you could resize and reposition everything before export, but that workflow was a bit finicky in the early versions. Nowadays, you can simply select all parts and move them where needed, so the guide frame may be somewhat redundant. I’m still evaluating whether it remains useful enough to keep.

Parent Links

This is my stubborn alternative to the industry-standard bone system.

Let’s say you have an upper arm, lower arm, and hand. You use a parent link to connect the hand to the lower arm and the lower arm to the upper arm. Once linked, selecting and moving the upper arm will move everything together.

You can also animate the entire chain by adding keyframes only to the upper arm, which makes posing much faster.

This becomes especially useful when building a full character rig. Once all limbs are linked to the body, you only need to keyframe the body when moving the entire character around. You only need additional keyframes on the arms, hands, legs, etc. when those specific parts are changing pose.

I probably need to create a tutorial soon, as this is likely why you had a hard time moving and animating everything.

In general, SpriteLoop is primarily focused on creating in-place character animations, such as walk cycles, run cycles, idle animations, and similar loops. The expectation is that the game engine will handle the actual movement of the character through the world. You can absolutely animate characters moving across the screen, but parent links become much more important for that workflow.

To be continue…

1 Like