Canvas Game Level Builder

This was kind of an interesting question. Someone was building a top-down game (ala Zelda) and wanted to supply a multi-dimensional array, with values that represent different ground types, and have that translated into a gameboard. This way, they could modify the array to create new levels:
var map=[[1,1,1,1,1,1,1,1,1,1],[1,3,0,0,0,0,2,4,0,1],[1,1,1,1,1,1,1,1,1,1]];
Here is the basic code I used, it edits the canvas size to match the map contents, then loops through the map, drawing each block:
var blocksize=30;
var can=document.getElementById('can')
if(can){ctx=can.getContext('2d');}
function init(){
can.width=map[0].length*blocksize;
can.height=map.length*blocksize;
for(y=0;y<map.length;y++){
for(x=0;x<map[y].length;x++){
draw(y,x);
}
}
can.addEventListener('click',builder);
}

function draw(y,x){
kind=map[y][x];
switch(kind){
case 0:
ctx.drawImage(floorimg,x*blocksize,y*blocksize);
break;
case 1:
ctx.drawImage(wallimg,x*blocksize,y*blocksize);
break;
case 2:
ctx.drawImage(blockimg,x*blocksize,y*blocksize);
break;
case 3:
ctx.drawImage(playerimg,x*blocksize,y*blocksize);
break;
case 4:
ctx.drawImage(goalimg,x*blocksize,y*blocksize);
break;
}
}

function ExtractNumber(value){
var n = parseInt(value);
return n == null || isNaN(n) ? 0 : n;
}
You'll see it's using images such as playerimg, which I'm leaving out here for brevity, but it's just along the lines of:
var floorimg=new Image();
floorimg.src=some_image_here;
Which would yield:
Sorry, your browser doesn't support HTML5 Canvas, please try another

So that's cool, change the values in the array (perhaps supply an entirely new map variable via AJAX?), new level. But their real request was that they'd be able to turn this into a level editor, so they'd be able to dynamically change the map by clicking. So, we needed to create a process that would 1) determine where the user clicked 2) convert that to a particular block of the canvas 3) redraw that block with the new texture value. You can see in the init() function near the top of this page that we added an event listener that would fire the builder function when the canvas is clicked. That code is shown here:
function builder(e){
if (e == null) {e = window.event;}
x = e.clientX; //where the click was
y = e.clientY;
offsetX = ExtractNumber(can.offsetLeft)-window.pageXOffset;//where the canvas is
offsetY = ExtractNumber(can.offsetTop)-window.pageYOffset;
x_grid=Math.floor((x-offsetX)/blocksize); //which block in the canvas was clicked
y_grid=Math.floor((y-offsetY)/blocksize);
map[y_grid][x_grid]++;
if(map[y_grid][x_grid]>4){map[y_grid][x_grid]=0;}
draw(y_grid,x_grid);
}

The offsetX and offsetY lines are a bit touchy, because offsetLeft and offsetTop are defined relative to the parent element, so if the canvas is within some other div which has position, it needs to be taken into account...there is this post that provides some other code that handles this dynamism. It may or may not be necessary depending on the design of your site.

You can see the map change if you play with it. The next step in that project was to create an AJAX call that would JSON.stringify the map variable, and submit it to a web service, which would append it to a text file (along with a level name)...later, that file could be retrieved in a similar manner, so levels could be loaded, edited, and saved.

The canvas element is quite versatile. I saw this game the other day and I'm hooked. Are you using canvas in any interesting projects?

HTML5 Canvas Sketch

I've been (slowly) working on an app for kids recently, and it's based around a drawing construct. While my first thought was 'this is going to be hard,' it turned out that it's a pretty straightforward space to work in. On day zero I found this dev.opera page about working with a canvas and drawing.

