HTML5 Zone is brought to you in partnership with:

I was born in 1981 in one little city. Since I was 10y/o I programmed in different languages. My first languages were basic, then C++/MFC, after .Net (C#, VB.Net, J#, ASP.Net), XSL+XML processing). In the last 5 years I worked with web languages (HTML, CSS, PHP, SQL, XML, XSL, JavaScript). After university I worked in several different companies, eventually becoming a blogger. This is my hobby too. Andrey is a DZone MVB and is not an employee of DZone and has posted 110 posts at DZone. You can read more from them at their website. View Full User Profile

HTML5 Game Development – Lesson 9

04.12.2012
| 6003 views |
  • submit to reddit
Today we continue the series of articles on game development in HTML5 using canvas. For today I have prepared new game. I have updated our lesson 4 and added fireballs, enemies and collision detection. So, now our dragon can cast fireballs and kill enemies (plus, we have scores now). Now this game is much more interactive.

Our previous article you can read here: Developing Your First HTML5 Game – Lesson 8.

Here are our demo and downloadable package:

Live Demo

download in package

Ok, download the example files and let's start coding !

Step 1. HTML

First – our basic html code:

<!DOCTYPE html>
<html lang="en" >
    <head>
        <meta charset="utf-8" />
        <title>HTML5 Game Development - Lesson 9 | Script Tutorials</title>
        <link href="css/main.css" rel="stylesheet" type="text/css" />

        <!--[if lt IE 9]>
          <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->
        <script src="js/jquery.js"></script>
        <script src="js/script.js"></script>
    </head>
    <body>
        <header tabindex="0">
            <h2>HTML5 Game Development - Lesson 9</h2>
            <a  href="http://www.script-tutorials.com/html5-game-development-lesson-9/" class="stuts">Back to original tutorial on <span>Script Tutorials</span></a>
        </header>

        <div class="container">
            <canvas id="scene" width="1000" height="600" tabindex="1"></canvas>
        </div>
    </body>
</html>

Step 2. CSS

Here are the CSS styles.

css/main.css

I won’t publish styles today – it is just page layout styles, nothing interesting. Always available in package.

Step 3. JS

js/script.js
// inner variables
var canvas, ctx;
var backgroundImage;
var iBgShiftX = 100;

var dragon, enemy = null; // game objects
var balls = [];
var enemies = [];

var dragonW = 75; // dragon width
var dragonH = 70; // dragon height
var iSprPos = 0; // initial sprite frame
var iSprDir = 0; // initial dragon direction
var iEnemyW = 128; // enemy width
var iEnemyH = 128; // enemy height
var iBallSpeed = 10; // initial ball speed
var iEnemySpeed = 2; // initial enemy speed

var dragonSound; // dragon sound
var wingsSound; // wings sound
var explodeSound, explodeSound2; // explode sounds
var laughtSound; // wings sound

var bMouseDown = false; // mouse down state
var iLastMouseX = 0;
var iLastMouseY = 0;
var iScore = 0;
// -------------------------------------------------------------

// objects :
function Dragon(x, y, w, h, image) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.image = image;
    this.bDrag = false;
}
function Ball(x, y, w, h, speed, image) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.speed = speed;
    this.image = image;
}
function Enemy(x, y, w, h, speed, image) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.speed = speed;
    this.image = image;
}
// -------------------------------------------------------------
// get random number between X and Y
function getRand(x, y) {
    return Math.floor(Math.random()*y)+x;
}

