Paper Mario: The Dozen-Year Door (2023)

View source on Forgejo

Play in browser (sorry, this won't work in text mode!)

X
Screenshot of Paper Mario: The Dozen-Year Door

How to play

Press A and D on your keyboard to cycle through options.
Press Space to accept the option.

Jumping

Press Space as you land to double-jump and deal max damage!
Press Space at the apex of either jump to impress the crowd with a Stylish trick.

Hammer

Hold A to charge the hammer up, and release when the big circle lights up to deal max damage!
Press Space as your hammer lands to impress the crowd with a Stylish trick.

Summary

After playing through Paper Mario: The Thousand Year Door, I was very impressed with how they pulled off the artstyle. This is why, when given the assignment to recreate a "2D" "retro" game in my first year of university, I opted for this one as a challenge.

This project was programmed from scratch entirely by me.
The art is from Nintendo, and belongs to them.

Challenges

I had a lot of fun reverse engineering the formats the game uses. Each character in the game is an intricate collection of small sprites acting as a fully articulated armature, and I got to re-create this system for my demake. I ended up designing a custom YAML-based format to store the scene graph and all the animations for each character (click to see an example).

Every moving sprite in the engine is powered by my custom templated Animatable type, which allows scripting many interpolation types one after the other for any variable. For example, here is how the animation for the opening curtains looks in code (paraphrased):


pCurtain->SetPosAnim({
    new Animatable<vec2>::NoAnim{ vec2{ 0, startY }, waitTime },
    new Animatable<vec2>::Lerp{ vec2{ 0, startY }, vec2{ 0, 1500 }, riseTime },
});
                            

This instructs pCurtain to first wait for waitTime, and then lerp upwards (revealing the stage). I found the design of this API very versatile, and used it to power everything, from the player animations to the attack movement to the particle system;

I also enjoyed simulating a 3D effect within the 2D limitations of the engine, which I did by remaking parts of the stage in Blender and rendering out layers, then writing a parallax effect to really sell the effect when moving the camera. I am very proud of how well this turned out.

Each character in the game is driven by compatible hierarchical state machines, that pass along the "turn" when they have finished all their moves. For example, Mario's state is Waiting until he is notified of his turn, and then switches to Thinking (where the player can pick an action). Then, it goes to Attacking, which itself has many sub-states for stages and types of an attack. Finally, he switches back to Waiting until it's his turn again.

As I use Linux, I undertook porting the entire engine as well as its build system from a Windows-only Visual Studio .sln project to a cross-platform CMake system. With that I also brought support for Emscripten and other platforms, which allowed the final game to be playable online!

Another part of my engine work went to writing a custom OpenGL 2.1 driver on top of OpenGL 3.3. This allowed me to better debug the various systems at play (the de-facto graphics debugger RenderDoc does not support the older API), while keeping compatibility with existing code. I made my engine modifications available to other students, and others have been able to benefit without changing almost any code thanks to my compatibility layer.

Tech Used

  • C++
  • CMake
  • SDL2
  • Emscripten/WebAssembly
  • Rapid YAML (ryml)
  • OpenGL 3.3
  • RenderDoc