To be fair, that seemed to be the whole goal in a nutshell...did what I wanted, behaved logically, so I could build off of it, etc. But then someone working on a different drawing app (it's live: Dumpling) reached out for some guidance, and together we worked through a variety of sources (this was awhile ago now, but I'll add citation as I recall them) and streamlined it (from both a code, and graphical perspective).

The basic idea is that you take an HTML5 canvas element, and attach some event listeners for when the mouse button is pressed, the mouse is moved, the button is released...

So here was the start of my HTML:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Draw</title>
<style type="text/css">
body {background:#ccc; margin:0; padding:0}
html {margin:0; padding:0;}
#container { position: relative; margin:0; padding:0px; }
#canvas { border: 1px solid #000; position:absolute; width:298px; margin-left:11px; margin-top:5px; z-index:20; }
#under { border: 1px solid #000; position:absolute; width:298px; margin-left:11px; margin-top:5px; z-index:10; }
</style>
</head>

<body onload="listen()">
<div id="container">
<canvas id="canvas" width="298" height="298"></canvas>
<canvas id="under" width="298" height="298"></canvas>
</div>
</body>
You'll see I have two canvas elements atop one another. This is not the case in the dev.opera tutorial, but is a huge improvement on their approach.

Then we set up the listeners:

<script type="text/javascript">
var canvas,under,context,underCtx;
var points=new Array();
var iphone = ((window.navigator.userAgent.match('iPhone'))||(window.navigator.userAgent.match('iPod')))?true:false;
var ipad = (window.navigator.userAgent.match('iPad'))?true:false;
document.body.addEventListener('touchmove',function(event){ event.preventDefault(); });

function listen(){
canvas = document.getElementById('canvas');
under = document.getElementById('under');
if(canvas){
context= canvas.getContext('2d');
context.lineCap = 'round';
context.lineWidth=4;
if(iphone||ipad){
canvas.addEventListener('touchstart', strokeStart);
}
else{
canvas.addEventListener('mousedown', strokeStart);
}
}
if(under){
underCtx=under.getContext('2d');
underCtx.lineCap = 'round';
}
}
Oh, the reason you're not seeing things like an end to the SCRIPT is that I'm walking through a file, theoretically you could grab all of the code samples, concatenate them, and have the final page (but I'll give you that anyway, so don't bother).

Here you see a few things: a canvas element has a context (similar to a physical canvas having pigment on it), and those have attributes like line width, how line ends are handled, and so on. So I set up the different JS variables to match with the HTML elements and their contexts. I also use this:

document.body.addEventListener('touchmove',function(event){ event.preventDefault(); });

So that when something would have moved the body of the page, it doesn't respond (if I'm dragging my finger, I don't want it to move the page, but instead, want it to be recognized as drawing).

Then I test for iOS devices (that's the market my app will initially target, so you'd want to amend this if you're looking at Android), and begin setting up the event listeners. If it's iOS, I use touch events, otherwise, mouse events (assuming it's a browser based user...if this were really going to be distributed through iTunes [via a PhoneGap type wrapper] you could take those conditional elements out since you'd KNOW touch events would be required).

For those of you that aren't familiar with the syntax:

canvas.addEventListener('touchstart', strokeStart);
Means: "for canvas, if the touchstart event occurs, fire the strokeStart function"

So, what's in that function?

function strokeStart(ev) {
ev.preventDefault();
canvas.addEventListener('touchend', strokeEnd);
canvas.addEventListener('touchmove', stroke);
canvas.addEventListener('mousemove', stroke);
canvas.addEventListener('mouseup',   strokeEnd);
var a=new Object();
if (((iphone)||(ipad))&&(ev.touches[0])){ //iPad
a.x = ev.touches[0].clientX;
a.y = ev.touches[0].clientY;
}
else if (ev.layerX || ev.layerX == 0) { // Firefox
a.x = ev.layerX;
a.y = ev.layerY;
}
else if (ev.offsetX || ev.offsetX == 0) { // Opera
a.x = ev.offsetX;
a.y = ev.offsetY;
}
points.push(a);
}
So, I'm adding OTHER listeners (touchmove, touchend) and grabbing the coordinates of where that touch occurred, and then adding it to the points array. Why? You'll see in just a bit.

First let's look at what the other listeners are doing:

function stroke(ev) {
var a=new Object();
if (((iphone)||(ipad))&&(ev.touches[0])){ //iPad
a.x = ev.touches[0].clientX;
a.y = ev.touches[0].clientY;
}
else if (ev.layerX || ev.layerX == 0) { // Firefox
a.x = ev.layerX;
a.y = ev.layerY;
}
else if (ev.offsetX || ev.offsetX == 0) { // Opera
a.x = ev.offsetX;
a.y = ev.offsetY;
}
points.push(a);
context.clearRect(0, 0, canvas.width, canvas.height);
drawCurveStroke(context);
}

function strokeEnd() {
canvas.removeEventListener('touchend', strokeEnd, false);
canvas.removeEventListener('touchmove', stroke, false);
canvas.removeEventListener('mousemove', stroke, false);
canvas.removeEventListener('mouseup',   strokeEnd, false);
context.clearRect(0, 0, canvas.width, canvas.height);
//these three lines make sure the lower canvas has the same settings as the top canvas
underCtx.lineWidth = context.lineWidth;
underCtx.strokeStyle = context.strokeStyle;
underCtx.fillStyle = context.fillStyle;
drawCurveStroke(underCtx);
points = [];
}

So the stroke function (which fires each time the mouse/touch moves) grabs another coordinate, adds it to the points array, clears the context (of the top canvas) and then calls the drawCurveStroke function (with the context as a parameter). strokeEnd (which fires when the mouse button or finger lifts) removes some listeners, clears the context of the top canvas, and then calls the drawCurveStroke (with the underCtx as the parameter).

So what is going on? In the dev.opera example, there is just a single canvas, and each time a new coordinate is added to the drawing, all of of the line segments connecting preceding coordinates are re-stroked, causing a bit of graininess on the edges of the lines. What we're doing instead is continually clearing the context, so that the line we're drawing is only stroked one time (using the values in the points array). And then, when we lift our finger, that line is copied onto the lower canvas, so that the points array is only dealing with the current line being drawn.

Here is the drawCurveStroke function, I believe the core of it was from sketchfemme.com but I really can't find the exact nugget, that was something the other developer dug up:

function  drawCurveStroke(context) {
if (points.length < 1) {
return;
}
if (points.length < 6) {
var point = points[0];
context.beginPath();
context.arc(point.x, point.y,parseInt(context.lineWidth)/2, 0, Math.PI * 2, true);
context.closePath();
context.fill();
return;
}
context.beginPath();
// move to the first point
context.moveTo(points[0].x, points[0].y);
// curve through the rest, stopping at each midpoint
for (i = 1; i < points.length - 2; i ++)
{
var xc = (points[i].x + points[i + 1].x) / 2;
var yc = (points[i].y + points[i + 1].y) / 2;
context.quadraticCurveTo(points[i].x, points[i].y, xc, yc);
}
context.quadraticCurveTo(points[i].x, points[i].y, points[i+1].x,points[i+1].y);
context.stroke();
}
So the function determines how many points exist in the array, and then uses quadraticCurveTo between them if enough exist.

As promised, here is the consolidated page, you'll see that as you draw, the lines have a much more fluid aesthetic, particularly when compared to the rough lines in the dev.opera link (which reminds me of some sort of Apple IIGS paint program). Feel free to visit in your iOS device as well.

I'll leave it to you to add other controls, you'll want those to change the various settings of the upper canvas, which will then be mirrored to the lower on strokeEnd.

Working in Android? What changes are you making to the code to make it work? And, given the success of DrawSomething, what are your thoughts on other drawing apps (live, or concepts that could see the light of day)?