A few weeks ago I participated in Ludum Dare (LD) 45! This was my third time through the gauntlet of Compo. In this post I’m going to talk through my experience, broken down into these parts (click to jump):
- Game Idea
- First Iterations (MVP)
- Random Generation
- Leaderboard
- Gameplay Balancing
- Sound
- Building A Menu
- Takeaways
If you want to play the game I created, you can do so here!
Game Idea
This LD’s theme was “Start with nothing”. Yes it was terrible, just like almost always. This one was particularly nasty to me though. I usually like to put a unique spin on the theme, but this time around I really got stuck in a rut. I had a hard time coming up with a game idea that wasn’t just starting with some resource being low or empty. I think a lot of people shared this sentiment, and the submitted games reflect that.
Being at a complete loss for something unique, I kept coming back to a spaceship low on fuel. Not exactly a creative take, but I started piecing together a bigger game. I thought, what if I could really build a game that was arcade-y and competitive, but also give the sense of a massive universe to explore? Almost like a modern arcade cabinet No Man’s Sky?
Normally I try to stick with a single solid game mechanic, anything more than that can be difficult to pump out in a 48 hour period. However, this idea was different. For this game I had three separate “phases” in mind. The player would start with no fuel in their ship, careening down onto a planet. They would then have to explore the planet and find fuel to launch their ship. Finally they would fly their ship through a randomly generated universe to explore as far as possible. I had a very specific vision for the game loop, including a possible intro animation about having no fuel (yeah it got cut)!
With a massive scope, I set off coding.
Side note, I developed this game in Godot!
First iterations (MVP)
I actually started out on phase 3 (launch). I figured that getting orbits and gravity right in space might prove to be difficult, and I wanted to start experimenting with it ASAP. After a few hours of work, I had a foundation built.
As expected, I struggled with getting this right. Even in the submitted game, the gravity feels unbalanced. I really wanted planets to be orbitable, but also a threat that could pull your ship in. I found it difficult to balance the gravity so that planets could be used to fling your ship without it also just sucking your ship in. I toyed with a few approaches for this, including a varied gravitational field based on distance to planet. In the end I just gave the player’s ship more thrust power to counter the gravity. I set a max ship speed so that even at a really high movement speed gravity was a threat. In the end, I wasn’t able to make gravity useful for conserving fuel like I’d hoped to.
For the first night I always try to get the full gameplay loop implemented. With basic space flight done, I began on the crash phase.
The crash phase went fast, creating movement for a side scroller jet was straight forward. I was quickly onto the explore phase, which for the MVP consisted of just a top down character moving on a map. Once these two basic foundations were done, I was able to link everything together with some nice transitions. The gameplay loop was starting to come together. This is the same time that I came up with the name, Planet Jumpers.
It was at this point that I started to get worried about the scope. It was about 1 AM and I had only managed to create the absolute bare-bones game loop. There wasn’t even a way to lose yet, which wasn’t a great sign for making the deadline. To comfort myself before bed, I added in mountains and space debris for the player to avoid. These obstacles could actually kill the player, meaning the game actually had a full gameloop now in place!
At 4 AM I had the MVP done and went to bed!
Random Generation
Day 2 was all about expanding the MVP I had built the first night. A LOT of this revolved around random generation.
I won’t get too much into the technical details of the random generation here, if you are interested in the details, checkout one of these other posts!
To summarize, the planets generate with a random biome, size, gravity pull rate, and atmosphere toxicity. Biome mostly affects the look of the planet, size affects how long the crash phase is as well as how big the map is during explore phase, gravity pull rate affects the strength of gravity in the launch phase, and atmosphere toxicity determines how quickly your life support depletes on the planet surface.
I struggled with balancing again here. I tried to focus on making it fair to the player. The trick with random generation is to make the player feel like the playthrough is unique, but still familiar enough to feel fair. With a tight time limit (and lack of experience), I had a hard time making the fuel show-up in places I thought was fair. A big problem was landing on a planet that had a really toxic atmosphere and dying with no hope. As a player, you pick a planet to land on, and you have to just kind of hope for the best.
There were a few things I did to try and improve this. First, I made sure every planet was guaranteed to contain atleast 3 fuel pickups, enough to launch your ship. Next, I toyed with planet appearance in the launch phase. I gave planets with bad atmospheres a red or green hue, letting the player identify and actively avoid them. I also greatly increased the rate of fuel spawns on large planets to counteract the huge map sizes.
Overall, the random generation does a good job of making the world feel unique and deep. However, in the case of Planet Jumpers, it isn’t always balanced and can be frustrating for the player. Next time around I will try to be more intentional in my usage of random generation, and rely less on it to create a fun challenge where one doesn’t already exist without it.
Leaderboard
Something I really wanted to do last time was add a leaderboard, so from the beginning this was a stretch goal of mine. I didn’t want to just add a leaderboard with names and scores though, I wanted it to be something fun and personal.
The first thing that I did was work on the end game screen. I built a UI that displayed the exact cause of death, with a few extra bits of information, and the option for the player to name their cause of death.
This made losing more interesting. You got to see more information about your end, and you got to name your resting place!
The next thing I did was build a simple leaderboard API in NodeJS. I set it up locally and got it receiving and recording data from the game. With the connection figured out, I built a top 10 UI for the user to see.
I needed to make it public, so I put the leaderboard API on an Amazon EC2 instance. This is where I hit my biggest snag of the event. While I wanted my game to be hosted on Github pages, the leaderboard API lived on my EC2 instance at ld45.garrettallen.dev. My game began to hit a CORS problem when trying to make a request to the leaderboard server due to differences in the domains.
I wasn’t worried at first, I assumed I could just put my game onto the same server as the leaderboard and resolve the domain problem entirely. So I setup my game on an NGINX web server. Then I realized, I need to now also setup SSL. Without SSL the browser yells at the user that they are visiting an insecure website. Still, not a huge deal, I used Certbot to setup SSL on my webserver. My game was being served at https://ld45.garrettallen.dev and everything seemed fine. That is until I realized my leaderboard still wasn’t working. I started getting a new error:
Mixed Content: The page at 'https://ld45.garrettallen.dev/' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://18.224.157.46:5000/leaderboard'. This request has been blocked; the content must be served over HTTPS.
Turns out you can’t make a request to an insecure endpoint from a secure website. I suppose that makes sense, but now I was faced with setting up SSL for a NodeJS application. This isn’t something I’d done before, and it wasn’t obvious how to do it.
After already having spent about 2 hours troubleshooting, I spent another hour working out how to put the leaderboard on a subpath of the same domain and serve the same SSL certificate using an NGINX reverse proxy. Looking back now it isn’t that complicated to setup and it was a great thing to learn, but at the time it was a frustrating time sink that was completely unaccounted for.
While it was a larger time investment than expected, the leaderboard adds a lot of replayability. It’s also really fun to see the names and places where people ended their runs!
Gameplay Balancing
I’ve already touched on some balancing throughout this post, but there are a few specific things I wanted to discuss.
After getting a lot of the random generation in and balancing fuel spawns, there were still a few problems with the game. First of all, there was a clear best strategy for making it as far as possible. In the launch phase, when you’re flying through space, you can drift forever. So the best strategy was simply to reach max speed in a direction, and just float as long as possible. Using as little fuel as possible to veer away from incoming planets, you could go for a really long time. This was a boring tactic, and it kept the player in the launch phase with little input for much too long. I wanted the player to really feel urgency to pick and land on a planet, so I added hostile alien ships.
Alien ships start spawning after you launch off of your second planet, and for every 10 million miles the player crosses, an additional alien ship can be present. For example, at 5000 miles only one alien ship can spawn, but at 12 million miles two alien ships can spawn. Everytime the player causes a new universe to spawn, an alien ship has a chance to be spawned as well. The aliens immediately seek out the player, and fire slow homing missiles. The player has a few options when being attacked by an alien. They can use their fuel to dodge the missiles until they explode (missiles explode after 15 seconds or by hitting a planet) or they can land on a planet.
I really wanted the player to be able to counter the aliens by luring them into stars. However, I was unable to get the feature to work correctly after about 30 minutes of work and decided that just making them avoid missiles was acceptable. Technically alien ships can be lured into stars, but it is difficult to get the ships positioned in a way that they will actually collide with one.
Ultimately these aliens are a huge threat to the player. If someone tries to use the drift forever strategy, they will quickly be met and shot down by an alien. However, this added a new mechanic regarding ship health. I didn’t want the ship to die on first missile impact, that would be too difficult, so I opted to add ship health to the game.
With the addition of ship health, the crash phase also became more difficult. Instead of resetting ship health when starting the crash phase, the ship’s health now carries over. So if you were hit by debris in the first planet, your ship would start with only 4/5 health on the second planet. Same thing if you were hit by an alien missile, you would lose a piece of your ship health. This added a new pickup item to the planets as well. Now in addition to finding fuel on the planet surface, you could find ship repair kits! This made the game slightly more difficult and more interesting, a complete win!
I also found that I needed to make the game more beginner friendly. When play testing, a lot of people were dying on the first planet. This isn’t fun for anyone, and I really wanted the game to feel more accessible to newcomers. To fix this, I made the first planet have predefined attributes. It was easier than any other planet possible and was smaller in size. This meant the crash phase was shorter, and the player had more time to explore the planet surface. Lastly, I made the player start with enough fuel that a single fuel pickup was enough to launch their ship. This way you wouldn’t have to spend a bunch of time running around the first planet and could quickly skip to the third and final phase of the game.
The last thing I did was add tutorials. This was another big one on my to-do list, but wasn’t one I was sure I’d have time for. I really wanted to add interactive/guided tutorials, but settled for static screens during transitions instead. I think this was a huge benefit to new players due to the complexity of the game. While I could have put a guide in the description of the game, having it actually incorporated into the game itself made things feel more polished.
Sound
Per the usual, I left sound for last. It’s something I need to practice a lot more. As with the previous entries, I used ChipTone for my sound effects and Bosca Ceoil for the music.
I didn’t spend a lot of time on the sound effects. Ultimately my game didn’t have that many moments that needed sounds. I settled on a basic pickup sound, a few death sounds, and some explosions. There’s nothing fancy to report here, I just played with the settings on ChipTone until I found something that I thought fit. I was planning to make the sounds a bit more grounded and less arcade-y, but didn’t know where to start. With such a small amount of time left when I got to audio, I settled on the tried and true ChipTone approach. I also really wanted to generate a few unique ambience sounds for each of the planet biomes, but unfortunately I didn’t have enough time.
I really wanted to create some good background beats for the game this time around. I went in thinking I’d build a different track for each biome just like I wanted to with the ambience sounds, but again I didn’t have enough time. Instead I settled on a different track for each phase of the game. I spent a good hour on this, and I really like how it came out! The tracks aren’t too repetitive and they don’t stand out, which is a good thing for background music. I did however run into a problem with Godot having a noticeable skip when looping the file. I was able to play with some of the settings for the imported audio file to make it better, but unfortunately it is still sometimes noticeable in the submitted version of the game.
After submission I came across the Reaper DAW from watching this video from @PixelProphecy. It made things look a lot nicer, and next time around I’m going to use that tool for my sound and music.
Building A Menu
With only a little time left until submission hour, I was finally able to circle back around to the main menu. For this game I wanted to give the user a few additional options. One thing I was sure of was that I definitely wanted them to be able to adjust and mute the music and sound. With Godot this was really easy to implement. I created an audio bus for sound effects and an audio bus for music. On the main menu I added simple UI elements allowing the user to adjust volume levels of sound and music separately, and the option to mute them entirely if desired.
After a few rounds of playing, it seemed like the transition screens got a bit cumbersome. To cater to people playing competitively, I added a quick transitions option on the menu. When this option is turned on, the between phase transitions happen almost instantly.
I wanted to give players that were really struggling with the difficulty a better shot at seeing the game. For this I added the explorer mode option. With this turned on the player will not die from lack of oxygen and their ship functions on no fuel. However, they cannot submit their score to the leaderboard. What this means is that even if a player is having a really hard time playing, they can fly around in space and see some of the planets without stressing about their fuel levels. It also gives players more time to explore the maps and checkout the random generation and tilesets!
I also added a New Game button which launches the game without showing the tutorials. This gives returning players the option to completely bypass tutorial screens, while new players can make sure they see them!
The menu is more plain that I would have liked, but the functionality is there. I received a lot of positive feedback about how these options allowed people to enjoy the game at their desired pace.
Takeaways
All said and done, this was definitely my best submission for Ludum Dare. It showed me that I can actually squeeze a bigger scope in than I had previously thought. While people say to stick with a core mechanic for these events, I found that multiple phases was a lot more fun to play and gave the game more depth than most of the submissions.
I lost a lot of time to setting up my server for the leaderboard, so next time around I will make sure the server is ready to go before the event begins. That said, the online leaderboard has been a fantastic choice and I will definitely be incorporating some form of leaderboard or multiplayer again next time.
Another thing I didn’t do is add an always visible score to the UI. I think this would have really helped players feel the progression better, and I regret not making time for this before submission.
The random generation was a bit of a headache, in the future I will lower the scope of the random generation so that it’s impact is more intentional.
While my art was by no means exceptional, it is getting better each time I participate. With this event, I didn’t focus at all on art, and I thought it came out pretty well! Before the next event, I’d like to learn shaders. With shaders I will be able to create effects needed for things like the stars without drawing entire animation sheets.
I don’t think I got my game in-front of people enough early on. Next event I will be trying to get my game played earlier on for balancing feedback. This way I have more time to focus on things that take away the fun of playing. Without this feedback it was hard for me to properly balance the game, and I spent more time balancing than I probably could have otherwise. Additionally, I don’t think I got my game out there enough once it was submitted. Next event I plan to play more games on the Ludum Dare website so that my game is played more in return.
The sound effects for this submission were not great and creating what I wanted was a roadblock this time. I really need to learn a proper software such as Reaper so that I can create good and game appropriate sound effects next time around. I am starting to get the hang of creating basic background beats, so I’m hoping I can spend less time on the background tracks next time.
Building the menu was a lot more important than I had originally given it credit for. Next time around I’m going to make sure that I build something just as functional so that players can adjust the game to their needs. The addition of essentially an “easy mode” was a great last second decision that I will incorporate again. I will also make sure to incorporate tutorials into the game again as it helped people get into the swing of the game quicker.
Planet Jumpers scored the following for Ludum Dare 45: