Ludum Dare 50

Ludum Dare 50
Cover image submitted as part of the Ludum Dare 50 entry

It's that (semi-annual) time of year again - Ludum Dare 50 ran from April 1st to 3rd (Compo), and I submitted an entry at the end of the event. This post is a recollection of problems I faced, the bugs I fixed after submission, and some ramblings regarding the notes I sometimes took during the event.

Theme

Green I like my idea for, Yellow I have an idea that I can use, Red I have no (good) idea.

The theme for Ludum Dare 50 ended up being "Delay the inevitable". I submitted my votes well before the event started, and at the time I thought I had a good idea should I need it for the eventual theme. Maybe it was just the adrenaline of the event starting, but I wasn't feeling so great about the theme choice after it started. I don't feel this was the worst theme I've worked with, but it ended up far from my favourite.

Aside, if Ludum Dare 51 isn't Area 51/alien themed I'm going to be disappointed.

Lead-Up

Weeks Before

For each Ludum Dare I go through, I I take the day of and the day after off to make sure I have time to mentally prep and recover. This also gives me a bit of a safety net so that I can fall back into the Jam entry if I can't make the 48 hour deadline for some reason. This Ludum Dare was no different.

Additionally, for each Ludum Dare I try and make a change to my setup in preparation for the event - usually this means buying something and trying to utilize it during the event. Previously this has been smaller things, and didn't make the write up for LD49 - previous "upgrades" include a mouse and keyboard for the computer I was working from,  a deskmat for the same desk, and a fatigue/standing mat.

This time around was a bit different, and a bit bigger. I picked a Huion Kamvas Pro 20 (2019) to scribble on instead of relying on my aging Wacom Intuos. This is where the updated "graphic" on the Ludum Dare 49 post came from days before LD50.

Day Of

The day of the start of the event I accomplished two things that I definitely should not have been doing right before the event kicked off. The first was submitting an updated merge request for a bug I found in VScode which I managed to finish up 2 hours before kickoff, and the second of which was pulling some code from my previous entry to get started with.

You’re free to use any tools or libraries to create your game. You’re free to start with any base-code you may have. At the end, you will be required to share your source code.

Per the Ludum Dare Compo rules, you can start with some "base-code" to get you going.

Moving forward, I see two notes from myself on the day which state:

Can use template for game prepared ahead of time, should build a template to start with not an hour before the event starts next time
Phaser supports TypeScript. Should build that into the template to use for next time.

I also left myself a folder titled Ludum Dare Phaser Template which contains most of the code I started Ludum Dare with. Time permitting, I hope to get that cleaned up and ready to go before Ludum Dare 51 in October in case I continue with Phaser for that event.

For a test environment, I did all my development in a Linux environment this time around. As a result, instead of relying on a GitLab pipeline for deployments I switched to using a local docker container for serving up the files. However this also came with its own challenges. More on those challenges later.

version: "3"

services:
  web:
    image: nginx:latest
    volumes:
      - "/home/tharbakim/Git/Ludum Dare 50/src/:/usr/share/nginx/html"
    ports:
      - 5050:80
Contents of /containers/ld-web-shim/docker-compose.yml used for local testing

Kickoff

Planning

This time, I spent the first hour drawing out some notes and making a very rough plan. Thanks to the new drawing tablet it's much easier to get these notes down in a digital format, and therefore it's easier for me to include them here.

Initial planning following the theme reveal. I had hoped to use a "card based" mechanic going into the event, but it didn't work out with the theme.
Some of the gameplay features I considered, most of which would not make the final game due to time constraints. If there's one takeaway from the last few events for me, I need to get better at scoping features at the start of the event.

Problems

Phaser Preloading Issue

The only real issue I ran into on the first day was with the preload function of a Phaser scene. No matter what I did, I could not get a resource to preload when the scene was initialized. I even went so far as to add console.log(this) into the preload() function, and much to my surprise the result was null being output into the console.

I have no idea what caused this, but an hour later my resource was loading correctly. I don't think I did anything magical here, but it started working and I lost an hour to it.

