Making 'Layered', My First Game
Published on April 17, 2025 by Caer
This April, I competed in Ludum Dare, a biannual event that challenges devs to make an entire game in a weekend. This is a log of how I made my first game, Layered, during Ludum Dare #57...with my own 2.5D Rust game engine.
Try Layered in the browser and share your feedback.
Record Scratch
You're probably wondering how I got here...
For me, making code that interacts with a world is so much fun: Over the past few years, I've visited the WPI Swarm Technology Lab several times to test my new data platform (Codas) on real robot swarms.
However, I don't always have access to a lab full of robots, and I don't love the developer experience of simulators (like ARGoS) or game engines (like Bevy).
So...earlier this year, I decided to make my own 2.5D game engine in Rust, building off of Macroquad and deeply integrating Codas. And what better way to test this engine than to enter Ludum Dare and make a game with it?
Concept: Deep Networks, Dense Layers
The theme for Ludum Dare #57 was depths.
Although there were more than a few developers talking about ocean and cavern-inspired concepts, a different concept popped into my mind: Deep Neural Networks. Deep neural networks are comprised of sequential layers of "neurons" (math functions), enabling them to power models like ChatGPT.
During the jam's first ~24 hours, I daydreamt at my local eatery while pondering how I could create a gameplay mechanic based on neural networks:
Could players take the role of a single "data" traversing the network?
What if the "data" activates different neurons in the network, similarly to how neurons are "activated" when neural networks train?
How does "data" move between the layers of the network?
Building off these ideas, I settled on the core mechanic: Physically connecting neurons in the layers of a neural network.
Note: The neurons within the same layer of a real neural network typically don't connect to one another directly, so this game concept isn't technically accurate...but
fun > everything_else
.
Although my art style (2.5D Axonometric) restricts the playing field to an X/Y coordinate system, I realized I could stack layers on top of each other, visualizing the flow of data across layers.
Note: "Axonometric" is a family of art styles including Isometric, Dimetric, and Trimetric projections. Although most 2.5D games are described as isometric, they're actually dimetric: While visually similar, dimetric cubes can conveniently be rendered in a 2:1 aspect ratio, whereas isometric cubes have a trickier 1.7:1 aspect ratio.
In essence, my concept was puzzles inspired by deep neural networks.
Making the Layers
Layers are the foundation of Layered. Each layer is a self-contained 2D puzzle, visually inspired by something unexpected: My Mac's screen saver.
Introduced in MacOS Sequoia, this screen saver renders Susan Kare's iconic pixel art on a living axonometric plane that animates individual pixels.
Pixel Design™
These animations gave me an idea: What if layers were pixel art? Fueled by this inspiration, I decided to design each layer using 16x16
pixel art:
Through a process I now cheekily call Pixel Design™, this art is converted by my engine into an in-game layer. Each color in the art defines what gets rendered in-game:
Orange pixels define puzzle checkpoints.
Blue pixels define threat emitters.
Magenta pixels define the player spawn point.
Dark pixels create wall or obstacle tiles.
All other pixels create empty floor tiles.
The pixel art above results in this rendered layer:
What I love about Pixel Design™ is that anyone who can open MS Paint (and friends) can quickly create new layers—no specialized tools needed.
Connecting the Network
To represent the connections between neurons in a single layer, I settled on the idea of checkpoints (the orange pixels in each layer's design).
The players' goal is to form a continuous connection between all the checkpoints in a layer, with a twist: The connection is interrupted by walls and obstacles.
To detect these collisions, I implemented a super naive version of 2D ray tracing based on Bresenham's Line Drawing Algorithm.
During each key frame, this algorithm finds all tiles which intersect a straight line between the player and the most recently reached checkpoint (we'll call these line_tiles
). Then, we iterate over all the tiles in order, starting from the tile furthest from the player:
If the tile contains an obstacle, we stop iterating and set
broken_line = true
.Otherwise, we recolor the tile to magenta.
If the player intersects a new checkpoint tile in the current frame and broken_line == false
, we flood-fill the checkpoint with magenta and cache the current line_tiles
, using them to draw connections between all previously reached checkpoints.
If no orange tiles (checkpoints) remain on the layer at the end of the frame, the layer is complete, and we transition to the start of the next layer.
Making Waves
After wrapping up the layers and connections mechanic, I wanted to add visual excitement and mechanical threat to Layered. I forget exactly what inspired me, but I settled on the idea of expanding waves:
During each key frame, and for each blue tile (threat origin) on the layer, I used the Midpoint Circle Algorithm to find all tiles which intersect a circle with a frame-dependent radius centered at the threat.
If the player intersects any blue tiles in the current frame, we reset the entire layer and transition to the start of the current layer.
Occluding the Wave Front
I was really happy with how the expanding wave turned out, but I quickly hit a snag: Waves would pass through obstacles, which didn't seem very nice, since neither the player nor connections can pass through them.
To solve this issue, during each frame, I use my naive ray tracing to find all tiles between each tile on the "wave front" and the threat origin. If any of these tiles contains an obstacle, that section (tile) of the wave front is marked as "occluded", and doesn't get rendered.
Though simple, this logic results in a fairly convincing illusion of sections of the wave being "deleted" or occluded by obstacles.
Polishing the Aesthetic
Satisfied with the mechanics I implemented, I spent the last few hours of the event polishing the visuals and audio.
My first pass of polish focused on scene transitions: Instead of abruptly cutting when the player finishes a layer or hits a threat, I added a simple fade-out/fade-in between layers. As a finishing touch, I color-code blank screen based on context:
When resetting a layer, the screen is magenta.
When hitting a threat, the screen is blue.
In all other cases, the screen is neutral-dark.
Finally, I needed some audio! My goal was for the entire game to be calm, meditative, and happy. But alas, I'm no audio engineer—so I outsourced some gentle music (by MooMarMouse) and cute sound effects (by JDWasabi).
Feedback
The Feedback from my fellow Ludum Dare developers was overwhelmingly positive. Consistently, players:
Enjoyed the puzzles and the way I designed the introductory layers to gradually introduce the player to each mechanic.
Loved the art and audio, commenting that it felt polished and relaxing.
Celebrated that I built Layered using my own Rust game engine.
The most common critique I received was that players wanted to keep playing, but there weren't enough layers. This critique is such a precious one to receive, and it's inspired me to keep developing Layered beyond the game jam. ♥
Reflections
As someone who spends most of their days building "invisible" software systems (think: the plumbing that holds cloud services together), it was so fun to make something as visual as a game.
At the start of the game jam, my "Little Rust engine that could" really only contained features for rendering and converting between world-space X/Y coordinates and screen-space axonometric coordinates. By the end of the jam, I'd extended the engine to:
Support Pixel Design™ by generating tilemaps from PNGs.
ec4354dc
Identify tiles intersecting a line between two coordinates.
8044ce61
Support cursor-based tile selection as an optional module.
ee774b4c
Support a state-machine for swapping between tilemaps.
d62eca9d
And for Layered specifically, I:
Implemented an option to make player motion relative to the viewport (screen) instead of the axonometric grid.
35e57e4d
Used tile colors as a virtual stigmergy for game state.
446b7fdd
Implemented pulsing wave emitters with ray-traced occlusion culling (I'm really happy with this one).
105a0fc3
Most glaringly, I didn't take the time to integrate codas
into the engine for state management or codas-flow
for frame-to-frame event handling.
Looking ahead, for both Layered and it's engine, I want to:
Decouple the game logic from the engine logic, cleaning up the various stylistic crimes I committed in the heat of the game jam.
Introduce the
codas
andcodas-flow
crates, using them to manage state transitions and frame-to-frame event handling.Decouple the Pixel Design™ functionality from the game's color palette so that it can be used in other game contexts.
Devise a visual for data flowing across completed layers, realizing my original concept of connecting an entire neural network.
Design many, many more layers for the game (and, perhaps, a "win condition" of some kind).
As a maker, my goal is to craft things that make others smile—and I think I can hone Layered into one of those things.
Did I miss anything or get a detail wrong? Let me know on Bluesky!
all views expressed are my own, and
don't reflect those of my clients or employers
made by me with rust
© With Caer, LLC 2025