// draw functions :
function drawScene() { // main drawScene function
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // clear canvas

    // draw background
    iBgShiftX += 4;
    if (iBgShiftX >= 1045) {
        iBgShiftX = 0;
    }
    ctx.drawImage(backgroundImage, 0 + iBgShiftX, 0, 1000, 940, 0, 0, 1000, 600);

    // update sprite positions
    iSprPos++;
    if (iSprPos >= 9) {
        iSprPos = 0;
    }

    // in case of mouse down - move dragon more close to our mouse
    if (bMouseDown) {
        if (iLastMouseX > dragon.x) {
            dragon.x += 5;
        }
        if (iLastMouseY > dragon.y) {
            dragon.y += 5;
        }
        if (iLastMouseX < dragon.x) {
            dragon.x -= 5;
        }
        if (iLastMouseY < dragon.y) {
            dragon.y -= 5;
        }
    }

    // draw dragon
    ctx.drawImage(dragon.image, iSprPos*dragon.w, iSprDir*dragon.h, dragon.w, dragon.h, dragon.x - dragon.w/2, dragon.y - dragon.h/2, dragon.w, dragon.h);

    // draw fireballs
    if (balls.length > 0) {
        for (var key in balls) {
            if (balls[key] != undefined) {
                ctx.drawImage(balls[key].image, balls[key].x, balls[key].y);
                balls[key].x += balls[key].speed;

                if (balls[key].x > canvas.width) {
                    delete balls[key];
                }
            }
        }
    }

    // draw enemies
    if (enemies.length > 0) {
        for (var ekey in enemies) {
            if (enemies[ekey] != undefined) {
                ctx.drawImage(enemies[ekey].image, enemies[ekey].x, enemies[ekey].y);
                enemies[ekey].x += enemies[ekey].speed;

                if (enemies[ekey].x < - iEnemyW) {
                    delete enemies[ekey];

                    // play laught sound
                    laughtSound.currentTime = 0;
                    laughtSound.play();
                }
            }
        }
    }

    // collision detection
    if (balls.length > 0) {
        for (var key in balls) {
            if (balls[key] != undefined) {

                if (enemies.length > 0) {
                    for (var ekey in enemies) {
                        if (enemies[ekey] != undefined && balls[key] != undefined) {
                            if (balls[key].x + balls[key].w > enemies[ekey].x && balls[key].y + balls[key].h > enemies[ekey].y && balls[key].y < enemies[ekey].y + enemies[ekey].h) {
                                delete enemies[ekey];
                                delete balls[key];
                                iScore++;

                                // play explode sound #2
                                explodeSound2.currentTime = 0;
                                explodeSound2.play();
                            }
                        }
                    }
                }
            }
        }
    }

    // draw score
    ctx.font = '16px Verdana';
    ctx.fillStyle = '#fff';
    ctx.fillText('Score: ' + iScore * 10, 900, 580);
    ctx.fillText('Plese click "1" to cast fireball', 100, 580);

}

// -------------------------------------------------------------

