Learning to finish
This simple little game about a bird that collects coins and runs away from an amorous ladybird represents a big milestone in my growth as a game developer. Before this, I had never truly finished a game.
I was fresh off of failing to complete the fourth Reddit Game Jam. I had bitten off way too big a chunk of gameplay for a 48-hour jam and I didn't even come close to getting the basics in, let alone a complete project. Particularly, I failed to predict just how much complexity there is in robust tile-based collisions for a 2D character. I spent several evenings after that digging into a bunch of articles about handling 2D collisions. This resulted in a small test project with a little ninja that could run, jump, and even wall-slide.
When Reddit Game Jam 05 was announced, I was ready.
The theme for the jam was "Love". I wanted to have some form of AI controlled enemies chasing the player around a working tile-collision level. I started by porting over my ninja demo and upgrading the tile map format to support more advanced level layouts and enemy spawns.
Most of the art was taken and remixed from TigSource's Assemblee art jam. Once I created the ladybird the full theme of the game became clear. The player would run around collecting little coins while an aggressively amorous AI would chase them around. I would need to make it so an enemy could follow the player around my tilemap level.
I had never created any kind of working AI behavior before. I started with a very simple left/right walk approach to test the character animation but it was obvious that something more intelligent would be required to let an enemy follow the player around platforms and not get stuck. I worried that I had again bitten off too much and tried to think of ways to simplify the approach.
That night, my brain kept working and I came to the realization that the follow behavior was really a pathfinding problem.
I woke up the next morning and looked for an A* example I could understand. I found a very straightforward python implementation and ported it line-by-line to Lua. This was a great way to learn pathfinding because I had real problem to solve that needed a concrete implementation.
I added ladders to the map because I didn't know how to get the ladybird to handle pathing across a gap between platforms with a jump animation (more on this later). I could now generate a path between any walkable point in the level and the layer's location. I set the AI code t constantly generate a path from the ladybird to the player and have the ladybird follow it. It worked! The ladybird could now chase the player all over the map.
Now, my A* code had some performance problems. Specifically Lua doesn't have a native min-heap implementation and I didn't know how to write one. This meant that I could only run the path search every few seconds. The ladybird would follow her path until a new one was generated from the updated player position. Sometimes she might reach the end of her path and not reach the player (this usually happened when the player jumped off of a ledge with her nearby) . She would just stand there for a few seconds until the pathfinder generated a new path.
I wrapped up the day by adding a little frustration animation for the ladybird whenever this occurred. This was huge. Instantly the ladybird turned from a palette-swapped mechanical AI into a character with hopes and dreams.
I slept well that night.
The final day was spent adding in all of the small systems required to turn the AI demo into a real game. I hooked up the coins, sound effects, a health meter. The best change was to increase the ladybird's speed any time she played her frustrated animation and resetting it when she caught them. This created a nice increasing tension/release loop.
I spent a bit of time adding music and sound effects and polishing up the menus.
By the time I was done I had a killer headache but I had done it. I finished a game!
The Making of Unrequited from Jay Roberts on Vimeo.
This ended up being a pivotal project for my hobby game development career. I've used code from this game in every Lua-based game I've worked on since. I learned to keep my code clean and decoupled.
I should have playtested the game. A common problem just about everyone has is that they expect to be able to climb the ladders and can't. This is universally confusing. If I were to redo the game I would make them climbable and let players figure out on their own that it isn't very efficient. Or, I might change the ladybird to a different climbing sort of animal to make it clear the flap-jumping is for birds and ladder climbing is for their enemy.
Later on, I implemented a true min heap for my A* code which allowed it to get a fresh path every frame. I tried running the ladybird with perfect pathfinding and it completely broke the gameplay. It's no fun at all when you can't trick her. Implementation flaws can reveal gameplay benefits.
- Made in 48 hours
- Lua / LÖVE
- First A* implementation
- Custom spritesheet animation system for LÖVE