After having gotten experience with the fundamentals of 3D rendering by writing my own renderer in OpenGL and C++, I found that the next logical step is to get experience with an existing game engine to learn about the higher level aspects of game creation.
With no substantial prior experience in Unity, I worked over the Christmas break to learn Unity and build a game as part of the Search For A Star programming challenge.
I was able to collect very valuable experience in game creation and working with a game engine.
This includes
2D arcade platforming physics by using and manipulating Unity Rigidbodies
Player input
Colliders and Raycasts
Custom Unity Editor Windows (tool development)
Event system (data driven design)
Post Processing (Post Processing Stack v2)
Multiple other Unity systems (Cameras, Canvas, TextMeshPro, etc.)
Project management (project plan, pre-production, documentation)
The game was published on Itch here: https://brunnen153.itch.io/caught-inside
Following below I included a write up and documentation going through the project in detail. This was done as part of the challenge and is included here for reference.
It was only at the start of my Christmas break that I heard of SFAS, an annual competition held by GradsInGames to help students make the leap into the videogame industry. Since I was planning to make a game already, I saw this as an incredible opportunity to get valuable experience and feedback.
I wanted to take part in the SFAS programming challenge and I set myself out to have a finished product by the end of it.
Prior to this project I didn't have any substantial experience with Unity. Therefore I spent the first week after signing up to the challenge to learn Unity using their Unity Learn platform.
I worked through a good chunk of their "Unity Essentials" and "Junior Programmer" pathways. Luckily I was able to pick up most concepts very quickly. C# wasn't foreign to me and I could apply many of the things I learned from writing OGLR and various other projects.
After having completed some of the microgames as part of the exercises I felt ready to move onto the SFAS programming challenge.
The challenge provided us with a template of a Unity project. Our goal was to build on top of this template, to simulate working in a team and working with someone else's code. We were tasked to not just simply rebuild and discard this existing code.
The template came with a very simple scene and some code attached to it. Upon playing, a simple story system appeared inside a notebook screen.
The template also provided a Story Editor, which made it possible to modify the story without touching any code.
The initial idea was to build something very small and simple that can be improved on in an iterative manner. It was very important for me to keep the scope of the minimum viable product as simple as possible to ensure I am able to create a working deliverable while staying on top of multiple assignments and exams. Additionally, this was the first time I used Unity much beyond the install screen, forcing me to learn many new concepts in very little time.
After having a look at the template provided for the competition, I got the idea of using the existing setup off a notebook in a room and using the supplied story system to create a chat interaction with a stranger on the notebook. This chat interaction would then lead to the main gameplay. As a story bridge for that, I imagined the player getting pulled and trapped inside the notebook where the gameplay will now take part, with the stranger they’ve just been chatting with now being the villain.
Further on, I decided for this main gameplay to consist of a 2D platformer, using simple shapes and high contrast colours, remotely imitating the look of a text terminal on a computer.
From there on out, I brainstormed many potential ideas with no concern for the effort required for the time being. This was to get an idea of what could be done before I decided on a proper feature set for my game:
Simple 2D side scrolling platformer that requires the player to reach the goal of a level
Fast movement with double jumps and wall climbing
Score keeping on the basis of time taken
Collectibles that get counted towards the score
NPCs with dialog options that can change the level/outcome
Static obstacles that can kill the player
Moving obstacles that can kill the player
Physics-based obstacles and puzzles
Limited amount of player lives and checkpoints
HP system so that the player can take damage without dying
Sound effects (jumping, level end, movement) and background music
AI enemies that attack the player
Create a nice, interactive and explorable scenery around the notebook that the main game is taking place in
Add ambient sounds
As an easter egg, add a story path that makes the character in the game win before even getting trapped inside the notebook (for example have a story path in which the character gets frightened and shuts down the notebook before anything happens)
For the design of my MVP I chose a very small selection of the ideas I brainstormed. I did not prioritize any of the other ideas yet, since I did not know whether I will have time to implement any of them and I first wanted to collect experience so that I can better estimate the effort required for these other ideas.
Whit that said, the following was my initial game design.
The game starts at the main menu. This is where the player will be able to start the game, exit the game and view the controls.
The player starts off with the camera a little away from the notebook. A dialog sequence starts playing inside a dialog box at the bottom of the screen.
After a few interactions with the dialog system, the camera moves closer to the notebook. The dialog will now take place inside the notebook screen, in the form of a text chat with a stranger.
The stranger acts weird and ultimately threatens the player, resulting in the player getting pulled into the notebook.
The player is now trapped inside a 2D side scrolling platformer. The character is nothing more than a rectangle shape (similar to this text symbol: █). The level is also only made of simple, rectangular shapes. The color scheme is very green, making it look like an old text terminal on a computer.
After a short tutorial through the dialog system, the player is given control and needs to jump over obstacles, using abilities like double jumps and wall-jumps (jumping while touching a wall) to get to the end of the level. At this point the player is presented with a score screen that calculates the score based on the time taken.
Surfaces can be either slippery or have friction. Slippery surfaces can have the player slide and gain speed if they are at an angle.
Below I will explain some of the notable systems and features I developed.
This would be the first task I tackle. I encountered many of the basic questions in Unity and experimented around with the basic systems.
I experimented with using 3D primitives against using 2D sprites. Ideas like potentially turning this 2D platformer into something 3D were in my head, but problems with collision detection and other small problems eventually made me scrap these plans and let me refocus on building what I set out to build. A simple 2D platformer.
I also experimented between using the physics in Unity (Rigidbody2D) or building my own. I wanted to have the arcadey feeling of a classic 2D platformer, and by default the Physics in Unity were too realistic. With the intent on keeping things simple I decided against reinventing the wheel and instead worked on making the Unity physics work the way I want them to feel.
I changed the gravity multiplier, dynamically add and remove downforce to allow long jumps when the jump key is held down and added double jumps as well as wall jumps.
Part of the movement system also relies on information whether the player is touching a wall or the floor. Initially I was using the Unity callbacks OnCollisionEnter() and OnCollisionExit() and used the dot-product to figure out if the collision was with the floor or the wall. This turned out to become messy however as soon as the Player started touching multiple objects. Therefore, I switched to using three raycasts (two sides and one down) to determine player state every physics update.
Early screenshot of player performing a wall jump to climb over obstacle
Small demonstration of the physics (WIP)
Jump driven through the velocity of the physics body while hitting this ramp
The template Unity project provided a GUI Story Editor that is used to define the structure of the story with all the decisions and branches. However, there was no way to trigger events through decisions. This would have meant, that any behaviour triggered with a decision would have to be hardcoded inside the C# code and linked through the ID of the beat/decision. This strong coupling between the data and the code, combined with the hopping back and forth between a GUI tool and C# code is what I wanted to avoid. The idea being that not just me, but in a bigger team with multiple developers everyone could define the story and the events through the GUI.
Realizing this goal sent me through the rabbit hole of learning a lot about Unity and its serialization system, Scriptable Objects and Unity Events. Some behaviour was rather unintuitive, and I had to perform some odd workarounds in my code, but after a lot of experimentation I was able to get it working.
The final design of my event system stores the events inside a game object inside the specific scene. I decided against serializing all this data into an asset file, because the events always refer to a method call on a specific game object inside a scene and are therefore not valid across scenes.
I do use an asset file however to keep track of valid event IDs. Every new event created draws an ID from there. This way I avoid duplicates, so that an event reference inside a story beat is always unambiguous.
I added a custom editor window to create and manage all these events. And finally I modified the existing story editor so that it allows the user to have decisions execute events instead of simply leading to another beat.
My Event Editor providing a graphical interface to create events and connect them to method calls on game objects
The modifications to the Story Editor, allowing developers to easily trigger events through decisions
The GameEventHolder game object that gets created automatically to contain the Events that are created through the Event Editor
Decision triggering a scripted camera movement
Changing text output to notebook display through event
For the menu system, and later the general UI system of my game I found an interesting pattern here: https://youtu.be/vmKxLibGrMo
I adapted this for my use for the main menu and later even for the UI for the rest of the game.
To implement the main menu with multiple screens (Main Screen and Help Screen in my case) I implemented the MenuHandler class. This class contains a public enum of all the menu screens that exist. When more screens are added to the menu scene, this enum needs to be extended.
The MenuHandler script is then assigned to the Canvas in the scene, and for every child the MenuScreen script is assigned.
The MenuScreen script only contains the enum value for which type of screen this game object is.
With all this set up, MenuHandler will then activate and deactivate the relevant game objects on the canvas so that different screens can be displayed.
I found this approach to be very helpful in managing visibility of UI elements, so I carried over this pattern into other scenes with the UIHandler script. It does the same thing with activating and deactivating children, with the difference, that multiple children can be active at once.
Different screens are added as children to Canvas
Every screen gets a type assigned so that MenuHandler can manage their visibility
That looks like a menu, yup (and it's extendable)
After the basic flow of the game was present (Main Menu -> Intro -> 2D platformer), I wanted to add some effects to make the transition from the Intro sequence to the platformer better. I added the Unity Post-Processing Stack v2 to the project and started playing around with the effects.
While I’m not an artist, to create the impression of a noisy and foggy memory as the player is getting drawn into the notebook, I found the use of various of the effects with rather high intensities fitting. I fade the weight from 0 to 1 over a couple of seconds as the camera moves closer to the notebook screen and the field of view goes wider. Then I load the platformer scene where I fade those same effects out.
Transition from one scene to another with camera effects and gradual post processing (I'm no artist though, WIP)
Screenshot mid-transition, with grain, chromatic aberration, lens distortion, etc.
I found the use of a few of the effects during the platformer fitting as well to keep the look interesting and persist the feeling of being trapped inside the noisy electronics of the notebook.
Very simple looks without post processing
Bloom + Grain + Chromatic Aberration + Vignette added, looks more interesting during movement
After all the bits and pieces were in place and some refactoring of the code, I finally worked on building the level.
I started off with a sketch, numbering through the various key elements of the level. This level is supposed to be an introductory level, both for the player, as well as for future levels to show the movement mechanics I was thinking of including. These mechanics are as follows:
Simple jump
Long jump (hold jump key)
Wall jump to climb ledge
Double jump over cliff
Double jump and wall jump
Use momentum to slide over ramp and jump high/far
Use momentum to slide up ledge
Initial sketch of the level with the seven mechanics
I then went on to implementing this level.
This turned out to be an area that I wasn’t particularly happy with the end result. While I created a few prefabs to make level creation easier, I found that this wasn’t exactly the correct approach.
My approach created a lot of separate game objects, each with its own collider. This could negatively impact performance but more importantly it would cause the player to get stuck between colliders occasionally.
The alternative would have been to manually create separate game objects that only contain polygon colliders which I have manually wrap around the whole level.
The really proper alternative however would have been to use the Unity tile map system I believe. Due to time constraints I wasn’t able to have a proper go at this though.
Finally, I also wasn’t able to implement multiple levels. This was again due to time constraints, since I was mainly focusing on the programming aspect of the challenge instead of the content creation aspect.
In the background layer I added a couple of elements as decoration and to give hints to the player without interrupting their flow.
Tutorial Beats
I found an additional use of the story system by using it as a way to deliver a tutorial to the player. By placing trigger colliders throughout the level, I freeze the game and display tutorial instructions to the player.
Story System displaying tutorial tips to the player
Give the player a trail
This might not fully fit the theme of being stuck in a terminal window, but I found it to be a pleasant way to indicate movement to the player.
Additionally, it made it easier to interpret screenshots of the game :P
More level hints
To make cliffs look more deadly, I added a simple red halo-like texture. Same for the level end.
Red halo indicating death
Green halo indicating level end
Update screen
I added an update screen to the notebook to fit with the story.
This is what the player is greeted by when moving to the notebook
Other
Refactor and create StoryManager to display story beats with more flexibility while keeping most of the original code
Add a surface effector at the end to make it easier for the player to reach the required speed to slide up the wall
Recreate font atlas to improve rendering of smaller font sizes
Building and distributing the game
One thing I noticed when initially building the release version of the game was that it was surprisingly large. It was over 400MB large.
The textures included in the template took up a lot of storage
That’s where I learned about texture compression and crunch compression in Unity. I played around with the settings and found out, that I can compress certain textures more than others, while keeping the visual impact minimal.
Especially for the normal map I couldn’t enable crunch compression because it would have lost too many details.
I could have also considered lowering the resolution, but I decided against that since the game reached a more acceptable footprint already.
Textures I applied crunch compression on
Main normal map without crunch compression (but still compressed as DXT)
Footprint got lowered to 85MB just by using texture compression
Finally, I enabled LZ4HC compression in the build, and this resulted in a build size of 70MB. Compressing the build as a ZIP file then resulted in a 32MB large archive. Quite respectable in my opinion, and this is the build that I then uploaded to itch.io.
LZ4HC compression enabled for the final build