Building Spearfishing Leviathan
NOTE: The original version of this postmortem can be found on the itch.io page here.
Hi there. I’ve made a new game.
Spearfishing Leviathan was a project that was created with a deliberately tight timeline for me to explore something in game / mechanics design. In this case, 5 days, for Kaiju Jam…I’ll admit I chose the jam mostly because the timeline matched the timeline I wanted to use for this project, though the themes did help give inspiration.
It occurs to me I should describe the game a little. Spearfishing Leviathan is a short platformer, where your goal is to, well, spearfish Leviathan (in this incarnation, a giant shrimp dragon). Broad inspirations included the 2D Metroid games, as well as Megaman ZX. I was especially inspired by the underwater movement / combat of ZX, and thus decided that this project would also focus on water.
I made it because it’d been 4 months since I worked on a game and apparently that was too long for me. I made it because I’d participated in GMTK a few times now working on platformers, but I’d never actually designed (or coded!) a platformer. In the end, I don’t know if I actually made something ‘finished’: the game runs from beginning to end, but the frame is missing. There’s no music, or title screen, or ending screen, and I had less time to playtest and fine-tune mechanics than I wanted. At the same time, I did create an entire platformer by myself in 5 days when I started with absolutely no idea what to do. All in all, I consider this project a success, though there was a lot I wish had gone better just on the development side.
The rest of this article is mostly me musing on technological decisions. I call it a post-mortem, but perhaps a premature one, since the dust hasn’t settled yet and I haven’t actually seen too many people besides me play it to see how good my design decisions were. So, this will be about the tech. I made Spearfishing Leviathan on an M1 Macbook, using Godot 4.0 (upgrading to Godot 4.02), and the logic was written in C#.
What went well
It did what I wanted it to
I had several goals for this game that I accomplished well:
- Program a platformer for the first time
- Understand how to make a C# game in Godot
- Understand how 2D skeletal animation works
Platforming in Godot
The Godot engine is a pleasure to use for platformers. The built-in physics and character controller are already fine tuned to work well with platformers, and slope detection and support is available out-of-the-box. The collision system was also intuitive for me, and I was able to use Godot’s Area2D nodes for a wide variety of applications, from attack hitboxes to triggers to pickups to even detecting when the player was in water.
In any case, it took very little time to make a little platformer. I had no illusion of knowing how to make a platformer feel good, though, so I used https://gmtk.itch.io/platformer-toolkit to do some experimenting. The Platformer Toolkit also has some reference code for use in Unity, which implements common features like coyote time and jump buffering. I based some of my architecture off of that.
This was where C# worked well with me: both because I could port over the code from Platformer Toolkit directly to my game, and because it was more comfortable for me to reason with. Using GDScript within Godot encourages you to put all your logic into nodes. With C#, I was more easily able to create pure classes that interacted with but weren’t attached to Godot nodes, which I used for splitting off behaviors into multiple scripts (the _Process function of the player got scary quick with all the swim + dash states), and creating static utilities.
The water system required two nodes per room: an Area2D to determine which areas were underwater, and a Node2D to draw the water surface and generate the springs for the splashes. There isn’t a node that lets you draw rectangles in the editor without assuming it’s used for collisions or UI, so I added a Control node to let the script know which areas should be drawn with the water shader.
I think the free-movement underwater was really fun to play with. (And the feature I got the most positive feedback about from my friends.) Water in platformers tends to be an obstacle or a totally different control scheme, and I wanted the player to feel more powerful in the water, rather than less. I think that part turned out well, especially with:
- the extra velocity upon ‘jumping’ out of the water
- the ability to switch in and out of floating mode, to either maneuver in the water, or sink more quickly than you could otherwise.
- All the water effects and sounds
I might be biased since I know exactly how the enemy patterns worked, but there was also something fun about dashing around Leviathan to stab at its underside. It’s a little underdeveloped, but it’s interesting, which might be worth expanding on in the future.
I typically use pixel art when making jam games, because it tends to be the fastest to create and animate. This time, I decided to draw hi-res pieces for the characters, and then use Godot’s 2D Skeleton system to handle animations. The authoring process for these images worked very smoothly; I had a little experience with both 2D and 3D skeletons before, so knew how to split up the images. I drew all the images on Procreate on my iPad, and was able to import them and author the skeletons without any issues in Godot. Their polygon and bone editing tools are pretty good!
In the end, I was able to create full animations for the player, Leviathan, and two enemies.
What went okay
As it turns out, I’m not very fond of designing levels…and so I procrastinated on it for perhaps longer than I should’ve. By the time I started with level design, I had ~24 hours left and so had to rush through making all of them.
Originally, this was meant to be a Metroidvania, so I spent some time building a room transition system, suitable for loading nearby rooms and unloading them. This was inspired loosely from principles described in https://github.com/Yogoda/ZoneLoadingSystem. Which worked, with some awkwardness: Godot doesn’t allow you to create instances of scenes that are already instantiated, and thus Zones + Rooms were awkward to work in since all the children needed to be edited. The whole system isn’t as useful here as it would be in a game where there’s actual backtracking, but it was a nice system to get up and running.
One less good thing was that the editor started to lag when I looked at the whole world map, which made level editing more tedious. Fortunately, it appears to run fine in game with the loading + unloading of levels.
The asset pipeline for making 2D skeletons in Godot was very lovely, but the actual keyframe animation process was a little rough around the edges. The onion skin view is currently disabled in Godot, which was slightly annoying to work around. Much more annoying is the fact that there’s no way to select all the keyframes in a single frame, which made it very difficult to change whole frame timings or sweeping modifications. (This is also a requested feature within the animation editor, so I know I’m not alone in wanting this).
Inverse Kinematics has also changed a lot from Godot 3.x to 4.0…and I couldn’t get it to work, specifically the limbs never pointed towards the IK target. It’s either broken, or there’s some configuration that I never got right.
The water splashing effects were pretty heavily inspired from https://pixelipy.itch.io/water-2d-simulation, and uses spring physics to produce waves. The end result turned out well, with some heavy porting both from Godot 3 to 4 and from GDScript to C#.
What went less well was simply how much time it took; the water ate up a whole day in a 5 day project, which was time that probably should’ve been used to continue prototyping and refining the Leviathan battle.
I didn’t have much time to add sounds when everything else had wrapped up, which meant that I could either write some music, or implement sound effects, not both. I decided to add sound effects because I think them more important than music, especially in a more fast-paced game. The water sounds were especially impactful on implementation, and I’m pretty proud with how Leviathan’s sounds were implemented; most of its roars and movements were taken from samples of cars and other large machines.
All the sounds I used were from free sound packs, and I wound up creating a very spaghetti manager to play them in Godot. The manager was loosely based off of https://kidscancode.org/godot_recipes/3.x/audio/audio_manager/, with some extra functionality to set volume. I’m not sure what the most organized way of dealing with sounds is! But this was functional.
Anyway, I’m satisfied with the actual SFX used. The code makes me cringe.
What went badly
Building, in General
Godot 4.0 with C# does not support web builds. Which I didn’t know before starting this project, oops.
This meant that the only builds I could make were for desktop! …which is ungood for a game jam, when you want to be able to play and review games quickly.
Also, there was another problem with the desktop builds…
The Mac build, uncompressed, is 300mb. The Windows build is roughly 70mb. These are very high numbers for a short jam game. I did have some high-res artwork, but all the assets including artwork and sounds was less than 10mb. So all this extra filesize was coming from somewhere else.
On Mac, when I looked through the contents of the app, the C# and internal Godot modules I was using created two data folders, one for x86 architectures and one for arm64. Both those module folders were 70mb, so there’s a lot of extra stuff there! In addition, building an empty project (scene with single Node2D, no scripts) on my M1 Mac using Godot 4.02 produced a 67mb .exe and a 137mb .app file. Which suggests that, short of zipping and rebuilding the whole engine, there wasn’t much else I could do to make a smaller build.
There’s a tutorial on optimizing an engine build for size, which can then be used to export your game, but I didn’t have the time in a short jam setting to really dive into that this time, and I’m not sure I’d want to spend the time when it’s just as likely I’ll be using a different version of Godot for the next project I use it for.
Either way, it makes me hesitant to use Godot right now for game jams, because it’s bulky, and in a jam environment I won’t have the ability to take the time to really optimize the build to a form I like it in. Also, the web build problem is a pretty big one. Web versions are pretty important for jam games, because they’re short and it’s more important that people can play and review them very quickly. Which, then, is probably related to the next problem: Playtesting
A combination of the large build sizes, tight timeline, and inability to build for web (or test on Windows, given that I had access to macs only in this time period) meant that there was very little playtesting performed before submission. I sent it to some friends to verify that it worked, but people were reluctant to try a game that was so cumbersome to play.
As a result, I still am not sure that Leviathan is in any way a balanced game, and I wonder how fair it can be when I haven’t seen how other people react to the attack patterns. It’s definitely beatable, since I can beat it, but that doesn’t mean much when I programmed it!
In terms of understanding platformers and gaining more experience with Godot, this project was a resounding success.
In terms of making a good game…I think the results are more mixed. It’s playable, it’s a mostly complete experience, and I had fun testing and going swimming through the rooms, but I couldn’t verify how it felt for anyone else. Also, the builds are ridiculous and I still haven’t figured out how to make them smaller.
I consider Spearfishing Leviathan to be complete, as it was meant to be a sketch, and it does that. I think it’s worth playing, especially for its water mechanics. Leviathan is very beatable, if challenging. And that’s all it is.
I’ll add a web build if / when Godot supports web builds for C# games. I’ll fix any bugs that come up and that I’m made aware of. Beyond that, this is done. I’d be thrilled if you play it, and doubly so if you have any feedback for me or the game.