Back in my youth I used to write my own games. They weren’t very good games, but they were just about playable and thought me many concepts about coding. For this article I originally wanted to target the game to 3 platforms: Computers with keyboards, Smartphones and tablets, and The Sony PS4, but after doing some checking it seems the PS4’s gamepad isn’t accessible from Javascript (thanks Sony), so I’ll concentrate on the first two.
I want to write something that closely represents Super Mario Brothers, without infringing on copyright. This was originally released on the Nintendo NES way back in 85. The NES came with a control pad with two buttons: A and B. and a 4-button D-pad, giving a total of 6 buttons. If you were rich, you could buy the 2-controller pack, which meant 2 people could play the game by taking turns but they needed different controllers. (Yes, it makes no sense now either)… maybe they were more worried about germs back in the 80s..who knows.
I’m going to go slowly with this tutorial cause I want to develop the framework first before we delve into the game proper. That means handling various screen resolutions, checking for support of APIs, and abstracting this stuff away as much as possible from our game code. (Trust me it will be better in the long run). Also, you may want to be aware that I’m a fullscreen kind of guy and I’ll be coding this game such that the game takes up as much of the screen as possible. This is essential on smartphones where the display is small but also essential on web browsers where the user’s attention could otherwise be stolen by ads and other such distractions.
I’ll begin with the template I’m using nowadays for most of my web work, plus a few extra bits.
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> </head> <body></body> </html>
Nothing too complex there, you may not be familiar with the “viewport” meta tag. This is a mobile-specific tag that needs to be added to stop the user from zooming in/out the webpage and also allows the developer to hard-code a width (in pixels) so mobile browsers with different resolutions will automagically scale the content up/down so the page appears with the same proportions no matter what screen resolution their device has. We’re not going to use this feature so we’ve set it to “device-width”. (my reason is because I suspect this will add an extra layer of processing on the device which will put more pressure on the CPU and give less cycles to our game which we want to run as smoothly as possible).
Abstracting the gamepads
We intend our platform game to run on touchscreen devices as well as “traditional” computers, but there’s a third type of device we’d like to support and that’s computers with gamepads attached. There’s a relatively young API that modern browsers are starting to support and that’s the Gamepad API. It allows gamepad events to be heard by javascript thus allowing a HTML-based game to be controlled using a gamepad. I’ll get on to that later.
For now let’s forget about how we’re going to read the physical hardware or even what kind of hardware it is (if it is hardware), and write some simple javascript which will define our 2 Nintendo NES joypads (gamepads):
var gamepads=[ {up:false,down:false,left:false,right:false,A:false,B:false}, // Player 1 {up:false,down:false,left:false,right:false,A:false,B:false} // Player 2 ];
We can think of this structure as the ‘middle man’ between our (messy) API code and our (nice) game code. The game programmer will never ‘see past’ this structure and will simply read the boolean values to see whether a particular button is or isn’t pressed.
What did I mean by loop-based?
Most web programming these days is event-based, but all the cartridge-based games back in the 80s were loop-based. The game centered around a “main loop” which would run 25 times a second (or 50) typically doing the following tasks:
while (alive){ updatePlayer(); updateBaddies(); detectCollisions(); drawBackground(); drawSprites(); waitVSync(); flipBuffers(); }
Needless to say this is a very rough description. updatePlayer() would update the player’s sprite (let’s call him Mario), by reading the gamepad’s buttons and reading some other variables related to the player such as current X/Y coordinates, X/Y velocity, whether he was big mario or small mario / whether he was “dying”… and based on these it would arrive at the correct X/Y coordinates and the correct mario sprite to render. updateBaddies() would do something similar for all the baddies on the screen. detectCollisions() would check if Mario has come into contact with any baddies, drawBackground() and drawSprites() would draw the background/ sprites in their correct places and waitVSync() would wait until the TV’s beam had reached the bottom of the screen and returned back to the top of the screen ready to begin the next frame. This ensured a steady frame rate as long as all of the work was done before that beam got to the bottom of the screen. If a game missed that deadline, it would have to wait for the next VSync thus causing that frame to stay onscreen for twice as long as it should have.
There was also another colsely-related concept called double buffering that all games needed to implement in order to look “good”. I have a suspicion that we shouldn’t need it for our canvas-based game. I wrote one a few years ago when canvas had just been invented and I did need to implement a double buffer, which meant 2 canvases (canvii?) stacked on top of each other and while one was being drawn on the other was on display to the viewer, then once the first one is fully drawn it’s put on display and the other one is flipped. This involved changing the z-Index values and some messy css positioning code. I suspect this problem has been sorted out in newer browsers and the programmers have devised some clever way of rendering things to the canvas in “bunches” then displaying it all in an instant, overwriting what was previously there. This would make sense since it ensures less resources to be consumed by the browser and makes coding easier for the developer (us). I am just speculating here though and I could be wrong.
Our main loop
Without further ado, here’s our main loop:
var mainLoop=setInterval(function(){ // Loop code goes here },20);
This function will run every 20ms , or every .02 of a second, giving, (1/.02)=50 loops per second. or 50Hz. It would be nice to have it run at 100Hz but this might put too much strain on mobile devices. It may not be smooth enough for the young gamers of today who demand ultra high frame rates. So if they want 100Hz they are welcome to modify the javascript and play at 2X the speed.
Getting the canvas out of the way
I hate CSS. I’d like to keep the CSS to an absolute minimum for this tutorial so let’s try and nail the canvas code and get it positioned properly before we move on to the good stuff.
game.html:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> <script type="text/javascript" src="game.js"></script> <style> body{ margin:0px; padding:0px; overflow:hidden; } #theCanvas{ margin:0px; padding:0px; border:0px; width:100%; height:100%; } </style> </head> <body onload="game.init()" onresize="game.resizeCanvas()"> <canvas id="theCanvas"></canvas> </body> </html>
game.js:
var game=(function(){ var cvs; // reference to the canvas itself var ctx; // canvas's 2D drawing context var frame=0; // counts the number of frames in our game var gamepads=[ {up:false,down:false,left:false,right:false,A:false,B:false}, // Player 1 {up:false,down:false,left:false,right:false,A:false,B:false} // Player 2 ]; var resizeCanvas=function(){ cvs.style.width=window.innerWidth+"px"; cvs.style.height=window.innerHeight+"px"; }; var init=function(){ cvs=document.getElementById("theCanvas"); // Set width and height of our game raster // (in pixels). These will never change cvs.width=900; cvs.height=500; resizeCanvas(); ctx=cvs.getContext("2d"); ctx.font="20px Bold Arial"; ctx.textAlign="center"; ctx.textBaseline="middle"; var mainLoop=setInterval(function(){ // Loop code goes here // frame++; ctx.fillStyle="#0f0"; ctx.fillRect(0,0,cvs.width,cvs.height); ctx.fillStyle="#000"; ctx.fillText("Frame "+frame,cvs.width/2,cvs.height/2); },20); }; // Public properties return{ resizeCanvas:resizeCanvas, init:init }; })();
So that’s the most basic HTML and JS code. If you run it on your browser (computer or phone) it should show a green background with the words Frame XXX in the middle with XXX increasing rather fast. The good news is the HTML code is done!. That’s it! same with the CSS! after this all we’ll be working with is Javascript and the only element we’ll be working on is the canvas.
In part 2 we’ll “plug in” our gamepads and get them live and responding to keypresses/touch events. Stay tuned!