I created this game along with 2 other people for the Great Warwick Game Jam 2023. This jam lasted for 2 weeks, where teams had the opportunity to make a game that fits the theme "retro". Our team won the second prize for best designed game out of 18 submissions.
I'm only going to go over a few aspects of the game's creation, focusing particularly on areas that I worked on.
Which bits did I do?
As we were working in a team, I think it's useful to clarify which bits of the project I was responsible for. I was responsible for:
- Designing the Game
- Level Design
- Implementing the Graph Plotter
- Handling User Input
- Making the Level Controller
- Making the Menus & UI
- Overseeing the Project
I was not responsible for:
- Designing Art
- Making Art
- Choosing Music, Sound Effects or Font
- Train Following Waypoints
- Lines in Graph Window
- Collectibles & Walls
Choosing a Concept
Choosing what type of game to make is always the hardest bit, and this was no exception. Unfortunately, the theme didn't immediately bring many ideas so we spent a while working through different ideas to see what would work. There are so many different ways we could've taken it, but we had to settle on just one. Many of our initial ideas were big (and also included a lot of frogs).
After lots of careful planning, I randomly had a thought of plotting graphs for trains to go over, and it instantly stuck. Despite having spent hours of planning, we had actually just gone with an idea that I randomly thought of when walking back.
This idea was great because it combines two genres we were considering: puzzle and education. The game is both a puzzle game (albeit limited to those with some proficiency in maths) but also a valuable teaching tool to allow students to practice graphs and graph transformations in a fun setting.
Beyond the initial concept, we had to actually make some levels. It became evident quite quickly that the initial concept wouldn't be particularly fun if it was just about finding a function that joins two points - after all, you could just always use a straight line! Then, I thought we could have walls to block the path, which dramatically improved it.
However, when I drew this out, it became immediately apparent that we could not only have walls but collectibles! This means that each level would have an 'easy' solution, as well as a more difficult solution where the player has to try and collect a frog (we had spent so much time thinking of frogs that the collectibles very naturally became frogs...). Collectibles made such a drastic impact to the game, as it was suddenly possible to have different difficulty levels built-in (without an option in the options menu), something which is great to have in any game.
Designing the actual levels themselves was more challenging than I thought it would be. Puzzle design is just incredibly hard. Each puzzle needed to be hard enough so it isn't obvious, but also challenging enough such that it isn't trivial. Also, they had to use functions that people generally know about and can easily formulate.
If you find the design of particular levels interesting (I certainly do!), I made a separate blog post that goes over each level with a brief evaluation on how good it is. You can find this blog post here.
One of the biggest challenges was probably plotting the graphs themselves. Obviously, the game will not be very good without the graph plotter.
It was very important for the game that the graph plotting was fast and flexible, so it would update as you type and also allowed basically any function to be input. To ensure it was flexible (and to save us a lot of hassle), we used a library to parse the maths expressions. This gave us a function as an output, which we could then put whatever numbers we want into. Performance also wasn't much of an issue as computers are fast enough to handle graph plotting quite adequately.
As for actually drawing them, I first followed a tutorial which involed creating various different gameobjects and transforming them so they would form a line. Later, I figured out that LineRenderer existed and made this so much easier. We would simply pass in 100 values into the equation, convert them to co-ordinates and pass it into the LineRenderer. Getting everything to display properly took a bit of tweaking but eventually it all worked and graphs were being plotted. Then, once input was working, graphs could be plotted! This was a huge milestone for the project and meant it actually felt feasible. This took until about the end of the first week.
There was still plenty of work to be done on graph plotting. Mapping the track asset to the curve was fairly simple with the LineRenderer, although did still take a few hours to fix (as usual). Also, when functions went out of bounds there needed to be a point on the edge so that the graph would draw to the edge and the train could connect to it. This was done with linear interpolation as it made it much easier and was barely noticeable.
Piecing it All Together
Now we had a working graph plotter, we had to make this actually work as part of a level. Easier said than done.
Fortunately, due to how graph plotting was implemented, it was actually quite easy to have a train traverse the tracks. As the co-ordinates were being stored, it simply needed to follow a list of waypoints which is quite easily done.
But the levels consist of not only dynamic sections, but also static sections. While there would've been more efficient ways of doing it, I decided the best way to do the static sections would just be to do it in exactly the same way as the dynamic sections, so they looked consistent. As we were short on time, this was done by simply manually entering every co-ordinate (including manually generating all the curves!).
Finally, we just had to join these all together. Surprisingly, this was made quite easy by two key invariants:
- Input must be a function (i.e. they cannot be many-to-one)
- The train can only move from left to right
This meant that the algorithm simply needed to:
- Follow waypoints along the static section
- When the train runs out of waypoints, switch to a graph. For each continuous segment in a graph, compare the leftmost point's position to the train's position. If they fall outside of a certain range, derail.
- Follow waypoints along the graph
- When reaching the end, check the distance between the train's position and the next static section
- Continue until reaching the end of the static sections, when the level is completed
Note that we needed to handle discontinuous graphs, which were actually quite easy because each segment was plotted separately, so could just be handled as its own separate graph of which the train would follow the closest one. The continuity checker did only check for bounds, but we found it was near impossible to enter any function that broke this (entering piecewise functions is not possible).
There are various issues on GitHub that still remain unresolved, however there are a few that are particularly important, which only became more apparent through playtesting.
When picking up the game, most players found it unintuitive to start typing when there wasn't a flashing indicator to let them know that they can type. Also, not being able to use the arrow keys to edit parts of a function was inconvenient.
The camera currently stays fixed around the train, which is not too bad for functions where you enter from the left, but was very annoying for graphs that are entered from the top or bottom. Ideally, when the train gets close to the graph, the camera should smoothly transition to lock onto the graph instead.
Using ENTER to toggle fast-forward was just one more thing that players had to learn while under an already strict time limit. It would be far better if the speed adjusted to be faster when going through dialogue and automatically slow down as it approached a graph. This could also make it more generous for the player, as it could slow down extra slow as the train is about to derail. A fast-forward button should've been available to skip through dialogue (but stop for graphs). When the user typed an equation correctly, the game could've automatically skipped forwards (although this may skip before the player collects a frog, which is undesired, hence why enter was used). It was planned to have messages that tell the player in advance whether their graph would work or not, but this didn't make it in time either.
Overall, this was a great project that was a lot of fun and I learnt a lot. As usual, the levels were too hard. As with a lot of game jam games, the implementation could've done with a but more work. But overall, it was very good considering the time we had available. We had a great concept and produced a nice prototype that people enjoyed playing. Also, I learnt a lot about how to use Unity! Winning a prize was also a welcome surprise at the end of it all. This was a really great event and I'm looking forward to getting involved in more game jams in the future.
More About Graph Train
I also made an article that goes in-depth into the level design in particular. You can find that here.