Building My First Game

The other night at NashFP I did a whirlwind tour of a game I wrote, PewPew (play it!). While I was going over the source code with the group, I realized that I learned quite a bit while doing this and needed to share.

pewpew

About 4 months ago I had just launched the new website for Firefly Logic. I love websites with awesome 404 pages. That little bit of detail is just icing on the cake - it's how you know that someone behind it all gives a damn. What better way to solidify our nerdiness than to write a small game to play? I wanted people to know that our site was loved and so I pitched the game idea to the people who sign my paycheck: "I want to write a game for our 404 page, you cool with that?" They agreed and I went to work.

I only had a bit of down time to make it happen. This idea had been bouncing around in my head for a bit and so I had been researching a couple of javascript game engines. I told Calvin about my plans and he suggested I give it a try in Elm. I've been learning about functional programming for a few years, so it felt like an opportunity like this should not be wasted. I looked at some of the examples on their site and knew I could make this happen. I decided that the pong example had enough features to serve as my starting point.

Elm is based on the paradigm of functional reactive programming (FRP). In other languages I've worked with, user inputs are modeled as events. We're all used to the pattern at this point: register for what events we care about and provide some callback. Then later in the app our callback gets invoked and we must reach into some shared state to see if we can even accept the user input and then write back into that state some new data. It's callback spaghetti and debugging it is hell.  FRP is very different; instead of treating user inputs as some blip in time, they are modeled as a value that changes over time. That model is referred to as a signal. Signals aren't limited to only user inputs - time can also serve as a signal in terms of elapsed time or frames per second.  The latter of which serves drives the game and replaces the traditional game loop.

Quick Rundown
This is the first game I've ever built. This took about 9 days from start to finish and that includes my time learning Elm on the fly. Most of the Elm examples are all in a single file. I started out this way too, but quickly started having trouble keeping up with what went where. Then that feeling started to creep in that I was introducing too much coupling in places, so I started hacking bits out and moving them to separate modules. I feel okay about how the code organization shook out, but looking back at it now I can see some places where there is opportunity for improvement. Here's a quick rundown of the modules:

  • Entry - There's nothing too spectacular going on here. We create a level and initialize our game state with that level. I think the most interesting bit here is the foldp which is essentially a left fold over time. That turns your input into a state signal which we finally feed into the renderer.
  • Inputs - This game has very simple inputs: left/right to move and space to fire. On the last day I added touch controls (the touchFire and touchMove functions) and used the merge function to merge the keyboard and touch signals into one. The resulting touch compatible version required no modifications to the game which I thought was awesome. One last note, the final signal is a combination of all of the above plus time sampled at 60 fps. This signal becomes our game loop that drives the foldp in our entry point above.
  • Step - This is where all of the logic for the game lives. Every tick of the game runs this function which advances game objects: ship, enemies, projectiles, and explosions. Here is where we keep track of scoring by doing (very crude) collision detection and then removing those objects from our state that have been destroyed or have flown off screen. The entire job of this module is to take the current inputs + past game state and produce a new game state.
  • View - The view ends up being very straight forward. This module takes a game signal and produces the UI. No tricky business - just render exactly what the state of the game is. It was challenging for me because I was having a hard time overcoming prior web experience with layout. Elm uses Cartesian coordinates and that took a bit to adapt to. I also lost a bit of time to the tweetLink function because I was trying to use Elm's ports functionality to reach out to JavaScript and invoke the encodeURI method on the windows. In the end I just pre-encoded it and moved on.
  • Level - This is the level generator. It would have taken me forever to get the enemy layout right by hand, so I modeled the level as a bit of ascii (art?) and then parsed that into enemy coordinates. This allowed me to try several configurations and enemy sizes without reworking a whole bunch of manual coordinates entries. Spoiler Alert: There's only one level. 😉
  • Model - The data structures that make up the game state.
  • Utils - The dreaded "I don't know where this stuff goes" module. throttle was an experiment in controlling firing rates of the ship at the signal level. Later in the game I modeled the enemy firing rates inside the game state to see what that would look like too. After trying both, I still don't know which place is the most sensible place to manage this. cubicEasing is a port of a JavaScript easing algorithm so I could have enemy fire be very light early on and transition sharply to heavy fire while remaining within some boundaries.

Wrapping Up
This was a fun step up above the basic exercises you typically do when learning a new language. If you haven't looked at any functional languages, I encourage you to do so. Elm would be an excellent first functional language because it's very interactive. It's also pure which forces you down a path without mutability so you can avoid the temptation to fallback to your old object oriented habits. Even if you won't be using Elm day to day, learning functional programming will make your other code better. Give it a try!

Thank you to Calvin and Rachel for letting me bounce ideas off of you while I was building this thing. Also, this game wouldn't look nearly as awesome without the sprites that Joel made.

No comments yet. Be the first.

Leave a reply