Red Ice Premortem – Hard Lessons in HTML5

Over the past two years I’ve been developing a multiplayer hockey game in CoffeeScript. What follows is a rough list of some things that I’ve learned.

And let’s be clear, these are all the mistakes I made before I got to the part where I built the game.

JavaScript
CoffeeScript is great as a programming language, but it’s still built on top of JavaScript. JavaScript has a few crippling problems when it comes to reusable libraries, like the lack of a require statement in the language itself. There are many tools that try to address this, falling mostly into two separate groups: compile/build tools and runtime tools.

Since I use CoffeeScript, a compile time tool fits well into my pipeline. For websites, generally I use Sprockets where you can explicitly require other files using special comments:

#= require player
#= require map
#= ...

This is good. Dependencies can be made explicit and you can require files from other files. The disadvantage is that this all happens during build time, so Sprockets itself doesn’t provide any way to require files at runtime. For rapid development some sort of guard script is needed to recompile all your files when any change. In practice this takes less than a second and changes are immediately available when refreshing the page.

Requirejs is an alternative that provides support for requiring additional files at runtime. This approach is great for development, but still requires a compilation step for optimizing distribution.

Either of these approaches work fine and the correct one to choose depends mostly on how well they integrate with the rest of your toolchain.

These problems would be mitigated greatly by a reliable and robust, game development-specific framework similar to Rails. There are many JS web development frameworks. There are even some that fit certain definitions of robust and reliable. I’ve even spent years trying to create one with its own crippling problems, but that’s a tale for another time.

As of yet I haven’t found any that I could recommend, and that’s coming from someone who spent three years building one. The features that I think are essential are:

  • Command line support
  • Packaging for web/download deployment
  • Rapid development environment
  • First-class testing environment
  • Extensions to core JavaScript libraries
  • Additional libraries specific to games
  • Dependency management as good as Bundler

Many people are working on these issues. For each one there are many attempts, but there isn’t yet an opinionated framework that brings them all together in a way that developers can get started with quickly and easily. The biggest problem is that even by choosing the most popular tool for each issue, you rapidly become the only person in the world who has used each of those tools together.

Distribution

HTML games provide a great multi-platform distribution mechanism: a website that players can go to and play the game directly in the browser. For selling a direct download, a zip file with a run.html file is easy to create – it’s pretty much the same as a zip of the website. Linux/Mac/Windows compatibility is practically free, though the major issue would be the player’s choice of browser. If it is extremely important that the game be played in a quality browser, Chrome Portable can be bundled with the game or provided as a separate install step.

Sharing with testers is also easy, just send a zip or link to the page where you’re developing your game. You could even host everything on Github Pages.

Gamepads

Gamepad support is now quite good in Chrome, but needs support from other major browsers. There are native plugins available that will enable all browsers on all platforms to provide equivalent gamepad support, but it’s too much trouble to assume players will install a plugin and too much additional work for developers to implement as well. Maybe if a plugin matched the API more exactly it would mitigate this from a developer perspective, but asking players to install native plugins defeats much of the gains from an HTML experience.

On the bright side, when playing Red Ice using Chrome, the gamepad support is better than most popular indie games. (Binding of Isaac, I’m looking at you!)

Multi Player

In an attempt to alienate all potential players, I originally designed Red Ice to be played 3v3 locally using XBox controllers. As you can see from this chart, number of friends drops rapidly when owning more than three controllers.

Right now Red Ice is 2v2, and that’s probably correct. I do want to do more experimental games with more than four players locally, but with six or more players, screen real estate starts to become an issue.

Using Multiple Cloudfront Domains with Paperclip

In order to speed up asset loading using a CDN is generally regarded as a good idea. It is also recommended to split up requests among separate hostnames to allow the browser to parallelize loading.

Enabling this in Rails with Paperclip is pretty easy, though the documentation isn’t extremely rich.

You’ll want to set the s3_host_alias option to a proc which determines the correct domain alias based on the id of the object the attachment is for.

  has_attached_file :image, S3_OPTS.merge(
    :s3_host_alias => Proc.new {|attachment| "images#{attachment.instance.id % 4}.pixieengine.com" },
    :styles => {
      ...
    }
  )

This sends requests to the following hostnames:

images0.pixieengine.com
images1.pixieengine.com
images2.pixieengine.com
images3.pixieengine.com

The best part is that the same image will always have the same hostname. I’ve seen some people suggest randomly choosing a domain, but that reduces caching potential as the same item could be requested from multiple different domains over time.

Capistrano cached copy takes a long time to copy during a deploy

If you’re using Capistrano to deploy your Rails projects it’s common knowledge to use a cached copy to prevent downloading your entire project from git or svn with every deploy.

What I didn’t know was that by default Capistrano copies over the .git directory when deploying. This was causing a 90 second delay in our deploys. Outrageous!

Fortunately the fix is simple, just cram this into your deploy.rb file.

set :copy_exclude, [ '.git' ]

DSLs In CoffeeScript

When programming if something is hard it means that you are not programming at the correct level of abstraction. Remember your ABCs (Always Be Coding-at-the-correct-level-of-abstraction).

What is the correct level of abstraction? The correct level of abstraction is the level that perfectly matches your problem domain. If you are creating an application about painting and geometry then you’ll probably be using points, lines, colors, areas, blending and more. Be sure to use those in your code as well, because doing cross products by hand is for mathletes, not painters.

I recently put this into practice when I was creating the menu screen for Red Ice:

gamestate "Tournament", MapState
gamestate "Versus", MatchSetupState
submenu "Mini-Games",
  minigame "Zamboni Defense"
  minigame "PushOut"
  minigame "Paint"
submenu "Options",
  item "Config", ->

The structure looks like a menu, with submenus being obvious by their indentation. There’s not a lot of extra syntax junk in the trunk. It’s clean an to the point.

I knew that I wanted the menu to be a simple list of strings and functions. I started out by just using object literals, but noticed much redundancy. In the natural process of drying up the code by removing redundancy and naming functions simply I ended up creating a DSL.

The starting point was creating a function called item.

  item = (text, fn) ->
    text: text.toUpperCase()
    action: fn

At that point the menu became a list of items, which was a big improvement, but several of the functions were similar, like opening a submenu or changing game states. So I decided to add more functions whenever anything was duplicated. These new methods were able to build on the item method I had created before, making them simpler as well.

  gamestate = (name, state) ->
    item name, ->
      engine.setState(state())

  minigame = (name) ->
    item name, ->
      engine.setState(Minigames[name]())

The submenu was a little more interesting. Because I made it a function, it was easy to automatically add a back button to each submenu just by sticking it in the function. Additionally implementing the back button was very simple by using the functions I had previously defined. One benefit of CoffeeScript is that I was able to use splats to allow any length of arguments to the submenu function, with each argument after the name being an item in the submenu.

  back = item "Back", popSubMenu

  submenu = (name, menuOptions...) ->
    item name, ->
      I.menus.push [back].concat(menuOptions)

Making nested submenus is also a breeze, not that you’d want to nest things deeply, but hey good DSLs make everything easy!

submenu "Mini-Games",
  submenu "Survival",
    minigame "Zamboni Defense"
    minigame "Mutant Attack"
    minigame "PushOut"
  submenu "Cooperative",
    minigame "Paint"

The biggest advantage of DSLs, correct abstractions, and higher-level languages, is that their power compounds. This would have been possible in pure JavaScript, but probably such a pain that it wouldn’t have been worth doing. Once you have a solid abstraction you can then build on it further and even combine different components in new and interesting ways.

If all you have is a hammer it’s going to take you a damn long time to build a house.

How to Create a Compass Extension as a RubyGem to Share Sass Mixins

I was searching for how to include sass mixins in Sprockets projects as a gem dependency. There may be some way to do it but I couldn’t figure it out. That’s why I ended up going for a Compass extension, since I use Compass anyway.

Create the compass extension:

compass create pixie_sass --using compass/extension

Remove the templates directory

rm -rf pixie_sass/templates

Create the gem via bundler:

bundle gem pixie_sass

Add compass as a gem dependency:

gem.add_dependency "compass"

Move the stylesheets folder to be within lib.

Register the plugin in lib/pixie_sass.rb


require "compass"

base_directory = File.join(File.dirname(__FILE__))
Compass::Frameworks.register('pixie_sass', :path => base_directory)

Add your sass files and mixins.

Push to github.

Require the gem in your other projects:

gem "pixie_sass", :git => "git://github.com/PixieEngine/pixie_sass"

See the example pixie_sass gem on github.

Weekly Recap of 1 Hour Daily Game Jams

@mdiebolt and I review our daily 1 hour game jams over the past week. Audio levels in games are still too loud.

First 30 Days:

Week ending 04-14-2012:

I find that doing these 1 hour jams every day really helps to focus my game design skills. Who’d have thought that practicing something for at least an hour every day will make you better at it? Now when Ludum Dare rolls around 48 hours will seem like a 2 year dev cycle.