Making HTML5 Canvas Canvas

Sunday, August 18, 2013

Part 3. Using the keyboard

Our rectangle moves now trough the canvas, but to really interact with it, we need to tell it where we want it to go. For this, we need first a variable where we store the last pressed key:
var lastPress=null;
And add at the end of our code a listener for the keyboard, which will store the last pressed key:
document.addEventListener('keydown',function(evt){
    lastPress=evt.keyCode;
},false);
Through this method, we can make choices on the game, knowing the last pressed key. Each key has a numeric value, which we will need to compare to make the desired action, depending on the last key pressed. A good way to know which has been the last pressed key, is adding the next line on the "paint" function:
ctx.fillText('Last Press: '+lastPress,0,20);
Right now, you don't have to worry about this. We will use the left, up, right and down keys, which values are 37, 38, 39, and 40 respectively. For easier use, we will save them on constant values:
var KEY_LEFT=37;
var KEY_UP=38;
var KEY_RIGHT=39;
var KEY_DOWN=40;
Unlike other programming languages, JavaScript doesn't have constants, but variables will do the job with no problem.

Now we go to the movement of the rectangle. First, we will need a new variable that stores the direction of our rectangle:
var dir=0;
This "dir" variable will have a value from 0 to 3, being 0 up, and rotating clockwise for the other values every quarter of hour.

We will now use a simple method to have a consistent time among devices (You can read more at Appendix 3: Consistent time among devices). The easiest way for now to do this, is dividing the act and paint functions in two different callbacks, one optimized for the repainting, and one regulated for the actions:
function run(){
    setTimeout(run,50);
    act();
}

function repaint(){
    requestAnimationFrame(repaint);
    paint(ctx);
}
As you can see inside the run function, we call a timer "setTimeout", which calls the run funcion again after 50 milliseconds. This is a simple way to get a game at 20 cicles per second.

Note that, as this makes the "act" function cicle asyncronous from the "repaint" function, you can't trust that what you do on the first, will be drawn on the second. But this is not usually a problem when the repaint is faster than the act cicle. Don't forget to call both functions at the init function!

Now let's start by detecting which direction our rectangle will take, depending on the last pressed key, inside the "act" function:
    // Change Direction
    if(lastPress==KEY_UP)
        dir=0;
    if(lastPress==KEY_RIGHT)
        dir=1;
    if(lastPress==KEY_DOWN)
        dir=2;
    if(lastPress==KEY_LEFT)
        dir=3;
Then, we move the rectangle, depending the direction taken:
    // Move Rect
    if(dir==0)
        y-=10;
    if(dir==1)
        x+=10;
    if(dir==2)
        y+=10;
    if(dir==3)
        x-=10;
Finally, we will search if the rectangle has gone out of the canvas, and in such case, we will return it to the same:
    // Out Screen
    if(x>canvas.width)
        x=0;
    if(y>canvas.height)
        y=0;
    if(x<0)
        x=canvas.width;
    if(y<0)
        y=canvas.height;
You may have noticed that before every code block, I added a reference preceded by two slashes (//). This is a comment, and they are very functional to describe what happens on each section of the code, so, if we need to modify it later, we can easily identify it's components.

Comments also work to "delete" code lines, but that we could want to use the later, for example, the one that paints on the screen which was the last key pressed (We wouldn't want that people playing our game sees that, right?).

Save the game and open "index.html" If everything is right, now you will be able to control the little rectangle with the keyboard. Congratulations!

Pause


We could now conclude today's lesson, but as we are learning to use the keyboard, I'll teach you a little trick to "pause" a game. We will start of course, declaring a variable that contains where the game is at pause or not:
var pause=true;
Now we put all the content on the "act" function inside a condition "if(!pause)", which is, if the game is not on pause. Until now, all conditions have had only one line, therefore we haven't use curly braces. But in the case that more than one line is used in a condition, you must use curly braces the same way as in functions, just as we will do in the current case.

At the end of the condition "if(!pause)", we will add these lines so, when pressing KEY_ENTER (13), the game toggles between paused and unpaused:
    // Pause/Unpause
    if(lastPress==KEY_ENTER){
        pause=!pause;
        lastPress=null;
    }
Is very important that these lines are not inside the condition "if(!pause)", otherwise, you will never be able to unpause the game (because you will never enter that part of the code). The assignation "pause=!pause" sets the new value to the opposite of the last one (false if it is true or true if it is false), and then we nullify "lastPress", otherwise, the game would be toggling the pause endless until another key is pressed.

Finally, we will paint in the "paint" function, at the center, the text "PAUSE" if the pause is active:
    if(pause){
        ctx.textAlign='center';
        ctx.fillText('PAUSE',150,75);
        ctx.textAlign='left';
    }
We update the game. Now every time we press the Enter key, the game will pause or unpause.

Final code:

[Canvas not supported by your browser]
window.addEventListener('load',init,false);
var canvas=null,ctx=null;
var x=50,y=50;
var lastPress=null;
var pause=true;
var dir=0;

var KEY_ENTER=13;
var KEY_LEFT=37;
var KEY_UP=38;
var KEY_RIGHT=39;
var KEY_DOWN=40;

function init(){
    canvas=document.getElementById('canvas');
    canvas.style.background='#000';
    ctx=canvas.getContext('2d');
    run();
    repaint();
}

function run(){
    setTimeout(run,50);
    act();
}

function repaint(){
    requestAnimationFrame(repaint);
    paint(ctx);
}

function act(){
    if(!pause){
        // Change Direction
        if(lastPress==KEY_UP)
            dir=0;
        if(lastPress==KEY_RIGHT)
            dir=1;
        if(lastPress==KEY_DOWN)
            dir=2;
        if(lastPress==KEY_LEFT)
            dir=3;

        // Move Rect
        if(dir==0)
            y-=10;
        if(dir==1)
            x+=10;
        if(dir==2)
            y+=10;
        if(dir==3)
            x-=10;

        // Out Screen
        if(x>canvas.width)
            x=0;
        if(y>canvas.height)
            y=0;
        if(x<0)
            x=canvas.width;
        if(y<0)
            y=canvas.height;
    }
    // Pause/Unpause
    if(lastPress==KEY_ENTER){
        pause=!pause;
        lastPress=null;
    }
}

function paint(ctx){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.fillStyle='#0f0';
    ctx.fillRect(x,y,10,10);
    
    ctx.fillStyle='#fff';
    //ctx.fillText('Last Press: '+lastPress,0,20);
    if(pause){
        ctx.textAlign='center';
        ctx.fillText('PAUSE',150,75);
        ctx.textAlign='left';
    }
}

document.addEventListener('keydown',function(evt){
    lastPress=evt.keyCode;
},false);

window.requestAnimationFrame=(function(){
    return window.requestAnimationFrame || 
        window.webkitRequestAnimationFrame || 
        window.mozRequestAnimationFrame || 
        function(callback){window.setTimeout(callback,17);};
})();

No comments:

Post a Comment