Summary
Available to play for free on itch.io here: https://swivelmaster.itch.io/moustachevania
Source code publicly available here: https://github.com/swivelmaster/moustachevania
On January 16th, 2024 I spoke about Moustachevania at the SF Game Developers Meetup! Check out the PDF of my presentation here. (Unfortunately, there’s no video.)
Soundtrack available for free on Bandcamp.
Moustachevania is a hardcore platformer with Metroidvania-style gated exploration. I released it in a permanently incomplete (but still very playable state) on October 19th, 2023.
What I did:
- Programming
- Game Design
- Art Direction
- Sound Effects
- Music
- Writing
With help from:
- Matt Petrak: Pixel art and colors for the character art
- Tommy Millar: Character art
Introduction
Skip this section if you just want to know about the game design and programming work I did on the game.
In late 2017, I began work on what would become Moustachevania. Having given up on building a social networking app for music fans (yes, I did this and yes, it was a bad idea in hindsight), I wanted to get back into game development and in particular build the kinds of games that I enjoy playing but had never worked on professionally. As a huge fan of indie platformers like VVVVVV and Braid, I wanted to build something with tight, precise controls focused exclusively on platforming with no combat.
I worked on Moustachevania on and off for years, as full-time and part-time contracting work came and went. My initial vision for the game was as a truly modest first-time project, but after receiving some minor publisher interest and talking with folks at a consulting agency, I developed a much more ambitious version of the game meant to be more appealing to publishers. Unfortunately, after completing a vertical slice in late 2020, I realized two things:
- In a world where Celeste and Hollow Knight exist, the bar for indie platformers is astronomically high
- The core puzzle/platforming concept I had developed for this updated version was extraordinarily difficult to design for, and not nearly as fun as I expected
Ultimately, I shelved the game. Other professional opportunities were afoot, and with the additional stress of the pandemic and raising a toddler, I simply didn’t have the energy to put the hours into Moustachevania on top of everything else that was happening.
Once I decided to begin working on a real portfolio web site in late 2022, I came back to the game with fresh eyes. Playing through my initial version that focused on platforming without the puzzle elements, I realized that I had made a fun little game that deserved to be released in some form.
My plan is to work towards releasing Moustachevania’s initial small-scoped version AND the more complex vertical slice, as well as open-sourcing a significant amount of the gameplay and cutscene code, which I am particularly proud of.
The Core Game Design
In the future, I will write some detailed text about my philosophy when designing Moustachevania. Consider this a placeholder for it.
Specific Interesting Details
I wanted to focus on making Moustachevania an incredible platforming experience with tight controls. The platforming in the game can get extremely difficult so, taking inspiration from (for example) Celeste, making sure the game was “forgiving” in subtle ways was really important.
I wrote all of the player controller code from scratch. The initial version, back when I was re-learning Unity, was based on some tutorials that used raycast-based kinematic rigid bodies for the player, but I ran into some issues with that and completely rewrote it using non-kinematic physics.
Between Player.cs and PlayerController.cs, there are 2,000 lines of code and comments.
Here are some of the cool features:
- Different gravity settings between the first frame of the jump button being pressed, holding the jump button, and releasing it. This allows for extremely tight jump controls.
- “Coyote time” is enabled 100% by default in the original version of the game (it’s a pickup in the updated vertical slice) – Players can jump at any time after walking off a ledge. This allows for some truly unique platforming puzzles.
- When dashing, players are bumped up or down a few pixels as needed so they don’t hit a ledge or ceiling
- When falling, players will be bumped left or right onto a ledge if they’re trying to move in that direction but are falling too fast for their horizontal acceleration to actually get them onto that ledge in one frame
- A comprehensive record of player state for the last N frames, including inputs, is saved into a ring buffer and used to allow easily-configurable input buffering for jumps and other actions. Inputs that are acted upon are marked as “used” so backtracking to find buffered inputs doesn’t return false positives.
- Separate responsibilities between Player script and PlayerController script; Player script reads input and tracks ability state, then requests movement from the PlayerController, which is responsible for interacting with the rigidbody and dealing with physics edge cases like standing on moving platforms
- Special case for coming out of a dash: If no horizontal input, come to a complete stop. This allows players to jump straight UP out of dashes or dash to a precise distance, which allows for some very tricky platforming maneuvers. If there IS a horizontal input when coming out of a dash, the player immediately resumes their full horizontal speed.
- Prototyped a teleport ability that can replace the dash and automatically checks for a valid destination a set distance in front of the player and fails with a funny sound and VFX if the destination is already occupied by foreground elements.
- Prototyped a ‘jump boost’ that allows the player to jump higher when buffering a jump during a teleport or when coming out of a dash. This uses the frame buffer and a special “post processing” step in the player controller.
- Toggleable “over-speed” flag that allows the player to boost over their usual maximum speed under specific circumstances. This was prototyped with the goal of unlocking it as an advanced speedrunning technique at some point. It has some special rules, like that the player only has a limited number of frames with no horizontal input before the boost wears off.
Cutscene System
Initial cutscene implementation for Moustachevania was piecemeal and kind of messy, so I replaced it with an integration of Yarn. The core of Yarn is built to simply read and respond to a rudimentary scripting language for dialogue, and to accommodate branching paths based on player input, but it can be extended by adding arbitrary function calls into the script.
In order to make completely scriptable cutscenes that include scene transitions, VFX, SFX, and other features, I implemented callbacks for 35 unique methods. Here’s an example script:
title: Pickup_HighJump
---
<< PAUSE_MUSIC >>
<< VIGNETTE_ON >>
<< SHOW_SCENE pickupCutscene_HighJump >>
<< ZOOM_MAIN_CAMERA 1 1.25 .5 >>
<< wait .5 >>
<< CUTSCENE_ACTION pickupCutscene_HighJump StopTween >>
<< PLAY_SOUND pickupCutscene_HighJump ClothingInventoryPickup .75 >>
<< ACTOR_MOVE pickupCutscene_HighJump InventoryItem Up 1.5 1.9 >>
<< OVERLAY_FADE alphaWhite .5 >>
<< wait .5 >>
<< OVERLAY_FADE_OFF .5 >>
<< wait 1.4 >>
<< SPAWN_VFX pickupCutscene_HighJump 0 InventoryItem >>
<< CUTSCENE_ACTION pickupCutscene_HighJump DoPickup >>
<< wait 1 >>
<< SHOW_CUTSCENE_OBJECT INVENTORY_PICKUP 11 >>
<< ZOOM_MAIN_CAMERA 1.25 1 .5 >>
<< VIGNETTE_OFF >>
<< wait .5 >>
<< RESUME_MUSIC >>
<< set $Pickup_FallJump to true >>
===
There’s no actual dialogue in this scene; Every line is a call to a custom function, except (obviously) “wait.” Because every custom method can be implemented as completely asynchronous, multiple actions can be triggered at a time and then a single “wait” command ensures they all complete before the next instructions.
I also created a “cutscene scene” component that can manage objects, camera locations, sound effects, custom callbacks, and VFX for a specific cutscene. In the example above, “pickupCutscene_HighJump” is a Cutscene Scene that contains relevant elements for when the player picks up the “high jump” item.
Vertical Slice Feature: Charms and Adjustable Objects
The “more expensive” version of the game I developed a vertical slice for includes a puzzle element inspired by games with built-in level editors. Internally, these are called “Adjustable Objects,” and are essentially platform blocks that the player can control using “charm” pickups. This was meant to be the game’s major USP, but after spending hundreds of hours building (and debugging) the feature AND building out content for it, I determined that it probably doesn’t work as well in the context of “Metroidvania”-style world design and would be much better in a game with small, focused levels, a la Super Meat Boy.
Other Cool Stuff
Some other fun features I built for Moustachevania:
- A save system that works by assigning all collectible, destroyable, and activate-able objects unique ids, then simply appending the id to a list when the object has been collected, destroyed, or activated. When loading a saved game, all objects are initially spawned in the world, and then will check the saved list for their unique id. If it’s found, they’re collected, destroyed, or activated automatically.
- A “zone” system that activates and deactivates groups of moving platforms and enemies when the player enters or leaves an area in order to conserve CPU while keeping all related moving elements in sync.
- A gameplay state framework that runs or pauses different aspects of the game (gameplay, cutscene, pause menu, automap) and can add or remove states to the stack as they start and end. This framework manually calls AdvanceFrame and PhysicsStep methods instead of using the built-in Update and FixedUpdate methods.
- A custom-built auto-tiler that accounts for 30 different combinations of surrounding tile states.