// initialization
$(function(){
    canvas = document.getElementById('scene');
    ctx = canvas.getContext('2d');

    var width = canvas.width;
    var height = canvas.height;

    // load background image
    backgroundImage = new Image();
    backgroundImage.src="images/hell.jpg";
    backgroundImage.onload = function() {
    }
    backgroundImage.onerror = function() {
        console.log('Error loading the background image.');
    }

    // 'Dragon' music init
    dragonSound = new Audio('media/dragon.wav');
    dragonSound.volume = 0.9;

    // 'Laught' music init
    laughtSound = new Audio('media/laught.wav');
    laughtSound.volume = 0.9;

    // 'Explode' music inits
    explodeSound = new Audio('media/explode1.wav');
    explodeSound.volume = 0.9;
    explodeSound2 = new Audio('media/explosion.wav');
    explodeSound2.volume = 0.9;

    // 'Wings' music init
    wingsSound = new Audio('media/wings.wav');
    wingsSound.volume = 0.9;
    wingsSound.addEventListener('ended', function() { // loop wings sound
        this.currentTime = 0;
        this.play();
    }, false);
    wingsSound.play();

    // initialization of empty ball
    var oBallImage = new Image();
    oBallImage.src="images/fireball.png";
    oBallImage.onload = function() { }

    // initialization of empty enemy
    var oEnemyImage = new Image();
    oEnemyImage.src="images/enemy.png";
    oEnemyImage.onload = function() { }

    // initialization of dragon
    var oDragonImage = new Image();
    oDragonImage.src="images/dragon.gif";
    oDragonImage.onload = function() {
        dragon = new Dragon(400, 300, dragonW, dragonH, oDragonImage);
    }

    $('#scene').mousedown(function(e) { // binding mousedown event (for dragging)
        var mouseX = e.layerX || 0;
        var mouseY = e.layerY || 0;
        if(e.originalEvent.layerX) { // changes for jquery 1.7
            mouseX = e.originalEvent.layerX;
            mouseY = e.originalEvent.layerY;
        }

        bMouseDown = true;

        if (mouseX > dragon.x- dragon.w/2 && mouseX < dragon.x- dragon.w/2 +dragon.w &&
            mouseY > dragon.y- dragon.h/2 && mouseY < dragon.y-dragon.h/2 +dragon.h) {

            dragon.bDrag = true;
            dragon.x = mouseX;
            dragon.y = mouseY;
        }
    });

    $('#scene').mousemove(function(e) { // binding mousemove event
        var mouseX = e.layerX || 0;
        var mouseY = e.layerY || 0;
        if(e.originalEvent.layerX) {
            mouseX = e.originalEvent.layerX;
            mouseY = e.originalEvent.layerY;
        }

        // saving last coordinates
        iLastMouseX = mouseX;
        iLastMouseY = mouseY;

        // perform dragon dragging
        if (dragon.bDrag) {
            dragon.x = mouseX;
            dragon.y = mouseY;
        }

        // change direction of dragon (depends on mouse position)
        if (mouseX > dragon.x && Math.abs(mouseY-dragon.y) < dragon.w/2) {
            iSprDir = 0;
        } else if (mouseX < dragon.x && Math.abs(mouseY-dragon.y) < dragon.w/2) {
            iSprDir = 4;
        } else if (mouseY > dragon.y && Math.abs(mouseX-dragon.x) < dragon.h/2) {
            iSprDir = 2;
        } else if (mouseY < dragon.y && Math.abs(mouseX-dragon.x) < dragon.h/2) {
            iSprDir = 6;
        } else if (mouseY < dragon.y && mouseX < dragon.x) {
            iSprDir = 5;
        } else if (mouseY < dragon.y && mouseX > dragon.x) {
            iSprDir = 7;
        } else if (mouseY > dragon.y && mouseX < dragon.x) {
            iSprDir = 3;
        } else if (mouseY > dragon.y && mouseX > dragon.x) {
            iSprDir = 1;
        }
    });

    $('#scene').mouseup(function(e) { // binding mouseup event
        dragon.bDrag = false;
        bMouseDown = false;

        // play dragon sound
        dragonSound.currentTime = 0;
        dragonSound.play();
    });

    $(window).keydown(function(event){ // keyboard alerts
        switch (event.keyCode) {
            case 49: // '1' key
                balls.push(new Ball(dragon.x, dragon.y, 32, 32, iBallSpeed, oBallImage));

                // play explode sound #1
                explodeSound.currentTime = 0;
                explodeSound.play();
                break;
        }
    });

    setInterval(drawScene, 30); // loop drawScene

    // generate enemies randomly
    var enTimer = null;
    function addEnemy() {
        clearInterval(enTimer);

        var randY = getRand(0, canvas.height - iEnemyH);
        enemies.push(new Enemy(canvas.width, randY, iEnemyW, iEnemyH, - iEnemySpeed, oEnemyImage));

        var interval = getRand(5000, 10000);
        enTimer = setInterval(addEnemy, interval); // loop drawScene
    }
    addEnemy();
});

In the beginning I added two new objects: Ball (or fireball) and Enemy. Each object has own attributes set (like position, size, image, speed). After, I added drawing balls and enemies for our ‘drawScene’ function. Plus, at the bottom of this function you can see the collision detection method:

// collision detection
if (balls.length > 0) {
    for (var key in balls) {
        if (balls[key] != undefined) {

            if (enemies.length > 0) {
                for (var ekey in enemies) {
                    if (enemies[ekey] != undefined && balls[key] != undefined) {
                        if (balls[key].x + balls[key].w > enemies[ekey].x && balls[key].y + balls[key].h > enemies[ekey].y && balls[key].y < enemies[ekey].y + enemies[ekey].h) {
                            delete enemies[ekey];
                            delete balls[key];
                            iScore++;

                            // play explode sound #2
                            explodeSound2.currentTime = 0;
                            explodeSound2.play();
                        }
                    }
                }
            }
        }
    }
}

And finally, we have to add our enemies periodically (randomly):

// generate enemies randomly
var enTimer = null;
function addEnemy() {
    clearInterval(enTimer);

    var randY = getRand(0, canvas.height - iEnemyH);
    enemies.push(new Enemy(canvas.width, randY, iEnemyW, iEnemyH, - iEnemySpeed, oEnemyImage));

    var interval = getRand(5000, 10000);
    enTimer = setInterval(addEnemy, interval); // loop drawScene
}
addEnemy();

Step 4. Custom files

images/dragon.gif, images/enemy.png, images/fireball.png, images/hell.jpgmedia/dragon.wav, media/explode1.wav, media/explosion.wav, media/laught.wav, media/wings.wav

All these files available in our package.

Live Demo

download in package

Conclusion

Do you like our new updated game? :-) I will be glad to see your thanks and comments. Good luck!

Published at DZone with permission of Andrey Prikaznov, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)