Project Banner

Contained

Play Contained

It has been about six months since the last Game Jam my university organized. Since then, I gathered a lot more experience using Godot for 3D.

Following the direction of this year’s theme Smarter, not Harder I tried coming up with interesting ideas playing with this phrase. Since the last AkGaming Game Jam #3 didn't contain any sort of player movement but was essentially a "3D Point & Click Adventure" (check it out here), I wanted to try having a player controller this time.

Smarter, not Harder Theme Logo

In the end I decided on making a 3D Jump & Run, set in a warehouse complex, where you could use machinery to reshape the level.

To bring us back to the theme, I made it possible to complete each level without moving around any objects in the level, giving the phrase "smarter, not harder" an even deeper meaning. Either you become an esteemed acrobat, moving atop shipping containers, or you work smart and reshape the level, according to your needs.


Coding an interesting player controller

From what I understand about game design: If it's not fun to just run and jump around, even without a goal, adding a goal won’t help much.
Following this principle, I tried implementing a both fun and responsive player controller that feels fair and allows for some more advanced movement tech.

Implementing the basics was easy, although that’s not where I wanted to stop. I recently saw a video by Mark Brown's GMTK, where I was introduced to the idea of coyote time and input buffers. You can watch it here.

Coding these into my player controller was quite simple and that’s when I moved on to adding some sauce to the controller. I wanted the players to be able to get some elevated levels of speed, using certain methods. I settled on boosting your speed while airborne whenever you are running diagonally using W+A or W+D.

GIF of player moving around

This enables you to jump larger gaps but not without some leadup before your jumps which I thought made those big leaps much more exhilarating, because you have to now plan out your exact route and jumping rhythm before crossing larger gaps that are otherwise impossible to cross.

Although this was very much fun, even inside a test level with only a few boxes set up, it already introduced a balancing problem. I do not want to make it too easy for you to cross the level without making your work easier using some machinery, but I also don't want players to get frustrated and just try the easiest but perhaps also most boring route.

Fixing this came down to carefully adjusting distances and sometimes placing helpful or even detrimental obstacles in your path, to both encourage and discourage jumping your way to the goal as seemed fit.

GIF of player jumping across container gaps

Additionally, after adding in my first (and unfortunately also last) piece of machinery and it being huge fun to operate, I gained more confidence in players just choosing the smart way, since the risk of them finding it to be boring to take shortcuts largely went away.


Coding the Forklift Vehicle controller

As you read above, I decided to add a Forklift as machinery for my game. This both fit the setting and enabled you to conveniently move pallets, placed around the level. Be that for creating a staircase, a point for you to jump on between obstacles or to open a passage, the versatility here was amazing.

Since I had never coded a vehicle controller before, I tried just winging it to mediocre success. The forklift ended up being just a Rigidbody3D, whose velocity I set manually based on player input. Turn strength was proportionally scaled to the Forklifts horizontal speed and an additional collider was added at the fork(?) to detect and lift up pallets when pressing Space.

Forklift Screenshot lifting up Pallet

Problems arose when I added slopes to the game in level 4. Until that point, the controller was limited to handling horizontal movement, and while it could move up ramps no problem, it would look like the forklift was floating, lifted up by just one corner of a rigid collider.

Accounting for the rotation of the slope colliders made this a bit of a hacky fix, though. By now I know that there would have been an easier solution using a raycast, but my solution works without troubles too.

Forklift standing on a slope

Designing levels with diegetic tutorials

To familiarize the player with the systems in place I wanted to skip making a boring tutorial and instead went with a learn-as-you-go approach, which is apparent in the very first level, where I placed nothing but a few containers and a door.

You might think this is too easy for a first level, but I wanted to make sure the players first understood the basics of how to navigate around, before tasking them with operating the forklift. Additionally I made it such that you might also get sidetracked taking a detour through the window instead of going straight for the door, throwing us back to the jam's theme.

Up next I wanted to introduce the goal and forklift. The goal of each level is to reach the exit of the warehouse, in these cases the blocked-off emergency exit door. I made sure players could see it, but clearly were unable to get to it, sparking some natural curiosity.

Screenshot of Level 2 Peek under container visible exit

Next I wanted them to notice the forklift tucked into a corner. To make it slightly more noticeable I employed a technique I used more than once during the level design, being stringing up spotlights to highlight this point of interest.

Controlling the forklift is not quite trivial, especially lifting and placing down pallets or even figuring out you can do that. That’s why I set up a "notification system" that allowed me to prompt the player with hints about what to do and nudging them in the right direction.
With this and some jumping above the containers and repositioned pallet stacks the second level can be cleared as well.

Screenshot of solved Level 2 Standing on tilted container looking back

In the next two levels I implemented both staircasing your way to the goal and clearing out blocked passageways. At that point the jam slowly came to its end, so I cut it off there.


Locating and fixing performance issues.

In later levels I encountered critical performance issues. We are talking about 10 FPS in the later levels. It was quite a task to fix this, especially because I didn't know where this steep performance cost was coming from.

Screenshot Godot performance tools FPS

Luckily though Godot offers a range of performance monitoring tools which made my work countless times easier. Through this, I was able to determine that the problem was neither the code I wrote nor the tiny obstacles like barrels or boxes I distributed around the level.

Screenshot Godot performance tools Method Overview

Instead I was shocked to find out that the containers - the main building block of the levels - turned out to be the culprit. Hiding these brought the frames per second back up to my cap at 144. Going about fixing this was a bit of an issue. I couldn’t just delete them from the level, like I had tried in an earlier misguided attempt with the tiny obstacles.

After a few online searches I found out about several ways that might help solve the troubles I was facing. In the end, I decided on using the Multimesh Component offered by Godot.
For those unfamiliar with this technique I will give my best explaining how it works here.

In order to render objects the CPU is tasked with sending off the mesh data to the GPU. Both of these have no problem with handling the mesh data themselves. The problem arises when we want to send a lot of mesh data to the GPU for rendering. The communication between our CPU and GPU starts being too slow for that. They both have to spend a lot of time waiting for the data and each other, and that for each individual draw call.

Sketch of Draw call communication between CPU and GPU with Multimesh explanation

Fixing this for our containers is done by grouping together all of our containers. No need to send the same mesh over and over again, if each container is the same regardless. We can just send the container once and then append a list of transformation matrices and colours, positioning and colouring each of our containers in a single draw call. This is exactly what a Multimesh allows us to do. If you are interested in the exact execution of this I suggest the Godot Documentation or this video by Dave the Dev.


Results and learnings

After some finishing touches, adding UI, a leaderboard (that has since broken) and a bunch of self-recorded sound effects, I submitted the game on itch.
I received quite positive feedback and landed a second place, surprisingly landing spot #1 for Audio Design. Another reminder of how important sound effects are. While writing this article I am additionally preparing to present the game at the 2026 GG Bavaria, revisiting the projects and fixing up a few kinks I was unhappy with in the earlier jam version.


I am excited for the convention to see what times people can achieve with some practice.

Best regards, Max!

written on 28.03.2026


Play Contained