Tag Archives: canvas

Aniziz and Zizim

The online part of the borrowed scenery project is an experiment in geotagging plants and plant related locations via a website/app called Zizim (the compass) combined with a multiplayer online game called Aniziz (the soil) where you can interact with the plants people have found. Having spent the last couple of months developing them, they are now ready for more of an open beta phase. Another part of the project is the forum here for collecting any feedback and thoughts.

Your role is to strengthen the connection between the world of Aniziz and the plants of Ghent. The plants are broadcasting messages which can only be correctly tuned into by energising them with fungi, the more plants you energise the higher your score will be.

The latest addition are specially tagged items called “pataportals” you can create with the android app which create “wormholes” in the Aniziz world. Stepping into one causes you to get sent to another one – which could be thousands of miles away. Right now Ghent is connected with the Cornish town of Penryn via a wormhole on the sea shore:

Aniziz: Keeping it local and independent

A large part of the Aniziz project involves porting the Germination X isometric engine from Haxe to Javascript, and trying to make things a bit more consistent and nicer to use as I go. One of the things I’ve tried to learn from experiments with functional reactive programming is using a more declarative style, and broadly trying to keep all the code associated with a thing in one place.

A good real world example is the growing ripple effect for Aniziz (which you can just about make out in the screenshot above), that consists of a single function that creates, animates and destroys itself without need for any references anywhere else:

game.prototype.trigger_ripple=function(x,y,z) {
    var that=this; // lexical scope in js annoyance

    var effect=new truffle.sprite_entity(
        this.world,
        new truffle.vec3(x,y,z),
        "images/grow.png");

    var len=1; // in seconds

    effect.spr.do_centre_middle_bottom=false;
    effect.finished_time=this.world.time+len;
    effect.needs_update=true; // will be updated every frame
    effect.spr.expand_bb=50; // expand the bbox as we're scaling up
    effect.spr.scale(new truffle.vec2(0.5,0.5)); // start small

    effect.every_frame=function() { 
        // fade out with time
        var a=(effect.finished_time-that.world.time);
        if (a>0) effect.spr.alpha=a; 
   
        // scale up with time
        var sc=1+that.world.delta*2; 
        effect.spr.scale(new truffle.vec2(sc,sc));

        // delete ourselves when done
        if (effect.finished_time<that.world.time) {
            effect.delete_me=true;
        }
    };
}

The other important lesson here is the use of time to make movement framerate independent. In this case it’s not critical as it’s an effect – but it’s good practice to always multiply time changing values by the time elapsed since the last frame (called delta). This means you can specify movement in pixels per second, and more importantly means that things will remain consistent in terms of interactivity within reason on slower machines.

Borrowed Scenery – spacesuits for plants and full screen scrolling in HTML5 canvas

Lots more of Theun’s new artwork which is coming on quickly over the last few days has been added to the game, the glass bubbles (more precisely cloches) are space suits for the plants from our world to live in, in their new patabotanical environment. Information about tagged plants is printed in a Voinych Manuscript inspired font.

I’ve been doing a bit more research into HTML5 canvas, turns out the the screen size doesn’t affect rendering framerate too much, and it now adapts to the browser size. Full screen scrolling is working – centring the view on the player. It wasn’t immediately obvious how this could be done without redrawing all the sprites each frame (which is way too slow), in this respect HTML5 development is really reminding me of 16bit era – hacking things like this to work quickly. It turns out it’s possible to use drawImage using the current canvas as it’s own input – so redrawing the whole thing with an offset:

function scroll(diff_x,diff_y) {
    // calculate the source and destination offsets - whether we offset
    // the source or destination rectangle depends on which direction 
    // we are travelling in 
    var sx=0;
    var dx=diff_x;
    var width=this.ctx.canvas.width-diff_x;
    if (diff_x<0) {
        sx=-diff_x;
        dx=0;
        width=this.ctx.canvas.width+diff_x;
    }
        
    var sy=0;
    var dy=diff_y;
    var height=this.ctx.canvas.height-diff_y;
    if (diff_y<0) {
        sy=-diff_y;
        dy=0;
        height=this.ctx.canvas.height+diff_y;
    }

    // remember to convert to ints, subpixel 
    // scrolling results in crazy artifacts
    this.ctx.drawImage(this.ctx.canvas,
                       ~~(sx),~~(sy),~~(width),~~(height),
                       ~~(dx),~~(dy),~~(width),~~(height));
}

Map rendering with OSM and HTML5 canvas

This week I’m trying to get as much code done on the borrowed scenery game as possible – today, fixing the map rendering.

I decided to load map tiles from OpenStreetMaps directly rather than using OpenLayers. This is done quite simply, accessing the millions of pre-rendered png files with URLs in this form:

a.tile.openstreetmaps.org/z/x/y.png

Where x and y are the tile coordinates and z is the zoom level. The question is, given a GPS coordinate – how do you find the right tiles? Luckily the OSM site is very helpful. This is what I’m using, where lat and lon are specified in degrees:

function latlon_to_tile(lat,lon,zoom) {
    with(Math){
        var m=pow(2,zoom);
        var lat_rad=lat*PI/180;
        return [floor((lon+180)/360*m),
                floor((1-Math.log(tan(lat_rad) + 
                       1/cos(lat_rad))/PI)/2*m)];
    }
}

I’m then doing quite a bit of work chopping the images up to create a 4×4 grid of game tiles for each map tile on the fly before the perspective transform. This brought up an issue with processing images loaded from domains other than the current server – try and do anything with the data you’ve loaded other than display it and this happens:

Unable to get image data from canvas because the canvas has been tainted by cross-origin data.

The answer is quite simple, as presumably it’s quite rare for this to actually cause vulnerabilities (perhaps if executing image data in some way?) you can simple add this property to the image:

image.crossOrigin = "anonymous";

And everything works as normal, cross origin data can be treated as if you’ve loaded it locally. I’ve also spent quite a bit of time fiddling around with the scaling to make the street names visible. There will probably be some more image processing to come. I also found another MMO game that used OSM data: Monopoly City Streets.

Clay mushrooms

More work on the Borrowed Scenery project and a first screenshot, experimenting with different visual styles. I’m trying modelling clay to get a 3D look on the fungi and using Theun Karelse’s mockup characters for player avatars. The floor is being built out of map tiles of Ghent pulled from OpenStreetMap, using the OpenLayers API which has been pretty fast to get running.

A lot is changing with code too, the client graphics are now entirely HTML5 canvas, the server is running a modified version of the Germination X game, switched to using websockets and upgraded versions of all the clojure libs.

Fast HTML5 sprite rendering

After quite a lot of experimentation with HTML5 canvas, I’ve figured out a way to use it with the kind of big isometric game worlds used for Germination X which are built from hundreds of overlapping sprites. There are lots of good resources out there on low level optimisations, but I needed to rethink my general approach in order to get some of these working.

It was quite obvious from the start that the simple clear screen & redraw everything way was going to be far too slow. Luckily HTML5 canvas gives us quite a lot of options for building less naive strategies.

A debug view of the game with 10 frames of changes shown with two plant spirits and one butterfly moving around.

The secret is only drawing the changes for each frame (called partial re-rendering in the link above). To do this we can calculate sprites which have changed and the ones they overlap with. The complication is maintaining the draw order and using clipping to keep the depth correct without needing to redraw everything else too.

In the game update we need to tag all the sprites which change position, rotation, scale, bitmap image, transparency etc.

Then in the render loop we build a list of all sprites that need redrawing, along with a list of bounding boxes for each overlapping sprite of the changed sprites that touch them. There may be more than one bounding box as a single sprite may need to be redrawn for multiple other changed sprites.

For each changed sprite:
    Get the bounding box for the changed sprite
    For each sprite which overlaps with this bounding box: 
        If overlapping sprite has already been stored:
            Add the bounding box to overlapping sprite's list 
        Else:
            Store overlapping sprite and current bounding box.
    Add the changed sprite to the list.

Assuming the sprites have been sorted into depth order, we now draw them using the list we have made (we would only need to loop over the redraw list if we built it in depth sorted order).

For each sprite:
    If it's in the redraw list:
        If it's not one of the originally changed sprites:
            Set a clipping rect for each bounding box.
        Draw the sprite.
        Turn off the clipping, if it was used.

With complex scenes and multiple moving objects, this algorithm means we only need to redraw a small percentage of the total sprites visible – and we start to approach Flash in terms of performance for games (I suspect that flash is doing something similar to this under the hood). The code is here, currently written in HaXE, but will probably end up being ported to Javascript.