Create a Pong Game in HTML5 With EaselJS
In this tutorial, we'll create a clone of the classic game, Pong, in HTML5, using the EaselJS library. The game will have multiple screens, sound effects, and a (very simple) AI opponent.
Step 1: Brief Overview
Using pre-made graphics we will code an entertaining Pong game in HTML5 using the EaselJS library, which provides a Flash-like interface for the HTML5 canvas. For an introduction to EaselJS, see this Activetuts+ article.
The player will be able to control a paddle using the mouse and play against the computer-controller opponent to get points.
Step 2: Interface
A simple interface with a neo-futuristic style will be used; this involves multiple shapes, buttons, bitmaps and more.
The graphics required for this tutorial can be found in the attached download.
Step 3: Get EaselJS
The EaselJS library will be used to build our game, make sure you read the Getting Started tutorial if you're new to this library.
You can download the latest version of EaselJS from its official website. However, this may be incompatible with the code here, so I suggest using the version of the library that is included with the source download.
Step 4: HTML Structure
Let's prepare our HTML document. We'll start with the very basics, just a barebones outline:
1 |
|
2 |
<!DOCTYPE html>
|
3 |
<html>
|
4 |
<head>
|
5 |
<title>Pong</title> |
6 |
</head>
|
7 |
<body>
|
8 |
</body>
|
9 |
</html>
|
Step 5: Hide Mobile Highlight
We must add a little bit of CSS too, to remove the default highlight that's applied when you tap on an element in a mobile browser. Without this, the mobile experience would decrease drastically.
1 |
|
2 |
<!DOCTYPE html>
|
3 |
<html>
|
4 |
<head>
|
5 |
<title>Pong</title> |
6 |
|
7 |
<style>*{-webkit-tap-highlight-color: rgba(0, 0, 0, 0);}</style> |
8 |
|
9 |
</head>
|
10 |
<body>
|
11 |
</body>
|
12 |
</html>
|
Step 6: Javascript Libraries
The following code adds the necessary javascript libraries for our app to work.
1 |
|
2 |
<!DOCTYPE html>
|
3 |
<html>
|
4 |
<head>
|
5 |
<title>Pong</title> |
6 |
|
7 |
<style>*{-webkit-tap-highlight-color: rgba(0, 0, 0, 0);}</style> |
8 |
|
9 |
<script src="easel.js"></script> |
10 |
<script src="Tween.js"></script> |
11 |
<script src="sound.js"></script> |
12 |
<script src="Main.js"></script> |
13 |
</head>
|
14 |
<body>
|
15 |
</body>
|
16 |
</html>/
|
As well as EaselJS, we'll also be using TweenJS (for handling screen transitions and the game loop) and SoundJS (for the sound effects).
Main.js
is the file we'll use to hold our own JS code.
Step 7: Call Main Function
In the next lines we call our Main()
function. This is the function that will start our application; it'll be created later in the tutorial, inside Main.js
.
1 |
|
2 |
<!DOCTYPE html>
|
3 |
<html>
|
4 |
<head>
|
5 |
<title>Pong</title> |
6 |
|
7 |
<style>*{-webkit-tap-highlight-color: rgba(0, 0, 0, 0);}</style> |
8 |
|
9 |
<script src="easel.js"></script> |
10 |
<script src="Tween.js"></script> |
11 |
<script src="sound.js"></script> |
12 |
<script src="Main.js"></script> |
13 |
|
14 |
</head>
|
15 |
<body onload="Main();"> |
16 |
</body>
|
17 |
</html>
|
Step 8: Canvas Tag
An HTML5 canvas is created in this line; we assign it an ID so we can reference it later and also set its width and height.
1 |
|
2 |
<!DOCTYPE html>
|
3 |
<html>
|
4 |
<head>
|
5 |
<title>Pong</title> |
6 |
|
7 |
<style>*{-webkit-tap-highlight-color: rgba(0, 0, 0, 0);}</style> |
8 |
|
9 |
<script src="easel.js"></script> |
10 |
<script src="Tween.js"></script> |
11 |
<script src="sound.js"></script> |
12 |
<script src="Main.js"></script> |
13 |
|
14 |
</head>
|
15 |
<body onload="Main();"> |
16 |
<canvas id="Pong" width="480" height="320"></canvas> |
17 |
</body>
|
18 |
</html>
|
Step 9: Create Main.js
Let's begin our game creation!
Open your preferred JavaScript editor (any text editor will work, but you won't have syntax highlighting) and prepare to write the code. Remember to save the file as Main.js
in your project folder.
Step 10: Define Canvas
We'll start by defining all the graphic and logic variables.
The next variables represent the HTML canvas element and the stage that will be linked to it. The stage variable will behave in a similar way as the AS3 stage.
1 |
|
2 |
/* Define Canvas */
|
3 |
|
4 |
var canvas; |
5 |
var stage; |
Step 11: Background
This variable stores the title background image.
1 |
|
2 |
/* Background */
|
3 |
|
4 |
var bgImg = new Image(); |
5 |
var bg; |
Step 12: Title View
This is the Title View, the first interactive screen to appear in our game. These variables store its components.
1 |
|
2 |
/* Title View */
|
3 |
|
4 |
var mainImg = new Image(); |
5 |
var main; |
6 |
var startBImg = new Image(); |
7 |
var startB; |
8 |
var creditsBImg = new Image(); |
9 |
var creditsB; |
10 |
|
11 |
var TitleView = new Container(); |
Step 13: Credits
This view will show the credits, year and copyright of the game, these variables will be used to store it.
1 |
|
2 |
/* Credits */
|
3 |
|
4 |
var creditsViewImg = new Image(); |
5 |
var credits; |
Step 14: Game View
The next variables store the individual graphics that appear in the Game View:
1 |
|
2 |
/* Game View */
|
3 |
|
4 |
var playerImg = new Image(); |
5 |
var player; |
6 |
var ballImg = new Image(); |
7 |
var ball; |
8 |
var cpuImg = new Image(); |
9 |
var cpu; |
10 |
var winImg = new Image(); |
11 |
var win; |
12 |
var loseImg = new Image(); |
13 |
var lose; |
Step 15: Score
The score values will be handled by the next variables:
1 |
|
2 |
/* Score */
|
3 |
|
4 |
var playerScore; |
5 |
var cpuScore; |
Step 16: Variables
These are the variables we'll use, read the comments in the code to understand what they're for:
1 |
|
2 |
var xSpeed = 5; //Horizontal speed of the ball |
3 |
var ySpeed = 5; //Vertical speed of the ball |
4 |
var gfxLoaded = 0; //used as a preloader, counts the already loaded items |
5 |
var tkr = new Object; //used as an event listener to the Ticker |
Step 17: Create Sound Effects
We'll use sound effects to enhance the feeling of the game. The sounds in this example were created using the excellent free tool as3sfxr and converted to MP3 using Audacity.
The sounds required can all be found in the source download. If you want to create your own, you'll need four:
-
hit.mp3
: played when the ball hits a paddle -
playerScore.mp3
: played when they player scores -
enemyScore.mp3
: played when the enemy scores -
wall.mp3
: played when the ball hits the top or bottom boundary
Step 18: Main Function
The Main()
function will be the first to execute when the web page is loaded, because it is referred to in the onload
attribute of the HTML document (see Step 7).
It will call the necessary functions to start the game.
1 |
|
2 |
function Main() |
3 |
{
|
4 |
//code...
|
5 |
}
|
Step 19: Link Canvas
This code gets the HTML canvas ID and links it to the EaselJS Stage class. This will make the stage variable behave like the stage class in AS3. Add this to Main()
.
1 |
|
2 |
/* Link Canvas */
|
3 |
|
4 |
canvas = document.getElementById('Pong'); |
5 |
stage = new Stage(canvas); |
Step 20: Enable Mouse Events
Mouse events are disabled by default in EaselJS to improve performance. Since we need those in the game, we add the following line. Add this to Main()
.
1 |
|
2 |
stage.mouseEventsEnabled = true; |
Step 21: Load Sounds
We'll use SoundJS to add sounds to our game; write the following code to import the sounds we'll be using. Add this to Main()
.
1 |
|
2 |
/* Sound */
|
3 |
|
4 |
SoundJS.addBatch([ |
5 |
{name:'hit', src:'hit.mp3', instances:1}, |
6 |
{name:'playerScore', src:'playerScore.mp3', instances:1}, |
7 |
{name:'enemyScore', src:'enemyScore.mp3', instances:1}, |
8 |
{name:'wall', src:'wall.mp3', instances:1}]); |
Step 22: Load Graphics
This code is used to preload the graphics, with the help of a function that we'll write later. It sets the Image objects we created before to point to the relevant source PNG files in our document folder.
A name is given to each, so we can detect which image is loaded later, and lastly the function that handles the loaded images is called.
Add this to Main()
.
1 |
|
2 |
/* Load GFX */
|
3 |
|
4 |
bgImg.src = 'bg.png'; |
5 |
bgImg.name = 'bg'; |
6 |
bgImg.onload = loadGfx; |
7 |
|
8 |
mainImg.src = 'main.png'; |
9 |
mainImg.name = 'main'; |
10 |
mainImg.onload = loadGfx; |
11 |
|
12 |
startBImg.src = 'startB.png'; |
13 |
startBImg.name = 'startB'; |
14 |
startBImg.onload = loadGfx; |
15 |
|
16 |
creditsBImg.src = 'creditsB.png'; |
17 |
creditsBImg.name = 'creditsB'; |
18 |
creditsBImg.onload = loadGfx; |
19 |
|
20 |
creditsViewImg.src = 'credits.png'; |
21 |
creditsViewImg.name = 'credits'; |
22 |
creditsViewImg.onload = loadGfx; |
23 |
|
24 |
playerImg.src = 'paddle.png'; |
25 |
playerImg.name = 'player'; |
26 |
playerImg.onload = loadGfx; |
27 |
|
28 |
ballImg.src = 'ball.png'; |
29 |
ballImg.name = 'ball'; |
30 |
ballImg.onload = loadGfx; |
31 |
|
32 |
cpuImg.src = 'paddle.png'; |
33 |
cpuImg.name = 'cpu'; |
34 |
cpuImg.onload = loadGfx; |
35 |
|
36 |
winImg.src = 'win.png'; |
37 |
winImg.name = 'win'; |
38 |
winImg.onload = loadGfx; |
39 |
|
40 |
loseImg.src = 'lose.png'; |
41 |
loseImg.name = 'lose'; |
42 |
loseImg.onload = loadGfx; |
Step 23: Set Ticker
The Ticker class provides a centralized tick or heartbeat broadcast at a set interval. This can be used to trigger the game loop.
The following code sets the frame rate to 30 and defines the stage as the listener for the ticks.
The TweenJS class will listen to this tick to perform the animations. Add this to Main()
.
1 |
|
2 |
/* Ticker */
|
3 |
|
4 |
Ticker.setFPS(30); |
5 |
Ticker.addListener(stage); |
Step 24: Preload Function
Every time a graphic is loaded this function will run. It will assign each image to a bitmap object and check thatall the elements are loaded before proceeding.
1 |
|
2 |
function loadGfx(e) |
3 |
{
|
4 |
if(e.target.name = 'bg'){bg = new Bitmap(bgImg);} |
5 |
if(e.target.name = 'main'){main = new Bitmap(mainImg);} |
6 |
if(e.target.name = 'startB'){startB = new Bitmap(startBImg);} |
7 |
if(e.target.name = 'creditsB'){creditsB = new Bitmap(creditsBImg);} |
8 |
if(e.target.name = 'credits'){credits = new Bitmap(creditsViewImg);} |
9 |
if(e.target.name = 'player'){player = new Bitmap(playerImg);} |
10 |
if(e.target.name = 'ball'){ball = new Bitmap(ballImg);} |
11 |
if(e.target.name = 'cpu'){cpu = new Bitmap(cpuImg);} |
12 |
if(e.target.name = 'win'){win = new Bitmap(winImg);} |
13 |
if(e.target.name = 'lose'){lose = new Bitmap(loseImg);} |
14 |
|
15 |
gfxLoaded++; |
16 |
|
17 |
if(gfxLoaded == 10) // remember to change this if you add more images |
18 |
{
|
19 |
addTitleView(); |
20 |
}
|
21 |
}
|
Step 25: Add Title View
When all the graphics are loaded the Title View is added to the stage by the following function:
1 |
|
2 |
function addTitleView() |
3 |
{
|
4 |
startB.x = 240 - 31.5; |
5 |
startB.y = 160; |
6 |
startB.name = 'startB'; |
7 |
|
8 |
creditsB.x = 241 - 42; |
9 |
creditsB.y = 200; |
10 |
|
11 |
TitleView.addChild(main, startB, creditsB); |
12 |
stage.addChild(bg, TitleView); |
13 |
stage.update(); |
Step 26: Start Button Listeners
This function adds the necessary listeners to the TitleView buttons (it is part of addTitleView()
):
1 |
|
2 |
startB.onPress = addGameView; |
3 |
creditsB.onPress = showCredits; |
4 |
}
|
Step 27: Show Credits
The Credits screen is shown when the user clicks the credits button; a mouse listener is added to the full image to remove it.
1 |
|
2 |
function showCredits() |
3 |
{
|
4 |
// Show Credits
|
5 |
|
6 |
credits.x = 480; |
7 |
|
8 |
stage.addChild(credits); |
9 |
stage.update(); |
10 |
Tween.get(credits).to({x:0}, 300); |
11 |
credits.onPress = hideCredits; |
12 |
}
|
Step 28: Hide Credits
When the Credits screen is clicked it will be tweened back and removed from the stage.
1 |
|
2 |
// Hide Credits
|
3 |
|
4 |
function hideCredits(e) |
5 |
{
|
6 |
Tween.get(credits).to({x:480}, 300).call(rmvCredits); |
7 |
}
|
8 |
|
9 |
// Remove Credits
|
10 |
|
11 |
function rmvCredits() |
12 |
{
|
13 |
stage.removeChild(credits); |
14 |
}
|
Let's stop here to test what we've done so far. Click here for a milestone demo.
Keep in mind that some lines have been commented out as some functions haven't been created yet.
Remember that the milestone is included in the source files, so if for some reason your file doesn't mimic this one, compare your source to mine to see what could be causing that.
Step 29: Show Game View
The following lines remove the TitleView from the stage and adds the GameView items to the stage. A mouse listener is added to the background, to start the game when clicked.
1 |
|
2 |
function addGameView() |
3 |
{
|
4 |
// Destroy Menu & Credits screen
|
5 |
|
6 |
stage.removeChild(TitleView); |
7 |
TitleView = null; |
8 |
credits = null; |
9 |
|
10 |
// Add Game View
|
11 |
|
12 |
player.x = 2; |
13 |
player.y = 160 - 37.5; |
14 |
cpu.x = 480 - 25; |
15 |
cpu.y = 160 - 37.5; |
16 |
ball.x = 240 - 15; |
17 |
ball.y = 160 - 15; |
18 |
|
19 |
// Score
|
20 |
|
21 |
playerScore = new Text('0', 'bold 20px Arial', '#A3FF24'); |
22 |
playerScore.maxWidth = 1000; //fix for Chrome 17 |
23 |
playerScore.x = 211; |
24 |
playerScore.y = 20; |
25 |
|
26 |
cpuScore = new Text('0', 'bold 20px Arial', '#A3FF24'); |
27 |
cpuScore.maxWidth = 1000; //fix for Chrome 17 |
28 |
cpuScore.x = 262; |
29 |
cpuScore.y = 20; |
30 |
|
31 |
stage.addChild(playerScore, cpuScore, player, cpu, ball); |
32 |
stage.update(); |
33 |
|
34 |
// Start Listener
|
35 |
|
36 |
bg.onPress = startGame; |
37 |
}
|
Step 30: Player Movement
The player will move along with the mouse's vertical position:
1 |
|
2 |
function movePaddle(e) |
3 |
{
|
4 |
// Mouse Movement
|
5 |
|
6 |
player.y = e.stageY; |
7 |
}
|
Step 31: Start Game
This code runs when the player clicks the game background, it adds the mouse listener that triggers the function in the previous step, and adds a Ticker to control the game loop.
Pay attention to the way the ticker is created: it's the equivalent of a Timer event in AS3.
1 |
|
2 |
function startGame(e) |
3 |
{
|
4 |
bg.onPress = null; |
5 |
stage.onMouseMove = movePaddle; |
6 |
|
7 |
Ticker.addListener(tkr, false); |
8 |
tkr.tick = update; |
9 |
}
|
Step 32: Reset
When a point is scored (by the player or the computer), the paddles and ball wil return to their original positions and the game will be paused:
1 |
|
2 |
function reset() |
3 |
{
|
4 |
ball.x = 240 - 15; |
5 |
ball.y = 160 - 15; |
6 |
player.y = 160 - 37.5; |
7 |
cpu.y = 160 - 37.5; |
8 |
|
9 |
stage.onMouseMove = null; //stop listening to the mouse |
10 |
Ticker.removeListener(tkr); //pause the game |
11 |
bg.onPress = startGame; |
12 |
}
|
Step 33: Ball Movement
If the game is not paused, the ball will be moved every frame using the variables we created before.
1 |
|
2 |
function update() |
3 |
{
|
4 |
// Ball Movement
|
5 |
|
6 |
ball.x = ball.x + xSpeed; |
7 |
ball.y = ball.y + ySpeed; |
Step 34: CPU Movement
This code controls the computer's movement; the paddle is moved in a way that it will follow the ball while still having some margin of error.
1 |
|
2 |
if(cpu.y < ball.y) { |
3 |
cpu.y = cpu.y + 2.5; |
4 |
}
|
5 |
else if(cpu.y > ball.y) { |
6 |
cpu.y = cpu.y - 2.5; |
7 |
}
|
Step 35: Wall Collisions
Here we check whether the ball is at the top or bottom border of the canvas; if so, the vertical speed is reversed and a sound is played.
1 |
|
2 |
if((ball.y) < 0) { ySpeed = -ySpeed; SoundJS.play('wall');};//Up |
3 |
if((ball.y + (30)) > 320) { ySpeed = -ySpeed; SoundJS.play('wall');};//down |
Step 36: Scores
Now the left and right sides. This code also modifies the score, calls the reset function and plays a different sound depending on the side the ball touched.
1 |
|
2 |
/* CPU Score */
|
3 |
|
4 |
if((ball.x) < 0) |
5 |
{
|
6 |
xSpeed = -xSpeed; |
7 |
cpuScore.text = parseInt(cpuScore.text + 1); |
8 |
reset(); |
9 |
SoundJS.play('enemyScore'); |
10 |
}
|
11 |
|
12 |
/* Player Score */
|
13 |
|
14 |
if((ball.x + (30)) > 480) |
15 |
{
|
16 |
xSpeed = -xSpeed; |
17 |
playerScore.text = parseInt(playerScore.text + 1); |
18 |
reset(); |
19 |
SoundJS.play('playerScore'); |
20 |
}
|
Step 37: Ball-Paddle Collisions
The following code checks whether the ball is colliding with a paddle, by comparing the paddle's position to the ball's coordinates. If the bounding boxes of the two intersect, there's a collision, so we reverse the x-speed of the ball and play a sound.
1 |
|
2 |
/* Cpu collision */
|
3 |
|
4 |
if(ball.x + 30 > cpu.x && ball.x + 30 < cpu.x + 22 && ball.y >= cpu.y && ball.y < cpu.y + 75) |
5 |
{
|
6 |
xSpeed *= -1; |
7 |
SoundJS.play('hit'); |
8 |
}
|
9 |
|
10 |
/* Player collision */
|
11 |
|
12 |
if(ball.x <= player.x + 22 && ball.x > player.x && ball.y >= player.y && ball.y < player.y + 75) |
13 |
{
|
14 |
xSpeed *= -1; |
15 |
SoundJS.play('hit'); |
16 |
}
|
Step 38: Check for Win/Game Over
You can modify the ending condition in the next lines - it's set to 10 points by default.
1 |
|
2 |
/* Check for Win */
|
3 |
|
4 |
if(playerScore.text == '10') |
5 |
{
|
6 |
alert('win'); |
7 |
}
|
8 |
|
9 |
/* Check for Game Over */
|
10 |
|
11 |
if(cpuScore.text == '10') |
12 |
{
|
13 |
alert('lose'); |
14 |
}
|
Step 39: Alert
This function will stop the game and show an alert, the contents of which depend to the game result.
1 |
|
2 |
function alert(e) |
3 |
{
|
4 |
Ticker.removeListener(tkr); |
5 |
stage.onMouseMove = null; |
6 |
bg.onPress = null |
7 |
|
8 |
if(e == 'win') |
9 |
{
|
10 |
win.x = 140; |
11 |
win.y = -90; |
12 |
|
13 |
stage.addChild(win); |
14 |
Tween.get(win).to({y: 115}, 300); |
15 |
}
|
16 |
else
|
17 |
{
|
18 |
lose.x = 140; |
19 |
lose.y = -90; |
20 |
|
21 |
stage.addChild(lose); |
22 |
Tween.get(lose).to({y: 115}, 300); |
23 |
}
|
24 |
}
|
Step 40: Test
Save your work (if you haven't) and open the HTML file in the browser to see your game working!
Conclusion
Try modifying the game's variables to create your own version of the game!
I hope you liked this tutorial, thank you for reading!