HTML5 Game Development – Lesson 7
Today we will create our first complete game – Arkanoid. In this lesson I will show you how to detect basic collisions and work with HTML5 local storage. You can operate with pad using mouse and keyboard (left/right buttons). We will store in local storage elapsed time of previous game and amount of broken bricks (points).
Here you can read our previous lesson: Developing Your First HTML5 Game – Lesson 6.
Here are our demo and downloadable package:
Live Demo
download in package
Ok, download the example files and lets start coding !
Step 1. HTML
Here is source code of our seventh lesson
index.html
<!DOCTYPE html> <html lang="en" > <head> <meta charset="utf-8" /> <title>HTML5 Game Development - Lesson 7 | Script Tutorials</title> <link href="css/main.css" rel="stylesheet" type="text/css" /> <script src="js/jquery-1.5.2.min.js"></script> <script src="js/script.js"></script> </head> <body> <header> <h2>HTML5 Game Development - Lesson 7</h2> <a href="http://script-tutorials.com/html5-game-development-lesson-7/" class="stuts">Back to original tutorial on <span>Script Tutorials</span></a> </header> <div class="container"> <canvas id="scene" width="800" height="600"></canvas> </div> </body> </html>
Step 2. CSS
Here are used CSS styles.
css/main.css
/* page layout styles */ *{ margin:0; padding:0; } body { background-color:#eee; color:#fff; font:14px/1.3 Arial,sans-serif; } header { background-color:#212121; box-shadow: 0 -1px 2px #111111; display:block; height:70px; position:relative; width:100%; z-index:100; } header h2{ font-size:22px; font-weight:normal; left:50%; margin-left:-400px; padding:22px 0; position:absolute; width:540px; } header a.stuts,a.stuts:visited{ border:none; text-decoration:none; color:#fcfcfc; font-size:14px; left:50%; line-height:31px; margin:23px 0 0 110px; position:absolute; top:0; } header .stuts span { font-size:22px; font-weight:bold; margin-left:5px; } .container { margin: 20px auto; overflow: hidden; position: relative; width: 800px; }
Step 3. JS
js/jquery-1.5.2.min.js
We use jQuery for our lesson. Available in package. Next file most important (here are all our html5 functional):
js/script.js
// inner variables var canvas, ctx; var iStart = 0; var bRightBut = false; var bLeftBut = false; var oBall, oPadd, oBricks; var aSounds = []; var iPoints = 0; var iGameTimer; var iElapsed = iMin = iSec = 0; var sLastTime, sLastPoints; // objects : function Ball(x, y, dx, dy, r) { this.x = x; this.y = y; this.dx = dx; this.dy = dy; this.r = r; } function Padd(x, w, h, img) { this.x = x; this.w = w; this.h = h; this.img = img; } function Bricks(w, h, r, c, p) { this.w = w; this.h = h; this.r = r; // rows this.c = c; // cols this.p = p; // padd this.objs; this.colors = ['#9d9d9d', '#f80207', '#feff01', '#0072ff', '#fc01fc', '#03fe03']; // colors for rows } // ------------------------------------------------------------- // draw functions : function clear() { // clear canvas function ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // fill background ctx.fillStyle = '#111'; ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); } function drawScene() { // main drawScene function clear(); // clear canvas // draw Ball (circle) ctx.fillStyle = '#f66'; ctx.beginPath(); ctx.arc(oBall.x, oBall.y, oBall.r, 0, Math.PI * 2, true); ctx.closePath(); ctx.fill(); if (bRightBut) oPadd.x += 5; else if (bLeftBut) oPadd.x -= 5; // draw Padd (rectangle) ctx.drawImage(oPadd.img, oPadd.x, ctx.canvas.height - oPadd.h); // draw bricks (from array of its objects) for (i=0; i < oBricks.r; i++) { ctx.fillStyle = oBricks.colors[i]; for (j=0; j < oBricks.c; j++) { if (oBricks.objs[i][j] == 1) { ctx.beginPath(); ctx.rect((j * (oBricks.w + oBricks.p)) + oBricks.p, (i * (oBricks.h + oBricks.p)) + oBricks.p, oBricks.w, oBricks.h); ctx.closePath(); ctx.fill(); } } } // collision detection iRowH = oBricks.h + oBricks.p; iRow = Math.floor(oBall.y / iRowH); iCol = Math.floor(oBall.x / (oBricks.w + oBricks.p)); // mark brick as broken (empty) and reverse brick if (oBall.y < oBricks.r * iRowH && iRow >= 0 && iCol >= 0 && oBricks.objs[iRow][iCol] == 1) { oBricks.objs[iRow][iCol] = 0; oBall.dy = -oBall.dy; iPoints++; aSounds[0].play(); // play sound } // reverse X position of ball if (oBall.x + oBall.dx + oBall.r > ctx.canvas.width || oBall.x + oBall.dx - oBall.r < 0) { oBall.dx = -oBall.dx; } if (oBall.y + oBall.dy - oBall.r < 0) { oBall.dy = -oBall.dy; } else if (oBall.y + oBall.dy + oBall.r > ctx.canvas.height - oPadd.h) { if (oBall.x > oPadd.x && oBall.x < oPadd.x + oPadd.w) { oBall.dx = 10 * ((oBall.x-(oPadd.x+oPadd.w/2))/oPadd.w); oBall.dy = -oBall.dy; aSounds[2].play(); // play sound } else if (oBall.y + oBall.dy + oBall.r > ctx.canvas.height) { clearInterval(iStart); clearInterval(iGameTimer); // HTML5 Local storage - save values localStorage.setItem('last-time', iMin + ':' + iSec); localStorage.setItem('last-points', iPoints); aSounds[1].play(); // play sound } } oBall.x += oBall.dx; oBall.y += oBall.dy; ctx.font = '16px Verdana'; ctx.fillStyle = '#fff'; iMin = Math.floor(iElapsed / 60); iSec = iElapsed % 60; if (iMin < 10) iMin = "0" + iMin; if (iSec < 10) iSec = "0" + iSec; ctx.fillText('Time: ' + iMin + ':' + iSec, 600, 520); ctx.fillText('Points: ' + iPoints, 600, 550); if (sLastTime != null && sLastPoints != null) { ctx.fillText('Last Time: ' + sLastTime, 600, 460); ctx.fillText('Last Points: ' + sLastPoints, 600, 490); } } // initialization $(function(){ canvas = document.getElementById('scene'); ctx = canvas.getContext('2d'); var width = canvas.width; var height = canvas.height; var padImg = new Image(); padImg.src = 'images/padd.png'; padImg.onload = function() {}; oBall = new Ball(width / 2, 550, 0.5, -5, 10); // new ball object oPadd = new Padd(width / 2, 120, 20, padImg); // new padd object oBricks = new Bricks((width / - 1, 20, 6, 8, 2); // new bricks object oBricks.objs = new Array(oBricks.r); // fill-in bricks for (i=0; i < oBricks.r; i++) { oBricks.objs[i] = new Array(oBricks.c); for (j=0; j < oBricks.c; j++) { oBricks.objs[i][j] = 1; } } aSounds[0] = new Audio('media/snd1.wav'); aSounds[0].volume = 0.9; aSounds[1] = new Audio('media/snd2.wav'); aSounds[1].volume = 0.9; aSounds[2] = new Audio('media/snd3.wav'); aSounds[2].volume = 0.9; iStart = setInterval(drawScene, 10); // loop drawScene iGameTimer = setInterval(countTimer, 1000); // inner game timer // HTML5 Local storage - get values sLastTime = localStorage.getItem('last-time'); sLastPoints = localStorage.getItem('last-points'); $(window).keydown(function(event){ // keyboard-down alerts switch (event.keyCode) { case 37: // 'Left' key bLeftBut = true; break; case 39: // 'Right' key bRightBut = true; break; } }); $(window).keyup(function(event){ // keyboard-up alerts switch (event.keyCode) { case 37: // 'Left' key bLeftBut = false; break; case 39: // 'Right' key bRightBut = false; break; } }); var iCanvX1 = $(canvas).offset().left; var iCanvX2 = iCanvX1 + width; $('#scene').mousemove(function(e) { // binding mousemove event if (e.pageX > iCanvX1 && e.pageX < iCanvX2) { oPadd.x = Math.max(e.pageX - iCanvX1 - (oPadd.w/2), 0); oPadd.x = Math.min(ctx.canvas.width - oPadd.w, oPadd.x); } }); }); function countTimer() { iElapsed++; }
I added my comments anywhere, hope that all code is pretty understandable. Please pay attention to ‘localStorage’ object to understand how to work with HTML5 Local storage (I use ‘setItem’ method to store something and ‘getItem’ to read this from local storage). Also, it can be also interesting to understand how to calculate collisions between the ball and bricks.
Live Demo
download in package
Conclusion
Today, we made our first arkanoid game. Most functionality are already here. We have implemented our first knowledge about collisions and HTML5 local storage too. I will be glad to see your thanks and comments. Good luck!
on my foirfex 6.0.1 it works fine! I wanted to make an animated background for my blog, but maybe it’s too early.