GMKT Game Jam 2025
I decided to give the Game Makers Tool Kit Game Jam a go this year. GMTK is a youtuber that has interesting videos on game design that I enjoy. I haven’t done a game jam in a long while so I thought it’d be apt that I participate.
The GMTK game jam is a four day jam, it is quite popular - this year it exceeded nine thousand entries.
The Theme - Loop
So the theme this year was loop. The first thing that sprung to my mind was “wallpaper groups” but that idea didn’t lead anywhere. Next I though that a loop is a closed path - yeah I could make a racing game with this. The idea is that you race in a loop on a loop. The loop you race on being a torus.
Initial Implementation
To start with I needed a controllable vehicle. I grabbed some models from Kenny and found a vehicle control tutorial online then got them working on a 2D plane. It felt good enough to control and the tutorial already had some juice with the car tilting when turning.
For a path to follow I tried to use the Path3D
node in Godot but this frustrated me.
I recalled that a path can be thought of a function of some parameter \(t\in[0,1]\).
Now it was as simple as adding a functionality to sample this path at a regular interval and place waypoints on the map at the location.
The waypoints needed to face tangential to the path, this was done with a simple finite difference as I was too lazy to do it symbolically.
With code to count the remaining waypoints and elapsed time I had a reasonably fun minimum viable product.
Sphere
Next I decided to tackle a sphere level. This was easy as Godot had a sphere mesh resource and a sphere mesh collision shape that I could use. For gravity I turned off the physics engine gravity and created my own class that had a callable that retuned a vector for the gravity. For waypoints the path was still a function of \(t\) but the \(y\) axis was non-constant. Next, waypoints had their \(y\)-axis set to the normal of the sphere. This all added up to an easy implementation of driving on a sphere.
Torus
Now I also wanted toruses, the theme being loops and all. Luckily again Godot had a torus mesh resource, unfortunately there was no collision shape. I had a look into adding a collision shape but that would require extending the jolt physics, which was beyond the scope of the jam. I used Godot’s function to generate a concave collision mesh from a mesh resource. Now this had the problem that the player’s angle would jump around. This was due to the \(y\)-axis being set to the normal of a ray-cast with the floor’s collision shape. I had no real way of increasing the polygon count of the collision shape. After some though, setting the \(y\)-axis to the gravity direction smoothed out the player’s angle.
I couldn’t place the player at the origin any more so I added a player origin and normal to the level format. This was tricky to do manually but after some time I realised that I could set the player position to \(t=0\) and it’d work well. But it didn’t, after much debugging the erroneous placement of the player was due to the player’s origin being at the base of the model. This caused that to be the point of rotation, rather than the centre of the player. Now the player wouldn’t fall through the world if \(t=0\) was far enough around the curve.
This was now most of what I wanted out of the game. I decided that opponents were unneeded and would be too distracting.
Level Format Refactor and Custom Levels
I now had a bunch of levels with lots of repeated code. I kinda wanted to release the source under MIT license and my code was not the neatest to say the least so I decided to clean the most egregious part. I created a three tier inheritance hierarchy, I’m not usually a fan of inheritance but in Godot its well supported.
- Controller class that saves to level resource, functions to sample path and calculate derivative.
- Level-Type class that has methods to create mesh, create collision shape, define normals, etc.
- A class for each level that defines the path and in the
_init
defines the option parameters for the shape and controller.
I realised that I could use the function from the Level-Type class that defines the shape parametrically then use two functions \(u=a(t)\) and \(v=b(t)\) to define the path. This further reduced repeated logic as the old paths necessarily defined the level shape implicitly.
Adding support for custom levels was now trivial as someone could quite easily define a new level generator script in a couple dozen lines which would generate a level exactly how the game expects. A simple function to scan the user data directory for a levels folder with level resources in it completed the functionality.
Juice
For the all important juice I added more tilting to the car when you accelerate.
Next I added a rubber trail for each tire by adding a Sprite3D
per wheel per frame - this didn’t have the performance cost I expected.
I thought that turning could be better so I grabbed a tire squeal asset and made it play when you turned hard enough with enough linear velocity.
This was jarring though so I reduced the threshold then made the volume (in db) go up linearly with this parameter
and smoothed the parameter with the same linear interpolation of the vehicle tilting.
Waypoints needed sound when you passed through them.
Waypoints were originally just two cones but I added a transparent blue box that would disappear when you passed the waypoint,
this added much needed clarity that you passed through the waypoint.
Now speed lines were subtly added when you reach near maximum speed, again with intensity linearly increasing with speed.
Debris was added, more cones, some tires and boxes that would be placed randomly around the level and would go flying with a satisfying thunk when hit.
This debris are placed with a bias function as just uniformly sampling \(u\) and \(v\) would not produce a uniform distribution on the surface.
Learning
I haven’t done any 3D game development before, but it proved extremely easy with Godot and I feel somewhat proficient with it now.
What struck me was the ease of use of Godot’s Resource type, it enables almost trivial serialise-ability of data. I used it regally throughout the game and am quite satisfied with its utility.
The Game
Feel free to try it out on Itch.io: