Game prototyping with JavaScript & CSS3

I wanted to share a quick few-hour game prototype to hopefully inspire a few people! The industry let-alone browser-based games seem very lacking in imagination and storytelling, or maybe I’m just not looking hard enough ;) but I miss games like Grim Fandango.

Below is a screenshot of the demo, consisting of several DOM nodes to represent parts of the main character, the birds, and the scene. Most of the animation is performed with CSS transitions / animations, some of which are dynamic and use the move.js library.

I have yet to profile anything but it runs quite smooth on my machine, and there are many aspects that could be optimized, and some simple ones like ditching jQuery.

iPhone gaming

Back to the original plan, roughly two years ago before the Ipad came out, I was writing an Objective-C iPhone game with Cocos2d and Chipmunk physics, two really wicked libraries. The iPhone seemed too small, and at the time the resolution was not great either, not to mention the image size restraints that Apple enforced, so I gave up after a few prototypes, and one full-screen PC prototype.

Browser gaming

With the advent of modern browsers, browser gaming is already a lot less shitty than a year or two ago, and it’s only about to get much better with all the work browser vendors are putting into making sure canvas and friends run smooth as butter.

The first prototype I wrote was written entirely with Canvas. One of the main benefits of this is a certain level of control that you obtain, it’s extremely easy to implement features like pausing, to apply post-processing, or ad-hoc features that are difficult with the DOM.

At first I was pretty happy with the result, until I wrote the prototype using CSS3 and HTML, the performance impact was pretty significant (though I had not implemented dirty rects etc). Certain aspects of gaming are significantly easier using the DOM, for example leveraging z-index and CSS transitions, however contrasting canvas pausing and post-processing are more of a problem. At least with the currently state of browser technologies, it’s a good idea to mix and match. You can build a large portion of your game using the DOM, and canvas for aspects like particle emitters, or perhaps even WebGL shaders like this awesome demo.

Demo

The video below shows off some of the interactivity, as well as the tiny scene building tool.

The demo consists of ~200 lines of js and ~200 lines of CSS, using CSS for transformations, animations and obviously for styling, while the js performs some basic interactions like the eye-ball targeting, and randomized cat walk routine. The demo also has a small toolkit for creating scenes, but it’s certainly nothing robust.

Source

You can grab the source on Github. Ping me of you come up with some cool prototypes!

Concepts

Some more concepts:

Styling Canvas Drawings With CSS

Today I implemented an idea to style canvas elements with CSS, I’ve never personally seen this done, and it’s not always practical, but in some cases it would be very handy. For example a text selection api may be comprised of several “elements”, the text, the caret, the selection range rect etc.

Let’s say by default, our drawing will look the image below:

default text selection

great, looks fine, but what if designers, or even developers for organizational purposes, could tweak this with CSS. Well you can! Suppose we want to support the following css:

  #editor .text {
    font: 20px helvetica, arial, sans-serif;
  }

  #editor .text .selection {
    background: #DFF3FC;
  }

First, the API to access such data will look very similar to how we wrote our css, however we provide the css property as well:

style('#editor .text .selection', 'font-family')

Now for the implementation details, keeping in mind this is not targeting cross-browser support, it’s simply a quick naive implementation, and I should note this does not cover the case of style changes, only initial styles.

So, firstly we check our cache, to prevent several expensive lookups, here style.cache is a property of our style function, if it’s available, we simply return the result.

  function style(selector, prop) {
    var cache = style.cache = style.cache || {}
      , cid = selector + ':' + prop;

    if (cache[cid]) return cache[cid];

    ...

Next up we will naively split the selector into an array so that we can work with it.

    ...
    var parts = selector.split(/ +/)
      , len = parts.length
      , parent = root = document.createElement('div')
      , child
      , part;

   ...

We loop through the “parts”, creating one div per “part”. This will allow us to see what the computed style is for an element, that otherwise, does not exist! because it’s a canvas. The naive implementation I have here, supports ids and classes only, so we check the first character, and assign the id and class respectively.

    ...
    for (var i = 0; i < len; ++i) {
      part = parts[i];
      child = document.createElement('div');
      parent.appendChild(child);
      parent = child;
      if ('#' == part[0]) {
        child.setAttribute('id', part.substr(1));
      } else if ('.' == part[0]) {
        child.setAttribute('class', part.substr(1));
      }
    }
    ...

Finally we append the root element to the document.body so that the styles are applied, get the computed style for the given prop, remove the element, and cache/return our value.

  ...
    document.body.appendChild(root);
    var ret = getComputedStyle(child)[prop];
    document.body.removeChild(root);
    return cache[cid] = ret;
  }

