C++/C# programmer
Developed in June 2021
Gameplay video of the custom engine version
Cabinet of Curiosities is a dungeon crawler set in a mysterious library full of strange artifacts. Defeat monsters and find upgrades to complete the level.
This game was built with a custom engine. The designers on the team also had a parallel UE4 project because the custom engine didn't have enough design tools for them to effectively experiment with mechanics and level design otherwise.
I exclusively worked on the custom engine version of this game.
Image I used to convey my proposal of silhouette rendering to the rest of the team
Based on player feedback, we ran into the problem of the player and enemies being hard to keep track of when they're behind walls.
The designers proposed to make walls translucent when the player or enemies are behind them, but because our renderer was deferred, this was out of scope to implement.
We also thought we could use lower models for walls facing the camera, but the dungeon generation would become too complex, which made this out of scope as well.
Then I used the image on the right to propose an idea where we render the silhouette of the player and enemies when they're behind walls.
This would be very simple to implement since it's just using a shader that renders a solid color and an inverted depth function, so it was within the scope of this block.
We decided to go with the approach I proposed and I implemented it.
Silhouette rendering in the final product
Showcase of the color grading process using Paint.net
This recording was made before we had assets from visual artists
The visual artists on the team wanted to be able to do simple color grading to enhance the visuals of the game.
I implemented this feature based on this article, the only difference being that the version of OpenGL we were using did support 3D textures, which made the process simpler.
It simply uses a 3D lookup table to replace the colors that appear on the screen.
This works by using a 3D texture that would have its colors match up with the coordinates of every pixel, so the pixel at (0, 0, 0) also has RGB(0, 0, 0) and the pixel at (0.5, 0, 0.8) also has RGB(0.5, 0, 0.8).
The table can then be sampled by simply using the input color as a coordinate.
Then that lookup texture gets modified by color effects in an image editor together with a screenshot of the game, which makes all colors in the table reflect those changes.
When the edited lookup table is used, all the color effects applied in the image editor are now applied to the entire game.
This approach comes with some limitations, especially the fact that it only supports effects that affect pixels independently.
You can't use an effect where the color of a pixel depends on the colors around it for example.
This was enough for our purposes though, and it was easy enough to implement to fit our small scope.
I communicated how to use the color grading feature to the artists by writing this quick guide for them.
Comparison to show how the color grading enhances the visuals of the game in the final product
Fog density changing over time
This example uses white fog, but any color is possible and the final product uses a purple fog instead
The visual artists on the team wanted to be able to add some simple fog to enhance the visual of the game.
This wasn't difficult to implement at all, and it even used a config file we set up so that the artists could change the values themselves after playing around with them in the in-game debug menu.
Comparison to show how the fog rendering enhances the visuals of the game in the final product
The color multiplier being changed in the in-game debug menu
As a contingency in case we couldn't finish features like animation or particles in time, I implemented a simple color multiplier for all meshes that could be used to convey things like damage or status effects.
It didn't end up being used for anything, but it was easy to implement and a good contingency.
The game with a multi-layer parallax background with assets from visual artists
There are several layers of stars and a foggy layer at the bottom, each layer scrolls at a different speed, and all the textures are tiled
To add the illusion of depth and to match our concept art, I implemented a multi-layer parallax scrolling background.
This filled up a lot of empty space on the screen and it's a big part of the visuals of the game.
This is another example of how I worked a lot with the visual artists on the team to make the custom engine version of the game look better.
The exploding barrel in action
One of the level design elements that I implemented is the exploding barrels.
They are quite simple, they explode when they're hit by a projectile.
The projectile can be from a player or an enemy and it will also damage both enemies and players.
This makes them both useful and dangerous to the player.
This wasn't all that difficult to implement, most of the challenge was with tying it into other systems.
I had to tie them in with dungeon generation to make them spawn with the right model and I had to make sure a sound and particle effect was added too, while all those systems were still new and mostly untested.
Pop-up text when near an interactable to show the player they can interact with something and what that will do
There were a few interactables planned for the game, but due to our limited scope, we had to cut all but two of them.
I implemented an ECS system for interactables but made the interaction itself polymorphic.
This way, the ECS could be used to generalize the pop-up text behavior and to find the closest interactable efficiently, while the interaction was slightly slower but more flexible.
The interaction itself didn't need to be that fast anyway, since it only potentially happens once every time the player presses the button for it.
The fountain interactable will heal the player up to three times
The lectern interactable was supposed to open a menu for managing weapon upgrades, but this mechanic couldn't be implemented in time so it opens the pause menu instead
Spike traps were implemented without animations by swapping out models as if they were animated sprites
One of the main level design elements of the game was the spike traps that would raise up and damage anything on them after being walked on.
These could be used by the player to damage enemies if they were to line up the enemy to walk on the trap right after the player.
I used C++ templates to write a list of component types that could be damaged by the spikes, it contained the player component and the enemy component to reuse the same code for both of them.
To do more effective playtesting, the designers on the team requested we implement the tracking of analytics data and I took on this task.
All the gathered data was discussed with the design team in a meeting we had.
To put the data in one place automatically without needing to worry about hosting, I set up a google sheets document where the data would be stored.
I also linked google forms to the sheet with a single data entry, this allowed me to send a JSON encoded object of data after every playthrough of the game.
Within google sheets, I was able to set up some javascript to decode all data and distribute it into sheets on every form submit.
On separate sheets, charts can be set up that use the distributed data like in any other google sheets document.
Obviously, I would not use an approach like this for a non-student project, it worked perfectly for our small-scoped school university though.
After being parsed, the data was automatically distributed into tables to lay out data about game runs and rooms
The data tables could be used to create graphs like the ones in this example that can be used to compare the difficulty and other characteristics of the different room types
I found and fixed many bugs while working on this project, some of them were:
Bug Fix: Void Tile Collision
Before I did this fix, the player would only collide with walls, meaning they could walk over empty tiles which is clearly not the intended behavior.
There was also no collision with fences because the collision system used full tiles and the player still had to be able to walk on the rest of the tile the fence was on.
The collision with fences for the player was fixed by implementing collision with void tiles since they were always next to one.
It was a bit more difficult for projectiles, since they have to be able to fly over empty tiles but not through fences, so they would simply check if there was a fence or not to see if they should collide.
Both the player and projectiles can't move through fences anymore
The player can't move over void tiles anymore, but projectiles can still be shot over them if there's no fence
Bug Fix: Pause Menu
The pause menu will now be opened when pausing the game, the game would be locked before because it paused the game without any way of unpausing it
Bug Fix: Enemy Destruction
The enemies are now destroyed when they die, when before they would remain in the game and consistently damage the player
Working in a custom engine with no editor, we had some problems getting a distributable version of the game whenever we wanted to test a new release.
I simply set up some post-build commands in Visual Studio to generate a distributable folder with everything necessary to run the game after every release build.
Commands to generate a distributable version of the build post-build by copying over the assets, executable, and any necessary files like DLLs
The resulting distributable folder
The game was released on itch.io and can be downloaded there. Make sure to get the custom engine version.
Project tags: #C++, #Custom-Engine, #ECS, #Gameplay, #Graphics, #Jenkins, #Multidisciplinary, #OpenGL, #QA, #Released, #Tools