Fangs and Zonkery - Post-jam postmortem



Hey, Bhijn and Myr here! (for reference, for those who don’t know us: we’re plural!)
The longer a game jam is, the more interesting a story you tend get out of individual submissions.
This is the story of Fangs and Zonkery, a prototype tech demo we’ve created for Strawberry Jam 9! Strawberry Jam is a series of month-long jams. It’s an NSFW jam, and has an emphasis on embracing the weird and kinky.
A few of our friends participated in last year’s Strawberry Jam, and exploring the games to come out of SJ8 lead us to making a decision back then: we’d be participating in SJ9.
We come from a very programming-centric background. We have a lot of experience working with weird langauges, and writing backend code to support the technologically complex features that we like writing. We have a bit of experience dabbling in other forms of game-relevant art, particularly mapping, texturing, and hard-surface modelling. We have a little bit of experience dabbling in audio design. However, what we don’t have is experience with character modeling, music, and animation.
Jammed Strawb Prelude
So this year’s Strawberry Jam had a unique twist: it had a theme! Specifically, hypnosis.
This immediately gave us quite a few ideas. A hypnotic rhythm game, a hypno-themed kart racer, a game about being a vampire’s thrall, a meta-horror game that pretends to be a virus that zonks you…
Suddenly, inspiration crashed into us like the average first turn of a simracing event.
We had been playing plenty of VR games around the time the theme was announced. VR games tend to be very physics-y by necessity, as players tend to expect to be able to intuitively interact with anything that moves, and tend to expect to be able to grab anything that looks like it isn’t nailed down.
Additionally, Godot 4.4’s beta recently released, introducing major improvements to physics (Jolt physics got a bunch of fixes, and was made the default physics engine for 3d), and major fixes for long-standing VR bugs!
The third piece of the puzzle: that third idea. What if the script were flipped? What if you were a vampire, seeking to feast upon and enthrall mortals?
The vision was clear. We knew our purpose. We wanted to make a VR vampire simulator.
So we got to work on writing a design doc. We defined a solid scope for what would be considered complete for the jam, and ended up cutting things down until they’d be reasonably manageable from us in what we estimated to be 2 weeks worth of time. The design doc was finalized on January 20th. (That design doc is available in the download of the jam build of Fangs and Zonkery, as DesignDoc.md
!)
One of the sacrifices we had to make was ultimately declaring VR support optional for the game to be considered complete. As we know from making content for Bonelab and VRChat, testing things in an actual VR headset tends to be a trial of patience, and can easily balloon the overall development time of something.
With the design doc written, we sat down and started working on getting the most annoying things about starting a Godot project out of the way. We configured the project settings the way we like, we imported a few libraries from our earlier projects (specifically, the scene manager and player manager), and imported our rudimentary quake-like player controller from an earlier project (which is very bare-bones compared to what made it in the jam build).
We additionally did a bit of smoke testing for func-godot (the importer for quake/valve-format .map files), as we were insistent on using Godot 4.4 despite its beta state. Thankfully, it worked out of the box!
Strawb Week 1: The Menu-ing
So at the start of the jam, our first order of business was simple: making a working menu system!
Our only problem: we had very little experience with Godot’s UI nodes, and similarly little experience with the API surrounding it. We also didn’t have much experience with Godot’s 2D.
However, the good news: Godot’s documentation is really good. It’s not perfect by any means, but it’s much better than what most other engines offer. We were able to get very basic menu working in just an hour!
But a basic menu isn’t what we were looking for. We were looking to have a fleshed-out menu system that could have entire menus defined entirely via data, such that flatscreen and VR can share the exact same menu data, even if their presentation needs to be different.
We ended up working on our game for around 2 hours a day during week 1, and most of that time was spent creating our own API that allows generating entire menus through code.
We wanted to do more, and clean up the code. But by the time we were ready to get into making the menu system be generated based off definitions within a custom resource, it was already Friday! Everything was theoretically in place to make menus generate based off data rather than code, but we felt that we had spent way too long on menus specifically, and ended up stopping ourself.
So we settled on making all of the game’s menus with the menu API we had created. The results of that are, well. About what one would expect someone to write during a jam:
By day 7 of the jam, the entirety of our project was fairly rudimentary! It looked a bit like this:
Something unpictured is the audio work we did for this. The menu sound is a pair of samples of a katana being sheathed and unsheathed, and passed through to a simulation of a guitar amp. This creates a very satisfying menu-y sound!
Strawb Week 2: The Move-ening
And now the fun part begins!
For week 2, we wanted to get jamming on both player and NPC movement. We started on player movement first!
So the player character controller we had going in was extremely rough around the edges. It got stuck extremely easily on stairs despite using a capsule collider, it had weird physics when over the edge of ledges, uneven geometry was extremely jerky, and in general it didn’t feel good outside of an environment that’s been thoroughly smoothed out with clip brushes.
Separately, the character movement also felt extremely stiff, which directly contrasted the vision we had for the player character. We wanted the player to feel like a nimble yet unwieldy beast, while the character controller we had instead felt like a shonkier Gordon Freeman.
So we made an attempt to fix that by shrinking down the player’s collider, and replacing the grounded check with a raycast that extends past the collider’s bounds. The collider was made to roughly cover the head and upper body, but nothing else.
This fixed the issue with getting stuck on stairs, but didn’t at all resolve the stiffness and jerkiness of the overall movement. Movement didn’t feel good, and we wanted something feeling far more fluid.
That desire for fluidity somehow reminded us of how the procedural animations in Overgrowth work. More notably, how the general basis of Overgrowth’s animations is making the upper body behave like a bouncy ball, with the entire upper body more or less mounted on a spring.
So that lead to a thought: what would happen if we apply that to the actual physical player object?
So we made a change to the player raycast. Instead of simply setting the player’s body to a fixed position above that raycast when it collides, and nulling out any vertical velocity, we instead made the raycast behave like a spring, pushing the player body away from whatever the raycast collides with.
This worked a lot better than we expected! Suddenly, player movement was feeling significantly more fluid, and uneven geometry was no longer jarring to walk over. The result is something far smoother than the current industry standard of simply putting clip brushes (or engine equivalent) everywhere and hoping for the best.
However, stairs in particular were still problematic, as the player ended up colliding with them for every step.
As an experiment, we added more raycasts. 16 of them, to be exact. These raycasts surround the player character in a radius that’s slightly smaller than the capsule collider. Whichever raycast has the shortest distance gets treated as the point the player’s standing on.
This ended up making stairs feel a lot better, with no clip brushes necessary!
As a side effect, those extra raycasts also make it trivial to do a very underappreciated thing present in Quake and Half-Life 2: increasing the player’s friction if they’re half-way over an edge, making it a bit harder to end up accidentally flopping right over a ledge. This is extremely subtle, but helps make movement a lot more controllable. We do this by simply checking if the center raycast is colliding, and if not, we assume the player is over a ledge.
We experimented with adding a crouch button, but sadly, due to the way we coded the spring (it doesn’t follow Hooke’s law, and is very haphazardly-written code), the lower to the ground the player’s supposed to be, the more wobbly the up and down motion is, which just feels incredibly silly.
However, we did get a bit of dynamicism in there, by making the character move down a bit when moving fast, to simulate the full-body lean of a full-on sprint! This does suffer from the aforementioned issue, but it’s only noticeable if you jump off a ledge while sprinting at full speed, and continue sprinting.
It took about 12 total hours of work, from February 8 to February 11, to finish up the player character’s movement, and tweak it to suit our vision.
During that time, we also mildly sidetracked ourself with creating the footstep sounds. We took a sound of a very soft paw pitter-pattering on wood, passed it through a guitar amp, and got a fairly distinct footsteppy type of sound that sounds nice and thumpy at full volume, but soft and gentle when it’s quieted down within Godot’s audio engine. Godot likes to drop off bass when a 3D sound is sufficiently quiet and distant, leaving mostly the midranges audible.
Our focus quickly shifted to ragdolls. We knew off the bat that we had to go for implementing active ragdolls, given the goals in our design document. Active ragdolls are quite common in VR games, but the issue is that we also wanted to go for fully procedural animation. Most VR games go for the animation matching route of active ragdoll simulation, wherein the ragdoll attempts to match the exact pose of every bone in a pre-determined animation.
First, though, we needed an actual ragdoll with an actual skeleton! So we spent about an hour in Blender, and came up with the following result:
… Yeah, it isn’t winning any awards; we’re more a coder than a character modeller. We had left the ragdoll without a head with the intent of modelling a small variety of different species that all share the same base body, but for now, that was good enough for testing what we needed to do.
So for procedural animation, inverse kinematics is more or less bread and butter. But the problem with inverse kinematics is that they simply don’t obey the laws of physics, which is problematic when the goal is to animate a physically-simulated ragdoll!
So we had a thought: do we really need inverse kinematics? As it turns out: no, not at all! There’s already prior art in the VR dev space of simply applying physics constraints to what would otherwise be the end effector of an IK chain!
Thankfully, Godot has really good physics constraints. We know that physics constraints were bugged when applied to ragdolls in previous versions of Godot. But with Godot 4.4 fully moving to Jolt physics, physics constraints have gotta work fine on ragdolls now, right?
Anyway, we had spent most of the night golfing a basic procedural animation driver, with the chest, hips, hands, and feet, all being assigned to spring joints serving as this system’s IK target equivalent. The default pose gets stored and used to determine where the natural resting position of the springs should be.
So began work on locomo– hey wait what do you mean we should test our code why would we eve–
OH.
OH NO.
Yeah, so about Jolt fixing the bug with constraints on ragdolls? It’s better than it used to be, undeniably, but it’s still incredibly bugged.
So, uh. Fuck?
This was incredibly demotivating, so we decided to perform the game dev equivalent of looking at someone else’s homework, and looked into what V-Sekai (an open source VR social platform made with Godot) does for its own active ragdoll implementation. Sure enough, it doesn’t even bother at all with Godot’s built-in constraints system, and instead implements its own spring resolver, manually setting the velocity of every bone to simulate a spring.
So we ripped specifically the spring resolver from V-Sekai’s active ragdoll repo, wired it up to the code we had already written under the expectation that Godot’s built-in had it covered, and sure enough:
It worked.
The ragdolls are definitely overexcited due to the defaults being overtuned for our rig, but just like that, our golfed code started working! And it only took three whole days of debugging to finally see it working how it’s supposed to.
On the 15th, we ended up leaving things off on a simple smoke test for locomotion, ensuring that ragdoll critters behave well when driven.
And of course, it worked!
Strawb Week 3: The Sidetrackening
The third week was definitely a slow one. At the start of the week, we decided to take a bit of a break, focusing our free time on modding Bonelab, instead, for a day.
We made a simple pair of code mods: one to resolve our single biggest annoyance with guns in the game, and one to fix a game-breaking bug that’s been in the game for months but never got a hotfix.
On Tuesday, we decided we’d try our hand at recreating the Whizzbanger as a custom weapon for Bonelab. We had a lot of time left to complete our minimum viable product goal, after all, so we could take a day off to work on something else, surely.
But we couldn’t really get it working. So on Wednesday, we put more work into trying to get it working. The Marrow SDK deeply perplexes us, and a whole day of debugging yielded no results.
Thursday, same deal. We still have a hearty amount of time, we can get back to working on Friday, we’ve made quite a bit of progress. But we still don’t have it working.
Friday night, we are so close to getting it working, we can get back to working on it as soon as we finish it up. Come time for sleep on Friday, and still no progress on figuring out why our prototype Whizzbanger is buggy in extremely bizarre and unexplainable ways.
Come Satur– OH FUCK IT’S SATURDAY ALREADY???
Strawb Week 4: The Jammening
So we had wasted a lot of time being sidetracked. It was time to start jamming.
Saturday night, we broke the cycle of procrastination and got right to work on making a procedurally-animated walk cycle.
This works fairly straight-forward! If a critter gets pushed past the radius of their feet being in a neutral spot, then they will take a step. The step’s target position is determined by the current direction the critter wishes to move, along with the natural pose for the foot that’s stepping. The step itself is simply lerping the foot’s spring target position, first to a mid-way point slightly above floor level, then to the final intended destination.
It took a bit of tweaking to make it look slightly more natural than what’s seen in that GIF, though critters still walk in a fairly quirky manner. Which is acceptable!
Throughout Sunday and Monday, we took our time creating a head for the critters! We had begrudgingly accepted that we’d only be implementing a single species of critter, rather than a small variety like we originally intended. So we chose a type of critter we relate strongly to: reptillians.
It wasn’t all work and no play, though. We ended up giving the critters an entire spritesheet of various eye states, including a lil blinking animation!
(Of the 8 eye states in the eye atlas, two of them sadly go unused in the jam build: a widened eye state, and an annoyed state.)
Our initial attempts to implement the head and its ability to look around yielded a scene straight out of a creepypasta!
That was thankfully easy to fix by fixing the rotation order of the neck, as the natural 90 degree angle caused gimbal lock!
Our attempts to decouple head movement from torso movement initially yielded a funky dance routine
It was 4 AM, and we quickly figured out how to fix it after waking up the next day.
Throughout Tuesday, the 25th, we worked on violence (punching and grabbing), and the basics of critter behavior
All critters (except for the player) have brains, and change their gaze to random objects within their zone of awareness every few randomly-determined seconds.
We also took the time to make a trio of sounds for the violence actions.
The punch sound is the sound of a crab’s shell being cracked, layered on top of a thick liquid being splashed on wood. Both of them are passed through a particularly noisy guitar amp. We only realized after the jam that it sounds extremely similar to that one RPGMaker hit sound.
The grab sound is the sound of a jacket being shaken around, passed through a not-as-noisy guitar amp.
The fist swinging sound is the sound of a crossbow bolt passing by a microphone, layered together with the sound of a spear swinging. Both are– you guessed it, passsed through a guitar amp.
Throughout Wednesday, we mostly worked further on critter behavior in ways that sadly can’t be illustrated through GIFs or screenshots. We also worked a bit on the zonk mechanics, but we never screenshotted it due to the effect being a placeholder.
The zonk mechanics were written in a bit of a rush due to the pressure imposed by the looming deadline, and works in a simple way: if a critter has you as its current look target, and is within a general shapecast protruding from your face, then they’ll be affected by the zonk. If a critter is resistant, then they’ll look away, and won’t be affected as much by the zonk. If a critter was recently punched, then it’s incapable of resisting the zonk. When a critter hits a certain threshold of zonkenness, they’ll be enthralled, acknowledging you as their [insert honorific here]!
Additionally, the zonk mechanics exposed a part of the active ragdoll’s mechanics that was previously unused: if a critter is zonked or low on blood, then their ragdoll’s spring force will weaken, causing them to start stumbling, and potentially struggle to stand!
On Thursday, that invisible work started bearing fruit, as we sat down and implemented the critter dialogue system!
We spent longer than we’d like to admit on adding bits of potential dialogue, and observing the resulting exchanges between critters.
The dialogue system is fairly straight-forward; when a critter is looking at something, it has a small chance of saying a comment, which can either be generic small talk, or directed at that specific thing. Whenever a critter hears another critter, it has a chance to respond to whatever they’re saying, from a list of responses related to the category of what the critter heard!
The talk sound is one of the few sounds to not involve a guitar amp at all. Instead, the talk sound is the sound of a camel making camel noises, passed through a phaser effect, with some modulation of the pitch to improve the overall dynamic.
The yell sound is the sound of a camel screaming, passed through a guitar amp, and passed through a phaser effect.
Additionally on Thursday, we composed a very simple ambient tune for the main menu. It consists of exactly four chords played in a sequence at 20 BPM, through a warm pad that’s been passed through a guitar amp. There are some additional effects to mix it up part-way through, including a bit of mixing-up, and the introduction of a much chiller pad (that was NOT passed through a guitar amp) in the final third of the song. We are not a musician, but it does the job at evoking a sense of unease without sounding too grating in the process.
On Friday, the first thing we did was implement biting, with work starting at around 12 PM. This was made in a rush, as Friday was the final day of Strawberry Jam!
Biting is simple: the player’s camera gets lerped between a spot attached to the bitten critter’s neck, and the spot the player’s camera is supposed to be! As the player continues biting, the critter gets drained of blood. Once the critter has absolutely no blood left, the process of venom injection begins, playing a rumbling sound to warn the player it’s about to happen, and turning the victim into a vampire once it happens.
The rumbling sound is the sound of a mains hum passed through heavy filtering and pitch shifting, and does not involve a guitar amp.
The venom injection sound is the sound of a sword being quickly sheathed, layered with the sound of mud splatting against a surface. Both of which are– yes. Yes, they are indeed passed through a guitar amp.
With biting out of the way, we spent some time turning our testing map into a proper level, as we didn’t have time at that point to create a proper level. It took around 5 hours to create the blockout, excluding the break we took to eat dinner.
With the level done, we worked as fast as we could to implement the ability for critters to wander the level and navigate on their own. This is done through special brush entities within the map, defining general areas for critters to be in. Whenever a critter has the opportunity to change its focus, it has a small chance of instead opting to move somewhere else within the area it’s in. There’s also a small chance for the critter to choose a different area to hang out in!
Critters who’ve witnessed a vampiric action (specifically: biting) are supposed to run away to random areas (as it was too close to the deadline to implement the code for the exit point; the original intent was for witnesses to prioritize getting out of the level as fast as they possibly can), but sadly the logic for forcing this is broken in the jam build, causing witnesses to be generally hesitant to run away. However, witnesses do run once they eventually roll the chance to wander around.
And with that, our minimum viable product was more or less complete, and we had something we wouldn’t be embarassed to ship in a game jam. We submitted with 1 hour and 30 minutes left.
We quickly noticed a bug in the web build where the rendering for critters completely breaks, while a very small number of critters work fine. This turned out to be a limitation with Godot’s web exports, wherein instanced shader parameters can only be applied to a certain number of instances of that shader, with all other instances being nulled out.
Since horns and fangs were separate objects sharing that instanced material, that resulted in only two critters ever being allowed to render properly.
We managed to somewhat alleviate this by making horns and fangs use a separate material that lacks the instanced shader entirely, allowing 8 total critters to render properly. We decided to add a conditional to the critter spawner that prevents more than 8 critters from being spawned within web builds, and shipped an updated web build with both improvements. We did not update the desktop builds, as neither the changes nor the bug affects them (to our knowledge).
So! What can we learn from it?
This was definitely a journey!
Most of the pressure in the jam for us undeniably boiled down to our opting to procrastinate during the entirety of the third week. We resorted to ramping up our activity during the last week just to catch up with the pace we lost.
We also definitely could’ve been better at time management; one whole week to finish up menus is admittedly fairly silly! But on the bright side, we do have a fairly fancy menu system that we can freely use for future projects moving forward, including potential future jam games.
We did manage to achieve the main goal we wanted, which was to complete our MVP at minimum! So thankfully we don’t feel like any of the time spent on our game was truly wasted.
Additionally, we realized that we’re genuinely nowhere near as bad at non-programming arts as we initially believed. 3D character modelling is actually kind of enjoyable? Which is something we never thought we’d be saying before this month.
We also learned a fairly humbling lesson for jamming: Even if you think your scope is small enough for the alloted jam time, even if you’re good at programming, your scope can and should be smaller! Our scope for Fangs and Zonkery was pretty big for a jam game, and even if we did work on it throughout the third week, we don’t think we would’ve had enough time to fully flesh it out within the jam’s time restraints, especially with us working as a solo dev.
What’s next?
Good question!
There’s a lot of things that we feel we could probably do much better while free from the time constraints of a game jam. A pretty huge chunk of the code is written in haste, prioritizing simply getting features working over maintainability. Some of the mechanics (especially the zonk mechanic) and inner workings are executed fairly poorly, and would benefit greatly from some proper love.
We might end up continuing Fangs and Zonkery as a proper game in the future, as we feel it does have quite a bit of potential. However, due to a combination of work and various responsibilities related to projects we’re involved with, we’re usually pretty tight on free time (this month was special as we were able to clear our schedule out in advance to get a decent amount of free time to work on the game).
If we continue work on the game past the jam period, we’ll be creating a new page for it, writing up a devlog post announcing it, and leaving this jam prototype’s page up for historic value.
Files
Get Fangs and Zonkery
Fangs and Zonkery
An all-you-can-drink buffet!
Status | Released |
Author | bhijn |
Genre | Simulation |
Tags | Adult, Vampire |
Languages | English |
Accessibility | Subtitles, Configurable controls |
Leave a comment
Log in with itch.io to leave a comment.