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