Example Usage

Now let’s try it out, first, the css, providing a light yellow background:

  #chalk .text .selection {
    background: #FAFFDF;
  }

In our JavaScript text selection logic, we should provide a JavaScript API for altering styling as well, however for this example, let’s add it directly:

 ctx.fillStyle = style('#editor .text .selection', 'background-color');
 ctx.fill(...)

Once applied, we get the following result:

resulting canvas style

That’s it! you can apply this trick to assign font sizes and families, foreground and background coloring, border colors, anything you can come up with! if anyone comes up with a robust implementation I would love to see it!

Full Source

Here’s the full implementation:

  function style(selector, prop) {
    var cache = style.cache = style.cache || {}
      , cid = selector + ':' + prop;

    if (cache[cid]) return cache[cid];

    var parts = selector.split(/ +/)
      , len = parts.length
      , parent = root = document.createElement('div')
      , child
      , part;

    for (var i = 0; i < len; ++i) {
      part = parts[i];
      child = document.createElement('div');
      parent.appendChild(child);
      parent = child;
      if ('#' == part[0]) {
        child.setAttribute('id', part.substr(1));
      } else if ('.' == part[0]) {
        child.setAttribute('class', part.substr(1));
      }
    }

    document.body.appendChild(root);
    var ret = getComputedStyle(child)[prop];
    document.body.removeChild(root);
    return cache[cid] = ret;
  }

node recap #2

JSONSelect

This first library is JSONSelect. This library allows you to query JSON using css selectors! this seems like an obvious one, but I haven’t personally seen any other implementations.

It’s not node-specific, however it is JavaScript, so naturally it works with nodejs as well, and over all it’s just a neat concept. Suppose we have the following JSON:

JSONSelect

  {
      "name": {
          "first": "Lloyd",
          "last": "Hilaiel"
      },
      "favoriteColor": "yellow",
      "languagesSpoken": [
          {
              "language": "Bulgarian",
              "level": "advanced"
          },
          {
              "language": "English",
              "level": "native"
          },
          {
              "language": "Spanish",
              "level": "beginner"
          }
      ],
      "seatingPreference": [
          "window",
          "aisle"
      ],
      "drinkPreference": [
          "whiskey",
          "beer",
          "wine"
      ],
      "weight": 172
  }

To grab the languages “Bulgarian”, “English” and “Spanish” we could issue the following query:

.languagesSpoken .language

cool!

node-canvas Image#src=Buffer

A few days ago I added support to node-canvas for Image#src= to accept Buffer objects, not only path strings. This needs to be polished, and to increase format support but we can finally apply image data from arbitrary sources:

 var buf = fs.readFileSync(__dirname + '/textures/jpeg');
 var img = new Image;
 img.src = buf;
 ctx.drawImage(img, 0, 0, 400, 400);

When the Buffer is assigned we can sniff the bytes to determine the format by looking for the “magic number”. For example a PNG starts with the following bytes “89 50 4e 47 0d 0a 1a 0a”, while a JPEG starts with “ff d8”.

Express 2.3.8

A recent Express refactor moved Connect’s router middleware into Express so that I could modify it further. This also means that Connect 2.0 will no longer have router. At first it seemed like a good idea, but routing to specific and it gives people the wrong idea, Connect is meant to be an abstraction layer for higher level frameworks, not to be used directly for application logic. Though this is not true for all cases, such as simply serving static files from a directory or two.

This commit consists of a general refactor of the router, while retaining the public API. What does this mean for Express? flexibility! For example now we may query Express to see which (if any) routes match a specific route path:

 app.get('/user/:id');
 // => [Route]

or routes that match any HTTP method:

 app.all('/user/:id');
 // => [Route, Route, Route]

this is functionally equivalent to:

 app.lookup.all('/user/:id');

Another alternative is “matching” rather than “lookup”. With app.match.VERB() we can query Express to see which routes would match the url passed, for example:

app.match.get('/user/12/edit');
// => [Route]

app.match.del('/user/12');
// => [Route]

app.match.all('/user/12?foo=bar');
// => [Route, Route]

In this commit I’ve introduced the ability to register logic for defining parameters. Within Express core we only support middleware-style param pre-conditions, for example auto-loading a user for any route that includes the :uid param:

app.param('uid', function(req, res, next, uid){
  User.find(uid, function(err, user){
    if (err) return next(err);
    req.user = user;
    next();
  });
});

app.get('/user/:uid, function(){
  // req.user
});

while this is great, some cases can be less verbose, while still expanding to middleware-style functions, this is where express-params comes in. This plugin extends Express with additional app.param() logic, for example via RegExp:

  app.param('uid', /^[0-9]+$/);

  app.get('/user/:uid', function(req, res, next){
    var uid = req.params.uid;
    res.send('user ' + uid);
  });

  app.get('/user/:name', function(req, res, next){
    var name = req.params.name;
    res.send('user ' + name);
  });

or via return value, useful for validations and coercion:

  app.param('id', Number);

  app.get('/user/:id', function(req, res, next){
    var id = req.params.id;
    res.send('typeof ' + typeof id + ' ' + id);
  });

Fabric.js

This one is not node related, but I wanted to sneak it in :) Fabric is a slick client-side library providing an object model on top of canvas. It has a built-in drawing mode and path smoothing, SVG parsing and some other really cool features. If you do a lot of canvas work it’s worth checking out, the author did a good job at keeping the source clean and commented, which is very important when utilizing open source.

fabricjs

node-migrate

node-migrate is a very small abstract migration framework. All it cares about is that you supply an up() and down() method, and invoke the callback when your migration is complete:

 exports.up = function(next){
   next();
 };

 exports.down = function(next){
   next();
 };

node-migrate comes with the migrate(1) executable, allowing you to run and create migrations, for example here we add two migrations, populating ./migrations/0-add-pets.js etc.

 $ migrate create add-pets
 $ migrate create add-owners

The contents of our badass pet adding migration might look something like this:

 var db = require('./db');

  exports.up = function(next){
    db.rpush('pets', 'tobi');
    db.rpush('pets', 'loki');
    db.rpush('pets', 'jane', next);
  };

  exports.down = function(next){
    db.rpop('pets');
    db.rpop('pets', next);
  };

Now all we have to do is run the migrations:

 $ migrate
 up : migrations/0-add-pets.js
 up : migrations/1-add-jane.js
 up : migrations/2-add-owners.js
 up : migrations/3-coolest-pet.js
 migration : complete

The next time we run migrate, we’ll see that they have already been completed:

 $ migrate
 migration : complete

For more information check out the GitHub repo.

Move.js

The other night I noticed that Apple’s new iMac page showcased some really slick CSS3 animations, and wanted a really simple and intuitive way to create similar effects so I came up with Move.js.

Below is an example of the API:

move('#example-13 .box2')
  .set('background-color', 'red')
  .x(500)
  .scale(.5)
  .rotate(60)
    .then()
      .rotate(30)
      .scale(1.5)
      .set('border-radius', 5)
      .set('background-color', 'white')
      .then()
        .set('opacity', 0)
        .pop()
      .pop()
  .end();

It’s still in it’s early stages, and I don’t have much in the way of documentation yet however feel free to check out the site as well as the examples in the repository.

Extend Sylus with a Nib

Today I am releasing Nib, an extensions library for Stylus, inspired by the SASS compass library. Nib is a very new library with a minimal feature set at the moment, however I need to get some of my semi-finished projects open-sourced to make way for bigger badder things, and I’m already finding this project quite useful so feedback and contributions are always welcome.

If you dont like to read, feel free to check out the short screencast.

Vendor Support

Nib currently supplies some cross-browser vendor support for properties, however this will surely expand and be refined as we go.

Enjoy The Little Things

Ever forget if nowrap is no-wrap, or whitespace over white-space? well I do, thanks to simple definitions like the following, these are completely interchangeable:

no-wrap = unquote('nowrap')

Augmented Border Radius

Border radius works as you might expected, however it is also augmented to support positioning. For example to round only the top of a box, use top 5px, or perhaps round the top and bottom differently with top 5px, buttom 10px. For example:

button {
  border-radius: top left 5px, bottom right 10px;
}

yields:

