The following article will be a very minimalist and straightforward guide to getting started with HTML5. While it is targeted at beginners, some coding experience is preferable since I won't bother with explaining Javascript basics. I'll leave that to other online tutorials or a Intro to Compsci course. This also is not platform specific in any way. It should run equally well on Linux, Windows or MacOS.
The HTML
Since this will be a web game, an HTML file is required. This is not a proper web page; it's just enough to make it work.
[html]
[/html]
The first line specifies which HTML version to use, specifically 5.
Some older versions of Internet Explorer force compatability mode on certain web domains which is impossible to override from the web page. This may have been fixed since I last used Internet Explorer. It isn't an issue when testing, but if you try to put your code into a blogspot for example, HTML5 features like canvas may not work for that reason.
The second line is a dependency. JQuery does a lot of very useful things, but I will only be using it for mouse input in this article. The third line is a link to the script containing game logic.
The background color is not necessary but I find it useful.
Finally, you have the HTML5 canvas. The only unusual thing here is the
onContextMenu; it returns
false when the user right clicks on the canvas and as a result, no menu pops up.
The Game Logic
Declarations
Now to the game logic that goes in
game.js. First come the declarations:
var canvasID, context;
var cursorX = 0, cursorY = 0;
var canvasX = 0, canvasY = 0, canvasW = 800, canvasH = 600;
var playerScore=0, aiScore=0;
canvasID is the canvas itself. The
context is an object specific to the canvas that contains all the drawing functionality.
The cursor location is self explanatory. The canvas position is necessary in order to calculate the cursor position relative to the canvas. Canvas dimensions as variables also allows for greater flexibility while coding.
Mouse Code and Initialization
These two things need to be done first, which is the only reason why they are lumped together:
function OnMouseMove(e){
cursorX = e.pageX-canvasX;
cursorY = e.pageY-canvasY;
}
$(document).ready(function(){
canvasID = document.getElementById("gameCanvas");
canvasID.addEventListener("mousemove", OnMouseMove, false);
context = canvasID.getContext('2d');
var rect = canvasID.getBoundingClientRect();
canvasX = rect.left; canvasY = rect.top;
canvasW = rect.right-canvasX; canvasH = rect.bottom-canvasY;
})
The
OnMouseMove function takes an
Event Handler as an argument. The event handler has the cursor position relative to the upper left corner of the page, not of the canvas. This is where
canvasX and
canvasY come in.
The next bit is JQuery magic. When the document is
ready (everything is loaded) the
function is executed.
Fetching the
canvasID is straightforward.
The next line is the last of the mouse input code. It adds an event listener that calls
OnMouseMove when the mouse is moved. There are also
mousedown and
mouseup for mouse button presses and releases.
e.which in the event handler specifices which button was pressed.
Context fetching is simple.
Next is where we find the actual location and dimensions of the canvas.
getBoundingClientRect() returns the space which is taken up by the client. Getting the location is trivial. The
rect right and bottom are the sum of the y position & height and the x position & width.
The Update Loop
function Run(){
context.fillStyle="black";
context.clearRect(0,0, canvasW, canvasH);
context.fillRect(0,0, canvasW, canvasH);
}
setInterval(Run,20);
Run will be the update loop. This is where everything that needs to be executed repeatedly will be put.
setInterval tells the browser that it needs to be executed every 20 ms (or whatever you specify).
Now to the drawing code.
fillStyle and
strokeStyle specify the color which you will be working with. The former is for filling shapes, and the latter is for drawing lines.
clearRect empties the canvas of data. This doesn't really matter when you're starting out but it becomes important when you deal with transparency and compositing. The arguments are x, y, width and height.
fillRect should be self explanatory; it takes the same arguments.
Drawing the Score
function DrawScores(){
context.fillStyle = "white";
context.font = "16px sans-serif";
context.fillText(aiScore, 30,30);
context.fillText(playerScore, canvasW-50,30);
}
We have already worked with
fillStyle.
The font is specified as a string containing size and typeface. A couple of other valid typefaces are
serif and
monotype.
fillText takes a string, x coordinate and y coordinate as arguments.
strokeText will draw an outline around the text; just be sure to specify
strokeStyle instead.
Finally, call it in the
Run function after you clear the canvas.
function Run(){
context.fillStyle="black";
context.clearRect(0,0, canvasW, canvasH);
context.fillRect(0,0, canvasW, canvasH);
DrawScores();
}
The Classes
Javascript doesn't have classes like most other programming languages do, but they work. The only two things really necessary for a ping-pong game are the ball and the paddles.
The Ball
function Ball(){
this.radius = 8;
this.x = canvasW/2;
this.y = canvasH/2;
this.vx = 5;
this.vy = 0;
this.Draw = function(){
//...\\
}
this.Update = function(){
//...\\
}
}
The above code includes everything the ball will need; radius, position, and velocity. It will also need a
Draw function and
Update function. The
Draw function is the simplest:
this.Draw = function(){
context.fillStyle = "white";
context.beginPath();
context.arc(this.x, this.y, this.radius, 0, 3.1416*2);
context.closePath();
context.fill();
}
The only slightly unusual thing about drawing circles in HTML5 is the fact that the context doesn't have a function for explicitly drawing them. Instead, one can create an arc path. First call the
beginPath function, then create an arc of angle 2?, and finally close the path. The arguments are x position, y position, radius, start angle and end angle. More complicated paths are possible, but that is beyond the scope of this article.
Now to the next function:
this.Update = function(){
this.x += this.vx;
this.y += this.vy;
//move the ball
if(this.x > playerPaddle.x - playerPaddle.w/2 - this.radius){//check if the ball has traveled
//far enough to the right to possibly interact with the right paddle.
if(this.y >= playerPaddle.y - playerPaddle.h/2 && this.y <= playerPaddle.y + playerPaddle.h/2){
//check if it actually hit the paddle
this.vy = (playerPaddle.y-this.y)*-0.4; //change the y velocity depending on which part
//of the paddle the ball hit.
this.x = playerPaddle.x - playerPaddle.w/2 - this.radius; //moves the ball out of the paddle
this.vx*=-1; //make the ball bounce off
}else{//if the player misses the ball, incriment the ai score and reset the ball.
aiScore++;
this.vy=0;
this.x = canvasW/2;
this.y = canvasH/2;
}
aiPaddle.AIChangeOffset();//AI thing, will make sense later.
}
if(this.x < aiPaddle.x + aiPaddle.w/2 + this.radius){//same thing
if(this.y >= aiPaddle.y - aiPaddle.h/2 && this.y <= aiPaddle.y + aiPaddle.h/2){
this.vy = (aiPaddle.y-this.y)*-0.2;
this.x = aiPaddle.x + aiPaddle.w/2 + this.radius;
this.vx*=-1;
}else{
playerScore++;
this.vy=0;
this.x = canvasW/2;
this.y = canvasH/2;
}
}
if(this.ycanvasH-this.radius){
this.vy*=-1; this.y=canvasH-this.radius;}
//have the ball bounce off the top and bottom of the screen.
}
There's nothing HTML5-specific here, just ordinary arithmetic and algebra. I didn't comment the
aiPaddle code because it's pretty much the same thing.
The Paddle
Start with the simplest things; variable declarations and the
Draw function:
function Paddle(){
this.x=0;
this.y = canvasH/2;
this.w = 32;
this.h=64;
this.aiv=9; //the maximum velocity with which the ai paddle can move
this.aiOffset = 0; //how far off center the paddle will be when the ball reaches the end
this.Draw = function(){
context.fillStyle="white";
context.fillRect(this.x - this.w/2, this.y - this.h/2, this.w, this.h);
}
This includes all the relevant physical properties, and a pair of AI-related properties.
this.FloorAndCeiling = function(){
if(this.y < this.h/2) this.y = this.h/2;
if(this.y > canvasH-this.h/2) this.y = canvasH-this.h/2;
}
The above makes sure that the paddle is not outside the boundaries of the screen. It puts a floor and a ceiling on the y position. The syntax may seem unusual but it works just the same.
this.PlayerUpdate = function(){
this.y = cursorY;
this.FloorAndCeiling();
}
The above sets the paddle's y position equal to the cursor's y position.
this.AIUpdate = function(){
if(ball.vx < 0){//0.1
if(ball.y + this.h*this.aiOffset > this.y -this.aiv){//1.0
if(this.y+this.aivball.y + this.h*this.aiOffset)
this.y-=this.aiv;
else
this.y=ball.y + this.h*this.aiOffset;
}
this.FloorAndCeiling(); //3.0
}
}
The AI code is a little more difficult to understand conceptually, so the lines are labeled.
0.1 tells the AI to only do stuff if the ball is moving towards it. This isn't absolutely necessary and the game will work fine without this line.
1.0 checks where the ball is relative to the paddle. It's true specifically when the ball is above the paddle.
+ this.h*this.aiOffset simulates error by changing where the center of the paddle actually is.
aiOffset is a number between -0.45 and 0.45 so the new "center" will always be on the paddle.
1.1 checks if the y distance between the paddle "center" and the ball is greater than the maximum velocity at which the AI can move the paddle. If it is, it moves the paddle by that amount. Otherwise,
1.2 the ball is "within range" so instead of overshooting, it just moves to the ball's y position.
2.0 is pretty much the same thing just in the other direction.
3.0 makes sure that the AI doesn't actually move the paddle off screen.
this.AIChangeOffset = function(){
this.aiOffset = (Math.random()-0.5)*0.9;
}
}
Finally, just declare that last function which changes the offset. If you remember, it is called every time the ball bounces off the player's paddle. Remember to close all curly braces!
Variable Initialization
var ball = new Ball();
var aiPaddle = new Paddle(), playerPaddle = new Paddle();
playerPaddle.x = canvasW; //move the paddle to the other side of the screen
Here we create the ball and paddles.
The Game Loop
Finally, update the
Run function;
function Run(){
//NEW CODE PT 1
aiPaddle.AIUpdate();
playerPaddle.PlayerUpdate();
ball.Update(); //Update all the game objects
//END NEW CODE PT 1
context.fillStyle="black";
context.clearRect(0,0, canvasW, canvasH);
context.fillRect(0,0, canvasW, canvasH);//clear the context
//NEW CODE PT 2
ball.Draw();
aiPaddle.Draw();
playerPaddle.Draw();
DrawScores();//Draw it all
//END NEW CODE PT 2
}
Everything here is quite straightforward. Just call all the
Update and
Draw functions in the objects, and draw the score. The only important thing here is to Update before Drawing, and to clear the context before Drawing as well.
Conclusion
This is perhaps the simplest functioning game that one can make. It has a little bit of everything: graphics, input, physics, and AI. It doesn't use any advanced features, but it's a good starting point.
Great article, thanks for promoting HTML5 games!
As an example of a more complex HTML5 game have a look at http://edigames.com/revelimmortal
its a work in progress large scale RPG we're developing.
-Raymond