Hey! Long time follower, first time poster.
Recently submitted my first solo entry for the #MadeWithDefold Jam 2025 (Thanks @Pawel for organizing it, had lots of fun!) and a few people asked how the mechanic was made. Since the forum proved to be such a tremendous source of knowledge for me, I figured I might as well share my process here for anyone else who might find it interesting.
The Game
For a while I had this idea of a Tetris-like game where instead of the usual tiles, players need to stack random physics-enabled blobs of color and create clusters that span across the board. This year’s theme “Twist on a Classic” was the perfect excuse to try that mechanic and see how it goes.
Check out SPATTRIS by leshido
Development
Technically, I guess there are 2 aspects to this merging mechanic that are worth explaining: the visual “blobby” shader, and the physics based logic. There’s also the GameBoy postprocess effect applied which might not be directly related to the mechanic but I think helps tie everything together nicely.
So, let’s delve in.
Physics
Each blob the game drops is actually made of 4 separate “ball” game objects. The balls have 2 collision objects, one of type Dynamic to control their movement and collisions, and one slightly bigger of type Trigger used in the linking process. Each trigger is assigned a certain physics group and mask, corresponding to the different blob colors. Once a trigger event fires, I create_joint
on the 2 colliding objects to constrain their movements with a “rope”, essentially gluing them together. I also keep track of these links in a table and use it later to check each resulting cluster to see if it touches both walls.
Visuals
The blobbing effect involves a custom render script and materials. Each ball is assigned a sprite with a radial gradient in one of the three RGB primary colors. The sprites are configured to use the “screen” blending mode and a material which for the most part is an exact copy of the builtin sprite material, except it uses a different tag. Instead of the default “tile” rendering predicate, they’ll be rendered at an earlier pass to an intermediate render target. I won’t bother with the details on how to achieve this, but I can point to the Post Processing example project which for me was a very clear and concise introduction to rendering targets. The relevant code in the update()
function looks like this:
-- Draw blobs to another canvas
render.enable_state(graphics.STATE_BLEND)
render.set_render_target(self["rt_blobs"])
render.clear({[render.BUFFER_COLOR_BIT] = vmath.vector4(0, 0, 0, 0)})
render.draw(predicates["blob"])
render.set_render_target(self["rt_world"])
render.disable_state(graphics.STATE_BLEND)
NOTE: “rt_blobs” and “rt_world” are render targets created in the init() phase
With the balls all rendered to an off-screen texture, a model is used to actually display them. This is handled by the model’s material, in which a glsgl step()
function is used to limit the values of the gradients and create the signature metaball/blobby effect. Because the balls can only ever be either Red, Green or Blue, the step function essentially joins them in 3 distinct groups. Something like this:
mediump vec4 tex_sample = texture(texture_sampler, var_texcoord0.xy);
mediump vec4 stepped_sample = step(0.5, tex_sample);
out_fragColor = stepped_sample;
Finally, I took @MasterMind’s Gradient Map sample project as reference and modified it to be used as a full screen postprocess effect. Combining it with a stepped 4 color “gradient” instantly made everything feel more gameboy-y. It also allowed me to work simply in greyscale for the entire game’s assets, knowing the coloring will be handled by the shader.
Conclusion
That’s it! Thank you for reading, I hope this was a helpful breakdown. It was my first time using Defold for anything more than simple prototypes and I learned a lot doing so. Highly recommend giving the Jam a try next year