Easy HTML5 canvas and CSS Sprite texture atlas

Texture atlas is a collection of images, all composed in single large image. The game or HTML5 app can load this single image instead of wasting time for requesting every small image separately.

I wrote tool that can be used to package collection of images to single atlas and let web app use that conveniently both as CSS sprite or in HTML5 canvas drawing.

Tool itself is Python script that uses ImageMagick commands to read and write images. The output is the atlas image, CSS, raw JSON and Javascript import file. CSS file defines CSS sprite for each image. JSON is raw data of each images position in the atlas, Javascript import file can be included on HTML page and it loads the atlas position info into global variable.

Setup

Get script from here: https://raw.github.com/tikonen/blog/master/packer/packer.py

Make it executable

$ chmod +x packer.py

Verify that all is ok

~/work/blog/packer $ ./packer.py -h
usage: packer.py [-h] [-o OUTFILE] [-jo JSONOUTFILE] [-jso JSOUTFILE]
                 [-co CSSOUTFILE] [-p PAD] [-mw WIDTH] [-mh HEIGHT]
                 FILE [FILE ...]

Packs images to atlas. Uses ImageMagick to parse and compose the images

positional arguments:
  FILE             Image file

optional arguments:
  -h, --help       show this help message and exit
  -o OUTFILE       Output atlas file
  -jo JSONOUTFILE  Output atlas json file
  -jso JSOUTFILE   Output atlas import js file
  -co CSSOUTFILE   Output atlas css file
  -p PAD           Padding
  -mw WIDTH        Maximum width
  -mh HEIGHT       Maximum height

Install ImageMagick easily on OS/X with Macports or Homebrew.

$ sudo port install ImageMagick

Verify that ImageMagick installation works

$ identify --version
Version: ImageMagick 6.8.7-3 2013-10-28 Q16 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2013 ImageMagick Studio LLC
Features: DPC
Delegates: bzlib djvu fftw fontconfig freetype gslib jng jpeg lcms ltdl lzma png ps png tiff webp x xml zlib

Usage

You use script by giving image file as arguments and defining the desired output files and maximum image dimensions

For example:

$ ./packer.py img/*.png -o sprites.png

This would produce sprites.png, sprites.json, sprites.css and sprites.json.js

Options
Following options are supported

  • -p PAD
    Defines padding. The amount of empty pixels around each image. This prevents scaling artifacts on HTML canvas use when drawing from decimal source coordinates.
  • -mw WIDTH,-mh HEIGHT
    Maximum width and height of output image.
  • -o FILE
    Output image file.
  • -jo FILE, -jso FILE, -co FILE
    Output JS import, JSON and CSS file.

Example

We have following small images that are needed in our web app. Loading them all independently would take 5 HTTP requests.

  • button_minus.png
  • button_plus.png
  • cannon_marker.png
  • pause.png
  • status_bar.png

montage

Running tool converts them to single atlas

~/work/blog/packer $ ./packer.py example/pics/* -o example/html/sprites.png -mw 512
Checking ImageMagick
Found: Version: ImageMagick 6.8.7-3 2013-10-28 Q16 http://www.imagemagick.org
===========================
Resolving file dimensions
button_minus.png -> 53x43
button_plus.png -> 53x42
cannon_marker.png -> 43x28
pause.png -> 53x42
status_bar.png -> 496x74
===========================
fitting 5 images, padding 1
successfully fitted 5 images to 496x118 padding 1
Wrote: atlas to example/html/sprites.png
Wrote json to example/html/sprites.json
Wrote js to example/html/sprites.json.js
Wrote css to example/html/sprites.css

In this example we defined maximum width of 512 pixels. The resulting image is cropped to minimum size 496×118.

sprites
This atlas can be used now in two different ways on the web page.

CSS Sprite

Web page includes the generated CSS file and defines class by filename (e.g. “bg-sprites status_bar” for each element that uses sprite.

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <link rel="stylesheet" type="text/css" href="sprites.css">
    <style type="text/css">
        .bg-sprites {
            color: white;
            text-align: center;
        }
    </style>
</head>
<body>
    <div>
        <h1>Example</h1>
        <div>
            <h2>Cannon - simple sprite with image</h2>
            <div class="bg-sprites cannon_marker">
            </div>
            <h2>Status bar - sprite with text inside</h2>
            <div class="bg-sprites status_bar">
                <span style="font-size:xx-large;position:relative;top:15px;">Here is some text</span>
            </div>
            <h2>Buttons - list</h2>
            <div>
                <span style="float:left;margin:5px;" class="bg-sprites button_minus"></span>
                <span style="float:left;margin:5px;" class="bg-sprites button_plus"></span>
                <span style="float:left;margin:5px;" class="bg-sprites pause"></span>
            </div>
        </div>
    </div>
</body>

This renders following page:
example css sprite

See example page here: http://ikonen.me/examples/packer/example_css.html

HTML5 Canvas

Another option is to use it with HTML canvas. Page links the generated JSON import script and loads the image.

<!DOCTYPE html>
<html>
<head>
    <title>Example</title>
    <style type="text/css">
        canvas {
            width: 600px;
            height: 400px;
        }
    </style>
</head>
<body>
    <div>
        <h1>Canvas Example</h1>
        <div>
            <canvas id="thecanvas" width="600" height="400"></canvas>
        </div>
    </div>
  <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" ></script>
  <script type="text/javascript" src="sprites.json.js"></script>
  <script type="text/javascript">
      $(function() {
          var canvas = document.getElementById('thecanvas');
          var ctx = canvas.getContext('2d');
          ctx.strokeStyle = 'hotpink';
          ctx.strokeRect(0, 0, 600, 400);
          ctx.font = '20px Arial';

          // Load image and the json that defines locations
          var sprites = new Image();
          sprites.src = 'sprites.png';
          sprites.addEventListener("load", function() {
                  var assets = bg_sprites; // imported by sprites.json.js

                  // draw them all
                  var xoffset = 5,
                      yoffset = 25;

                  for (var pic in assets) {
                      var asset = assets[pic];
                      ctx.fillText(pic, xoffset, yoffset-3);

                      // draw image from sprite
                      ctx.drawImage(sprites, asset.x, asset.y, asset.w, asset.h, xoffset, yoffset, asset.w, asset.h);

                      yoffset += asset.h + 20;
                  }
          }, false);
      });
  </script>
</body>
</html>

This renders following page:

example canvas

See example page here: http://ikonen.me/examples/packer/example_canvas.html

Get complete example from Github.

Simple Slot machine game using HTML5 Part 4: Offline mode

This is the fourth part of the Slot machine game in HTML5 (previous parts 1, 2, and 3) and this time we modify the game to support HTML5 offline mode, also known as HTML5 Application Cache.

Slots Offline

Try out offline supported version here.

Word of warning. HTML5 offline mode is powerful but very fragile feature. It’s tricky to get right, but once you get it to work the mobile user experience can be very native app like.

Some problems you will encounter

  • Browser refresh logic is confusing. Especially the fact that browser does not use updated manifest and resources when they change but only after next reload. Fortunately Javascript workarounds exists.
  • Application cache file maintenance needs diligence. For example, browser will not reload any assets if this file is not modified.
  • Externally linked resources do not generally work offline. This makes CDN use difficult.
  • Web server has to use right MIME type and cache settings to reliably use application cache files. Most web servers don’t do this in default configuration.
  • No reliable way to detect if page was loaded in online or offline mode.
  • Chrome bypasses some restrictions (e.g. cross-domain issues) in the specification and what works in Chrome may not work anywhere else.
  • Each browser has slightly different meaning and heuristic for using offline mode. For example if browser can load some unrelated pages but can’t currently load your app page it may not show your page in offline mode and simply shows “Can not reach the server error”. This may happen especially if it knows from last load that manifest has been updated. Then at other times, it may load page few times in offline mode even when connectivity has returned.

Manifest file

Web page must use application cache manifest file to support offline mode. This manifest file is specified in html tag of the page.

<!DOCTYPE html>
<html manifest="slots.appcache">
<head>
 ...

The file has listing of all content that page needs. For detailed explanation of each section, refer to Beginners guide to HTML5 application cache

CACHE MANIFEST
# version 10

NETWORK:
# here goes resources that must be never cached
js/online.json

CACHE:
audio/nowin.mp3
audio/nowin.ogg
audio/roll.mp3
audio/roll.ogg
audio/slot.mp3
audio/slot.ogg
audio/win.mp3
audio/win.ogg
css/webfonts.css
css/reset.css
css/slot.css
css/Slackey.ttf
img/build-64.png
img/cash-64.png
img/energy-64.png
img/gold-64.png
img/goods-64.png
img/loading.gif
img/staff-64.png
js/slot.js
js/jquery.min.js
index.html

Web Fonts

Web fonts must be hosted locally if you want to use them offline. Note that some web fonts may have licensing restrictions for local hosting.

<link type='text/css' rel='stylesheet' href="css/webfonts.css"/>

The webfont.css defines the font face and loads true type file.

@font-face {
  font-family: 'Slackey';
  font-style: normal;
  font-weight: 400;
  src: local('Slackey'), url(Slackey.ttf) format('truetype');
}

The file Slackey.ttf is hosted locally in css directory.

Web Server Support

Web server must use correct MIME type for text/cache-manifest application cache manifest. For example, in NGINX web server edit the mime.types and add following file type to MIME type mapping.

text/cache-manifest		appcache;

Browsers should check manifest every time page is loaded online, but it may not do this often enough if cache control is too long. Therefore, set short cache lifetime for the manifest files by adding this inside server section of NGINX configuration file. This forces cache lifetime of 1 minute to all *.appcache files.

# set 1 minute cache life for HTML5 offline manifests
location ~* \.(appcache)$ {
   expires 1m;
}

Verify with cURL that server response Content-Type has right MIME type and that the Expires and/or Cache-Control have correct 1 minute cache life time. If you get 404 error, make sure that site root configuration is set in http section.

$ curl -I http://localhost:8081/slots.appcache
HTTP/1.1 200 OK
Server: nginx/1.4.0
Date: Sun, 05 May 2013 04:43:32 GMT
Content-Type: text/cache-manifest
Content-Length: 38
Last-Modified: Sun, 05 May 2013 04:25:28 GMT
Connection: keep-alive
ETag: "5185df38-26"
Expires: Sun, 05 May 2013 04:44:32 GMT
Cache-Control: max-age=60
Accept-Ranges: bytes

Detecting online status

Currently only “reliable” method to do is to make Ajax request and check the response. There are some caveats

  • Request may fail for other reasons, and this does not mean browser is in offline mode
  • Offline status may change while user is in page, you may want to do repeat polling check.
  • User may be in public WiFi that redirects requests to login server. This can confuse your app that gets response but is not what was expected.

Slots game checks online status in parallel while game loads and only on startup. Slots game does not really need to know if it’s online or offline, but just writes the status on screen for debugging purposes.

<script type="text/javascript">$(function () {

    var game = SlotGame();

    // Attempt loading static json file from server to detect online or offline mode.
    // The url has unique random parameter to avoid browser or proxy caches
    $.ajax({
        url: 'js/online.json?ts=' + (~~new Date()),
        dataType: 'json',
        success: function(data) { 
            if ( data.online ) { 
                game.setOnlineStatus(true);
            } else {
                // might be online, but we didn't get expected response. Could be
                // e.g. Wifi login page.
                game.setOnlineStatus(false);
            }
        },
        error: function() {
            game.setOnlineStatus(false);
        }
    });

});</script>

Otherwise loading images, audio and other content should be fully transparent to your app. Think twice before doing separate logic for online and offline as things will get difficult. Best advice I can give is to that you write code for online use with proper handling for Ajax errors. In this way when app loads in online mode but loses network later in session (e.g. when user goes in subway tunnel), the experience does not break completely.

Testing

This is the part where things get interesting, offline is tricky to test because of caching and browser reload logic. See detailed lamentation about subject here in Dive into HTML5.

These are the best practices I’ve come up with. First, set browser manually to offline mode to try things out. e.g. in Firefox this is enabled from File->Work Offline.

Firefox offline

Second, if you develop the game from local server, do not use http://localhost as host, but use real domain name that resolves to localhost. In this example I’ve used http://hexxie.com that supports wildcard subdomain. Any subdomain resolves to address 127.0.0.1.

$ nslookup anything-goes-here.hexxie.com
Server:		192.168.1.254
Address:	192.168.1.254#53

Non-authoritative answer:
Name:	anything-goes-here.hexxie.com
Address: 127.0.0.1

In this way you can always start from scratch the offline debugging simply by changing subdomain name. For example I just used slotsoff1.hexxie.com, slotsoff2.hexxie.com, … etc.:

Trick url

Note that at least Firefox asks each time if you allow offline content.
Accept offline

Before each deploy, remember to increment the version comment in manifest file, so web server notices that the file has changed and browser will refresh it on next load. Server does not look inside the manifest file, so it does not matter how you change the file, as long as it’s changed.

Good Luck!

Code is available in Github.

Car Game on HTML5

This is continuation to my previous blog post, that discussed how to make simple Slots machine with HTML5. The basic principle in this driving game demo is similar, but in addition to that it adds dynamic graphics using canvas.

HTML5 Drive

Try it out here: http://ikonen.me/examples/drive/

Game road is div with background image that is translated down and back up to create illusion of moving road. This same method is used in Slots machine.

Car spritecar2

Cars are drawn on canvas on top of the road so while road moves cars appear moving forward. Each click starts moving the player car from current point to the click point using smooth  Bezier curve. Car acceleration is increased or decreased depending on click location. Player car also has smoke plume animation which intensity depends on acceleration.

function _bezier_quad(t, p0, p1, p2) {
     return {
         x: (1 - t)*( (1 -t) * p0.x + t*p1.x) + t * ( (1 - t) * p1.x + t * p2.x),
         y: (1 - t)*( (1 -t) * p0.y + t*p1.y) + t * ( (1 - t) * p1.y + t * p2.y)
     }
 }

After car has travelled long enough, the update loop starts slowing down road speed and moves in sync  div that holds Finish text and flags.

finish

Collision detection is done on each update. In case player hits on other cars, the other cars may blurb random bubbles that are added as normal div elements with rounded borders. Element location is updated based on car location.

bubble

In collision both cars are bumped so player can make room by hitting to other cars. Game loop is run by animation frames, so it stops if browser window goes in background. This is not optimal but makes implementation simpler. Better option is to run update with setInterval and draw on animation frames.

...
    var that = this;
    (function gameLoop() {
        that.clear(); // clear canvas objects
        that.update(); // update game state
        that.draw(); // draw game
        requestAnimFrame( gameLoop );
    })();
...

Update and draw in separate loops. This is better solution for complex games because browser will always call setInterval callback but requestAnimFrame callback only if browser window is visible and active. Callback is not executed if browser window is minimized or it’s tab is on background. If you rely only on requestAnimFrame the game is paused on background.

    var that = this;
    setInterval(function() {
        that.update(); // update game state
    }, 1000/30); // 30 frames

    (function gameLoop() {
        that.clear(); // clear canvas objects
        that.draw(); // draw game
        requestAnimFrame( gameLoop );
    })();

Game code is available in GitHub.

Simple Slot machine game using HTML5 Part 1: Basics

UPDATE: See also Simple Slot machine game using HTML5 Part 2: Audio.

Here is overview on how to make simple Slot machine with HTML5. This demonstrates the basic structure of HTML5 game and how to use dynamically created graphics.

Slot machine has typically reels with images and player just initiates the action and waits until reels stop. 1 or more in single line usually determine the winning condition. In this game player wins if he or she gets more than one gold bar in row.

Here is view of the game.

Slots machine

You can try it out here http://ikonen.me/examples/slot/.

How it works

Slot machine is a single HTML page that includes the game code, webfont and jQuery. When loaded it runs SlotGame() function that initializes and runs the game.

HTML page has 3 narrow and tall HTML5 canvases, these are the reels. They are located inside div container “reels” that shows only a limited window at any time, hiding the rest of the canvases.

<div id="reels">
   <canvas id="canvas1" width="70" height="300"></canvas>;
   <canvas id="canvas2" width="70" height="300"></canvas>;
   <canvas id="canvas3" width="70" height="300"></canvas>;
</div>

On initialization, game preloads the 6 image assets. Preloading is simply done by creating Image object for each asset and listening its load event. Preloading is required, because otherwise game could not draw the reel canvases on initialization.

img = new Image()
img.src = "img/someimage.png"
img.addEventListener("load", function() {
   // image loaded
});

Game draws the pictures in random order on each canvas reel with shadow and slot separator bars.

ctx.save();
ctx.shadowColor = "rgba(0,0,0,0.5)";
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
ctx.shadowBlur = 5;
ctx.drawImage(asset.img, 3, i * SLOT_HEIGHT + IMAGE_TOP_MARGIN);
ctx.drawImage(asset.img, 3, (i + ITEM_COUNT) * SLOT_HEIGHT + IMAGE_TOP_MARGIN);
ctx.restore();
ctx.fillRect(0, i * SLOT_HEIGHT, 70, SLOT_SEPARATOR_HEIGHT);
ctx.fillRect(0, (i + ITEM_COUNT)  * SLOT_HEIGHT, 70, SLOT_SEPARATOR_HEIGHT);

The reels are not redrawn after this, but when moving they are simply translated with CSS3 transform downwards and when they reach threshold they are moved back to beginning. Threshold and reset offset is selected so that after the reset images are shown on same locations. This creates illusion of constantly rotating wheel. This is why images on borders are twice in the reel, so we avoid showing canvas bottom or top in any situation.
Click ‘Toggle Reels’ button on top left corner of the game page to toggle the reel visibility while it’s spinning, seeing reels in action makes explanation above much easier to understand

Here is image where reel container overflow is set to visible.

Reels

Game loop is simple, it starts when player clicks ‘Play’ and runs on every animation frame updating the reel locations based on game state and “draws” them on screen, or actually as explained earlier just translates their locations. Result is predetermined on each roll start and when each reel stop, its locked on the correct image. Update loop tries to make this when correct image is close to this location, so the jump is not too abrupt (see function _check_slot in slot.js for details).

Each browser has still different name for the transform so initialization code determines the correct CSS name and if browser has hardware accelerated 3d version.

this.vendor = 
  (/webkit/i).test(navigator.appVersion) ? '-webkit' :
  (/firefox/i).test(navigator.userAgent) ? '-moz' :
  (/msie/i).test(navigator.userAgent) ? 'ms' :
   'opera' in window ? '-o' : '';

this.cssTransform = this.vendor + '-transform';
this.has3d = ('WebKitCSSMatrix' in window &amp;&amp; 'm11' in new WebKitCSSMatrix())  
this.trnOpen       = 'translate' + (this.has3d ? '3d(' : '(');
this.trnClose      = this.has3d ? ',0)' : ')';
...
$('#someelement').css(this.cssTransform, this.trnOpen + '0px, ' + '-123px' + this.trnClose);</pre>

Complete code is available on Github.

Continue to Simple Slot machine game using HTML5 Part 2: Audio