Post mortem/Making of
(This is a mirror of the same blog post on my website.)
Alakajam! 21 was held February 21st – 23rd, 2025. The theme was “Gravity”, and I made waterfall-1, a point ’n click game.
Theme reveal stream
Being back in Zurich for the jam and the preceding weeks meant we were finally able to continue the tradition of Alakajam! theme reveal streams. The last one we did was AKJ 16. I always enjoyed this aspect of the community and wanted to look into giving the stream a new look. Coincidentally, I’ve been rewriting Danae’s chatbot and overlay in Rust, and I’ve also been trying some 3D modeling with Blender lately.
So the idea was born to create a 3D studio! Not a 2D picture of a 3D render (as in the previous version you can see in the linked video), but actually 3D so that we could switch camera angles etc. And, though still quite simple, here it is:
Of course, just making the scene wouldn’t be useful without a way to render it in the stream overlay. I used my Rust engine (source publication still pending) which is predominantly meant for 2D games, but all it takes is a shader and a way to get the geometry data! For the latter I used the gltf crate, having exported the Blender scene as a .glb
file. Long story short, here’s the final result.
Having written a gltf
importer and having tested a 3D render workflow, I was keen on using it for my game as well, ideally in a more pixel-arty style closer to some of my previous games.
Brainstorming and sketching
Bit tired from preparing for the stream (because of the definitely unavoidable crunch to finish things just before the stream starts), I used the first evening mainly to think a bit about the theme and sketch things out on paper. I didn’t especially like the theme “Gravity” and I didn’t want to do any of the more straightforward implementations of the theme, related to orbital mechanics or platforming with reversible gravity. There is also the phrase “gravity of the situation”, but that didn’t inspire me too much, either. Finally, I think I just wanted to do a point ’n click adventure(*1).
So I settled on trying to tell a story set on a space station, that happened to be a station monitoring “gravity flares” (not something that actually exists as far as I know) in the Sun. Given enough time, I might figure out how to properly connect the story to the theme(*2).
Here is the sketch page, including the space station layout at the bottom, most of which made it into the game in this form.
Recreating the station in Blender
I quickly transferred the exterior layout of the station (as seen from one of the four sides) into aseprite, then started recreating the geometry in Blender. The squares of the grid paper seen above ended up corresponding to 16x16 pixel blocks in the texture.
It helped a lot to set up an orthographic camera in Blender, which faced the station geometry that I was building, as well as a plane further back which had the aseprite texture on it. This way I could regularly make sure everything lines up neatly by checking what the camera sees.
Later I needed to actually UV map the texture to the geometry, rather than just use the texture as a reference in the background. The orthographic camera setup helped a lot here: when it was positioned and scaled exactly right (the render size set equal to the texture size, and the camera offset so that the space station is e.g. in the top left corner of the viewport), I could simply use Blender’s Project from View operation. For the selected faces, this placed them exactly where they should be on the texture, avoiding the hassle of any manual UV editing or making sure the UV coordinates are snapped to pixel boundaries, etc.
The workflow for each side of the station was then roughly:
- draw the station from that side in aseprite;
- set up an orthographic camera to see the station from that side;
- select some of the faces of the station;
- project from view;
- edit texture in aseprite and save;
- reload texture in Blender;
- go back to 5 until happy.
With the exterior of the station being octagonal, there were 4 “diagonal” sides which were seen from two camera angles. These were drawn just once in aseprite: for example, the “front” has the diagonals that are also seen from “above” and “below”.
I was reasonably happy about the workflow, though there were still some operations that took a bit too long. For example, creating the little greebles that would stick out into the interior, making the walls look a bit less flat when turning, was a slow-ish process that I had to repeat multiple times.
Interactivity
Having imported the model into the game (more or less using the same process as for the theme reveal stream), and adding some basic turning mechanics to see the station from different angles, it was time to start making more of a point ’n click game.
I realised early that animating the player character moving around the station even as the station is freely spinning around, would be quite painful. For full motion I would expect sprites of the player from each angle, some kind of “push” movement to leave a handhold, and a “grab” to catch a handhold… It might have been fun, but also a lot to draw and animate. Instead, I cheated a little bit: the player character is only visible from one side, fading out if the camera turns away, and the character “teleports” from one location to another, or from one pose to another. There is only a subtle animation for bobbing up and down (to sell the zero-G better I guess) and the head can flip and bob independently of the body, to animate speech.
The only unusual part in a point ’n click game that is rendered in pseudo 3D is figuring out what the player is actually clicking on. For 2D games this is easy: things are rectangles, and a point-against-rectangle collision on screen is trivial to check. In 3D, there is the added complication of a camera projection mapping the triangles of the model to triangles on the screen (usually somewhere in the shader). One could project all the triangles of the model through the same projection on the CPU, then see if any of those end up intersecting with the mouse coordinates, and also figure out which triangles occlude which others… But another option that is less CPU-heavy is a cryptomatte(*3)!
This is a technique which involves rendering the geometry in two passes: once, to produce an image (the “cryptomatte”) that doesn’t show actual colours, but instead renders each triangle with a colour that corresponds to an object ID. For example, if there are three interactive objects on screen, their triangles might be rendered with the RGB colours (1, 0, 0)
, (2, 0, 0)
, and (3, 0, 0)
, respectively. This pass is not shown on screen, but is rendered into a framebuffer, which the game can query for exactly one pixel: the pixel at the mouse coordinates. The RGB colours are then mapped back to an object ID.
The full cryptomatte is kept in a framebuffer because it also makes another postprocessing effect much easier: an outline of the hovered object. The shader is given the ID of the object to highlight and looks for pixels where the cryptomatte does not show that object, but one of the four neighbouring pixels does.
Story
The last day of the jam was spent actually making a “story” happen. There isn’t that much to say here: it’s a fairly linear progression with some “optional” interactibles you can click on. Unlike some of my previous games, the actual plot progression isn’t driven by coroutines or anything fancy, it’s just a queue of plot “steps”. The queue can be empty, which means the player is free to move around and find another interactible, or it can have some steps in progress. The first steps is checked every frame until it is “done”, which means something different for every step kind: a Wait(n)
step needs to be ticked n
times, a MovePlayer(to)
step waits for the player to actually be at the target location (waiting for the fade animation to finish), etc.
enum PlotStep {
Say(&'static str, &'static str, f32), // who, what, speed modifier
Interior(bool),
Fade(bool),
Wait(u32),
TurnTo(i32, bool), // where, instant?
MovePlayer(&'static str, bool), // where, instant?
PlayerFrames(Option<usize>, Option<usize>),
FinishMoving,
AlarmFlash(bool), // true = elec
Hatch(bool),
HabAim(bool),
LaunchHab,
// TODO: Sound
// TODO: Shake
// TODO: Fade
}
When an interactive object is clicked, the game simply calls a function associated with that object that returns a list of plot actions. The function may return different lists over the course of the game, with the plot progression overall being summarised in a global variable.
enum PlotPoint {
Start,
ContactedMC,
InvestigatedAFirst,
InvestigatedBFirst,
AnomalyFound,
ManualRead,
UnitReset,
ComputerUseless,
ShouldLaunchHab,
HabLaunched,
// ...
}
thread_local! {
static POINT: Cell<PlotPoint> = Cell::new(PlotPoint::Start);
}
I also decided at some point to have some “gravity” in the game, as a treat.
(17:58, aka 2 hours and 2 minutes before the end of the jam)
This wasn’t much, just a tiny gravity sim to shoot one thing from the station onto a target (the Earth). The sim works… well enough, and it recognises whether the player aimed the shot successfully or not. Then it does nothing with that information! No time and all that.
Conclusion
Overall, I’m happy with the entry, though I do wish it were longer and more connected to the theme. It’s not the first time I wish I had a third day after an Alakajam!, I guess I am rather used to the Ludum Dare jam format. I will continue exploring the Blender/aseprite/pseudo-3D workflow in the future. I think it would have made me much more productive in some past entries (two tapes or improbability engine come to mind) where I created all the 3D geometry in code.
Food log
Footnotes
(*1): Yes, I do them a lot, I know! It is simply a genre I enjoy a lot, and from my own games, I am quite proud of the point ’n clicks ones (like two tapes, The Last Scholar, Primary Objective).
(*2): Surprise! There wasn’t enough time.
(*3): I only learned the name for this technique recently, though I used it in some of my games before.
waterfall-1
Status | Prototype |
Author | Aurel |
Genre | Adventure, Visual Novel |
Tags | 2D, Atmospheric, No AI, Pixel Art, Singleplayer |
Leave a comment
Log in with itch.io to leave a comment.