Logic and rendering
The HTML file now contains a few new JavaScript files, and some extra bits needed for our custom framerate counter. The fps counter is of course not needed for your games, but it is nice to have because it lets us know when we have screwed something up and our code runs slower than it should.
This is the HTML file, index.html (with links to the JavaScript files):
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>HTML5 Game Programming Lesson 2</title> <script src='js/JareUtils.js'></script> <script src='js/FPSMeter.js'></script> <script src='js/GameLoopManager.js'></script> <script src='js/Lesson.js'></script> <style> #screen { display:block; margin:0 auto; background-color:#F0F; } .fpsmeter { padding: 0; margin: 0; width: 100%; text-align: center; } .fpsmeter p { margin-top: 0px; margin-bottom: 0px; } </style> </head> <body> <h3>HTML5 Game Programming Lesson 2</h3> <canvas id='screen' width='800' height='480'></canvas> <div id="fpscontainer"></div> </body> </html>
I will let you explore the details of the new JavaScript files/libraries, but the general idea is:
- js/JareUtils.js: contains a portable version of the RequestAnimationFrame() function (which is still not fully standardized across browsers), used to ensure that we are called regularly (about 60 times per second) when the browser is ready for our game to render another frame. This function is used by GameLoopManager, we won't be using it directly inside game code.
- js/FPSMeter.js: a simple framerate meter class. The HTML contains a new CSS class fpsmeter to style it. The object is created in window.onload (receiving the CSS class name and the DOM element that will be the parent). Once created, it just needs its update() function called regularly with the time of the last frame in seconds. You can use other framerate widgets like MrDoob's Stats.js if you want.
- js/GameLoopManager.js: general game loop and timing code abstracted in a Singleton/Manager object with a simple interface API, the GameLoopManager.run() function to specify a function that should be called every frame, and GameLoopManager.stop() to remove per-frame execution of code (useful for example while we are waiting for assets to load).
And this is the main JavaScript file, js/Lesson.js:
// ---------------------------------------- // Actual game code goes here. // Global vars fps = null; canvas = null; ctx = null; // ---------------------------------------- // Our 'game' variables var posX = 0; var posY = 0; var velX = 100; var velY = 100; var sizeX = 80; var sizeY = 40; function GameTick(elapsed) { fps.update(elapsed); // --- Logic // Movement physics posX += velX*elapsed; posY += velY*elapsed; // Collision detection and response if ( (posX <= 0 && velX < 0) || (posX >= canvas.width-sizeX && velX > 0) ) velX = -velX; if ( (posY <= 0 && velY < 0) || (posY >= canvas.height-sizeY && velY > 0) ) velY = -velY; // --- Rendering // Clear the screen ctx.fillStyle = "cyan"; ctx.fillRect(0, 0, canvas.width, canvas.height); // Render objects ctx.strokeRect(posX, posY, sizeX, sizeY); ctx.fillStyle = "red"; ctx.fillText("Hello World!", posX+10, posY+25); } window.onload = function () { canvas = document.getElementById("screen"); ctx = canvas.getContext("2d"); fps = new FPSMeter("fpsmeter", document.getElementById("fpscontainer")); GameLoopManager.run(GameTick); };
The main JavaScript file creates the framerate object, then calls GameLoopManager.run() with our 'game' function GameTick() as parameter. This function will be called repeatedly by the browser to calculate and render new frames of our game, and as a parameter it will receive the duration of the last frame in seconds (typically a small number, like 0.016). A few quick notes about this function:
- It is broken up into two distinct portions, 'logic' and 'rendering'. This is an incredibly sane thing to do when writing games, separate these two aspects as much as possible.
- This logic is the 'hello world' of physics and collision detection: just a box bouncing around the screen, when the box collides with a boundary, the speed along that axis is reversed.
- It uses the 'elapsed' parameter to scale the motions and computations. This ensures that things in the game move at the same speed regardless of your framerate.
- It only detects a collision against a boundary if the box was actually moving towards that boundary. This pattern becomes even more important when the collision logic gets more complex.
- Rendering consists of first clearing the canvas, then rendering our object.