I have a sneaking suspicion though this was a part of a different problem...

Browser Caching

The first time I loaded localhost:5050 to make sure the content was loading, I did it in Firefox. Later on, I started working in Chromium as it was giving me slightly better performance when doing some load testing. However at first local caching was enabled in Chromium, and I fought against some level of caching for most of the event. Even after disabling the content cache for localhost in Chromium, I still had to force a reload of all the resources to get the latest version. In comparison, Firefox was still loading old sprites after a new file had been up for 4 hours and a forced refresh wasn't pulling in the new version.

It's possible there was some level of caching enabled in the nginx container I was using, but these issues also persisted on the GitLab Pages instance I was uploading to. I found that the best method was using Chromium and forcing a reload with each change, which usually gave me a mostly updated version of the project.

Array Deconstructing into Object Properties

I found out the hard way while working through some initialization code that the following is not possible:

[this.player.x, this.player.y] = this.gameState.gameMap.getStart()
this.gameState.gameMap.getStart() returns [x, y]

I guess you can't deconstruct an array into object properties. I thought this was a bit odd, and I left a note for myself to write this down and look into it later.

So here I am copying my note into this post, and I'll continue to mark down looking into it as a "later".

Returning from forEach Loops

Comments about clean code aside, the following is perfectly valid PHP code:

$input = [1, 2, 3, 4, 5];
foreach($input as $value) {
	echo "Checking {$value}...\n";
	if ($value === 3) {
    	return true;
    }
} 
nonsensical example of returning from inside a foreach loop in PHP

Which will have the output of

Checking 1...
Checking 2...
Checking 3...

The loop is never run for values 4 and 5.

Comparatively, in JavaScript:

const value = [1, 2, 3, 4, 5]
input.forEach((value) => {
  console.log(`Checking ${value}...\n`)
  if (value === 3) {
    return true;
  }
})

Gives you the output of

Checking 1...
Checking 2...
Checking 3...
Checking 4...
Checking 5...

One of many differences between the two languages, but this one ate up a bit of time while I tried to figure out what silly typo I was making. Not sure I'll remember this one, but hopefully I'll recall writing it down here before I waste time on the same mistake again.

Sideways Data Structures

Since the map for each level is a series of tiles, they're stored as two dimensional arrays. Using normal programming practices, when initializing the first array it's indexed by the iterator x, and the internal array is indexed by iterator y. Unfortunately, this is opposite of how the actual coordinates work when you give them an x and y position on the screen.

As a result, I came up against things being rotated by 90 degrees quite a few times, and caused a few `IndexOutOfBounds` exceptions as a result. Lost a fair amount of time to this.

Additive Map Generation

The biggest time waste throughout LD50 was the code that generates the map for each level. My initial approach was to take a fixed size room (increasing with each level) and generate some obstructions inside the room.

This method I had working - except the positioning of the obstructions was far too random to be reliable. The map was constantly cut into multiple areas you couldn't get between, and as a result many levels were not completable. Levels you can't finish aren't much fun, so I spent an additional 4-6 hours writing the map generation code for a second time, the way they should have been written from the start.

The final method for drawing the "map" builds on my learnings from a similar problem in Ludum Dare 48, and hopefully represent the last time I'll make this mistake in a map of this style.

Conclusion

Overall a successful Ludum Dare event, but certainly a frustrating one. This entry lacks polish in a few simple areas, and rightly was criticized for it.

My favourite little feature was definitely the directional momentum of the player. With a few of the speed upgrades I found moving the character around to be joyful, and if one person should find joy in a game it is the person who created it.

My biggest issue with the finished product was the final product for calculating which sprite to use in the wall segments. Some pieces were missing (T and + shaped pieces specifically) as well as the calculation was incorrect when you reached the border of the map.

Ludum Dare 51

One of the biggest changed I want to make for Ludum Dare 51 is to get better at scoping the work to be more reasonable, and leave me a bit less burnt out at the end of the event. A more relaxed jam means more energy to play everyone else's entries, and I always regret not finding more time to play through other people's work at the end.