button {
  -moz-border-radius-topleft: 5px;
  -webkit-border-top-left-radius: 5px;
  border-top-left-radius: 5px;
  -moz-border-radius-bottomright: 10px;
  -webkit-border-bottom-right-radius: 10px;
  border-bottom-right-radius: 10px;
}

Position Properties

Nib has three position mixins or “custom properties”, fixed, absolute, and relative. These shorthands are helpful since you can define three properties in one concise, legible manner.

fixed: top left
fixed: top 5px left
fixed: top left 5px
absolute: top 5px left 5px
relative: left -5px

Expanding to:

position: fixed;
top: 0;
left: 0;

etc.

Gradients

Nib makes working with gradients easier than you could imagine. The following call to linear-gradient() duplicates the property to which it is assigned to (not bound only to “background”), and generates the appropriate webkit and mozilla output:

body
  background: linear-gradient(right bottom, white, 80% black)

yields:

body {
  background: -webkit-gradient(linear, right bottom, left top, color-stop(0, #fff), color-stop(0.8, #000));
  background: -moz-linear-gradient(right bottom, #fff 0%, #000 80%);
  background: linear-gradient(right bottom, #fff 0%, #000 80%);
}

You may pass as many color stops as you like, optionally providing a position before or after the color for each, whichever you prefer.

Gradient Image Generation

Another powerful feature of Nib, is the ability to utilize node-canvas when installed, and auto-generate a data URI representation of the gradient for further browser support. The image below displays google chrome’s render on the left, and node-canvas on the right.

gradients

The linear-gradient-image() function generates the data uri only:

body
  background: linear-gradient-image(50px top, white, black);

Alternatively we can pass the size to a regular call to linear-gradient() and the data uri will be created along with the other properties:

body
  background: linear-gradient(50px top, white, black);

Components

Nib currently ships with 5 or 6 sets of buttons, however I welcome the addition of other flexible components, and of course more buttons :). Below is an example of utilizing the “bold” button, assigning a glow which adjusts appropriately within the mixin.

.bold
  bold-button()

.bold-alternate
  bold-button(glow: #00ABFA)

glow button

Stylus 0.4.0 released

Stylus for those who are not familiar with it, is a dynamic language which compiles down to css, written with JavaScript for node.js.

CSS Syntax Support

Previously Stylus only allowed the use of our indented grammar, which may deter designers that are not comfortable learning or using a new syntax. For this reason and to aid in copy/pasting of css stylesheets I have added support for optional semi-colons, colons, commas, and braces, this means that most css is valid.

Example

The example below is completely valid, showing the use of mixins within both our indentation-style usage, and our css-style usage.

  vendor(prop, args)
    -webkit-{prop} args
    -moz-{prop} args
    {prop} args

  border-radius()
    vendor('border-radius', arguments)

  button
  a.button
  input[type=submit]
  input[type=button]
    border-radius 3px 5px
    color black
    &:hover
      background black
      color white

  button,
  a.button,
  input[type=submit],
  input[type=button] {
    border-radius: 3px 5px;
    color: black;
    &:hover {
      background: black;
      color: white;
    }
  }

yielding:

button,
a.button,
input[type=submit],
input[type=button] {
  color: #000;
  -webkit-border-radius: 3px 5px;
  -moz-border-radius: 3px 5px;
  border-radius: 3px 5px;
}
button:hover,
a.button:hover,
input[type=submit]:hover,
input[type=button]:hover {
  background: #000;
  color: #fff;
}
button,
a.button,
input[type=submit],
input[type=button] {
  color: #000;
  -webkit-border-radius: 3px 5px;
  -moz-border-radius: 3px 5px;
  border-radius: 3px 5px;
}
button:hover,
a.button:hover,
input[type=submit]:hover,
input[type=button]:hover {
  background: #000;
  color: #fff;
}

This of course applies to use within mixins as well, using the indentation-style we may define the mixin as:

  fade-to(to = .5)
    &:hover
      opacity to

  button
    fade-to(.1)

or with the css-style as:

  fade-to(to = .5)
    &:hover {
      opacity: to;
    }

  button {
    fade-to(.1);
  }

both yielding:

  button:hover {
    opacity: 0.1;
  }

Hope you enjoy the new feature, be sure to watch the project on GitHub to keep up to date with improvements.