100% found this document useful (1 vote)
1K views17 pages

Crafty JS Tutorial (DX Ball - Breakout)

This document provides an overview and outline for a Crafty JS tutorial to build a DX Ball game. It describes the prerequisites, file structure, coding considerations like using components, and outlines the major sections including loading assets, home screen, game play, and total score scenes. Key aspects like sprites, audio, mouse interactions, and randomly generating brick entities with different colors are discussed at a high level. The goal is to take developers with no game experience through building this game step-by-step using the Crafty framework.

Uploaded by

Nguyễn Phú
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
1K views17 pages

Crafty JS Tutorial (DX Ball - Breakout)

This document provides an overview and outline for a Crafty JS tutorial to build a DX Ball game. It describes the prerequisites, file structure, coding considerations like using components, and outlines the major sections including loading assets, home screen, game play, and total score scenes. Key aspects like sprites, audio, mouse interactions, and randomly generating brick entities with different colors are discussed at a high level. The goal is to take developers with no game experience through building this game step-by-step using the Crafty framework.

Uploaded by

Nguyễn Phú
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 17

Crafty JS Tutorial

(DX Ball)

Introduction:

This Crafty JS tutorial is part of Alkottab Studio's internship program, to take developers with no
previous game development experience in the quickest way to production phase.

Prerequisites:

Previous development experience.
The very basics of HTML/CSS/Javascript.
Finishing the first part of the Crafty tutorial on its website. [ How Crafty works , Download
and Setup ]

Section 1: Game description:


A ball continuously moving and hitting walls and bricks.
Bricks are destroyed and points are collected when hit by the ball.
User controls a bat and moves it right and left to prevent the ball from hitting the ground.
If the ball hits the ground, a life is lost.



Section 2: File Structure:

Before starting the tutorial, it is better to explain how the files are arranged:
index file : links to JS files, game div, some simple CSS.
js directory: Crafty file and game file.
assets directory: images and spritesheets used.

Section 3: Coding considerations:

For a cleaner code, some complex game items will be separated as Crafty components to be
used later in game scenes.
To handle game constants, variables and cross-component calls (like an object wanting to
affect the score bar), I will make a simple game manager to contain values and functions
connecting between components.

Section 4: Scenes:

Loading: A scene only for loading game assets and giving the user a progress indication.
Home: Main Menu screen of the game. For now, it will contain only a start button, but it
can have more buttons and some graphics representing the game.
Game: the scene with the actual play.
Total Score: When a user wins or loses, this scene will show the total score.

Section 5: Index file:

Here is the content of the index.html file.

<html>
<head>
<script type="text/javascript" src="js/crafty-min.js"></script>
<script type="text/javascript" src="js/game.js"></script>
<style>
#cr-stage {
margin:0px auto;
}
</style>
</head>
<body>
<div id="cr-stage" class="game">
<!-- cratfy code takes effect here -->
</div>
</body>
</html>
As you see, it is very simple. In the head: linking to the Crafty file and our game code, and
centering the game div at the middle of the screen. In the body: I added a div with id cr-stage.
Crafty searches by default for a div with this id and creates one if not found. So I prefer to add it my
self to have control over the design of my page.

Section 6: Game file:

The Javascript file for our game is a big block of code inside the window.onload function, plus a
Javascript object as the game manager.

Most of the code I write will not take effect immediately on the screen as it is a code for
components and scenes. The only code that takes effect immediately is the initialization code the
the scene loading code.

Crafty.init(game_man.game_width, game_man.game_height);
Crafty.scene("loading");
The above code initializes the Crafty framework with constants I created in the game manager then
loads the 'loading' scene.
[more about Crafty.init]

Section 7: 'Loading' Scene:



It is the first scene of them all. It is all about loading the game assets we will use later. This way,
there will be no lag in the middle of the game because of a missing resource or a slow connection.

First, let's create the assets before going into the scene. Note that I used two ways to load the
images:

Crafty.sprite("img/sprites.png", {
img_bat:[0, 0, 64, 16],
img_ball:[64, 0, 16, 16],
img_brick_0:[0 , 32, 32, 16],
img_brick_1:[32, 32, 32, 16],
img_brick_2:[64, 32, 32, 16],
img_brick_3:[96, 32, 32, 16],
img_brick_4:[0 , 48, 32, 16],
img_brick_5:[32, 48, 32, 16],
img_brick_6:[64, 48, 32, 16],
img_brick_7:[96, 48, 32, 16],
});

Crafty.sprite(48, 48, "img/start.png", {
img_start:[0, 0]
});
The first way is naming the sprite sheet then detailing the internal sprites inside it and setting
x,y,w,h of each sprite in the sprite sheet. Is used this way because the sprite sheet had different
sprite sizes.

The second way is the default way of Crafty: setting the default sprite size, naming the sprite sheet,
then loading the sprites by the row/column offset (not the pixel offset).
[more about Crafty.sprite]

I also want to add an audio file for the background music. Note that I have to add three audio types
to be able to support audio in different browsers, HTML5 constraint.
[more about Crafty.audio]
Crafty.audio.add({
music: ["audio/background.wav",
"audio/background.mp3",
"audio/background.ogg"]
});
Now let's see the scene code:
// loading scene
Crafty.scene( "loading", function(){

// set view
Crafty.background("#000");
Crafty.e("2D, DOM, Text, progressText")
.attr({ w: game_man.game_width, h: 50, x: 0, y: 20 })
.text("Loading ...")
.textColor('#FFFFFF')

// load resources
var toLoad = [];
for (var i in Crafty.assets) {
toLoad.push(i);
}
Crafty.load(toLoad,
function () {
// when finished
Crafty.scene("home");
},
function(e) {
// progress
Crafty("progressText").each(
function(){
this.text("Loading ... " + Math.floor(e.percent) + "%")
}
);
},

function(e) {
// error loading
console.alert('error loading files');
}
);
});
The code is very simple. It has a black background with a white text entity to show the progress
percentage. Then I prepare for loading the assets by getting all the assets links into an array then
calling the 'load' function. The function has three callbacks: 1) finished, 2) progress, and 3) error.
During the progress, I set the text of the progressText entity to the current progress percentage.
When finished, I load the home scene. If an error occurs, I inform the user. Note that selecting the
'progressText' entity is by using the Crafty selector which is similar to the JQuery selector.
[more about Text, Crafty.assets, Crafty.loader, Crafty selector]

Section 8: 'Home' Scene:


The home scene is simple scene with a button to start the game. Let's have a look at the code:
Crafty.scene( "home", function(){
Crafty.background("#000");

Crafty.e("DOM, 2D, Mouse, img_start")
.attr({x:120, y:170, w:48, h:48})
.bind("Click", function(e){
Crafty.scene("game");
})
.bind("MouseOver", function(e){
this.toggleComponent("img_start, img_start_hover");
})
.bind("MouseOut", function(e){
this.toggleComponent("img_start_hover, img_start");
});
});
It has a black background and a single entity for the start button. The start button is not big and not
used anywhere else, so I did not create a separate component for it (I'll get to that later). I added the
Mouse component to the entity so I can make use of the mouse events. I also added the img_start
sprite as a component to apply the sprite on that entity. Here I'm binding to three events:

Click: To load the game scene when the entity is clicked.
MouseOver: To change the sprite and add a brighter one to give the effect of highlighting.
MouseOut: To add the original (darker) sprite again. This event and the previous one are not
adding a functionality, just a taste of UX.
[more about Mouse, .toggleComponent]

Section 9: 'Game' Scene:

The game scene is a bit complicated, so let's go into it a step by step. Let's create the same
background we made before. Play the audio and let it repeat forever by setting number of loops to -
1

Crafty.background("#000");
Crafty.audio.play("music", -1);
Bricks:


Now let's create the bricks, as they are the easy ones. I want the bricks to have random colors, and
this will mean that I should have a lot of 'if' conditions with the number of different sprites I have!
An easier solution can be made by remembering a couple of things: 1) the sprite is set by adding a
component with its name to the entity string , and 2) we have set the sprites with a naming of
brick_number. So the solution is easy, we will generate a random number and append the number as
a string to the components string.

Now let's get back to the game scene, and remember the game manager whenever we need a
constant or want to call another entity. The creation of bricks is as easy as a for loop for rows and
columns, with a little offset at the start point.
// create bricks
for (var row=0; row<7; row++) {
for (var col=0; col<7; col++) {
rand = Crafty.math.randomInt(0, game_man.bricks_colors-1);
Crafty.e("DOM, 2D, brick, img_brick_"+rand)
.attr({x:col*32 + 40, y:row*16+40, w:32, h:16, points:10});
game_man.bricks_on_screen++;
}
}
Something to note is that I put two components, brick and brick_number. The first one will be used
later in collision detection as an id or type for every brick object the ball hits. The 'brick' component
has no effect on the behavior of look because it is not initialized anywhere (yes, it does not crash
anything). Because I do not care about the color and I do not want to add conditions for every
brick_number, the 'brick' component will save a lot of lines and keep it clean. The second
component 'brick_number' is the one responsible for the colored sprite.

Buttons:


Let's keep moving with the small entities: back, mute, pause. This time, and to keep the code clean,
I will create components outside the game scene then add them to it later.

Inside the window.onload and outside the scene code, I'll will add the code for the back component,

Crafty.c("button_back", {
init: function(){
this.requires("DOM, 2D, Mouse, img_back")
.attr({x:game_man.back_x, y:game_man.back_y, z:3, w:24, h:24});
this.bind("Click", function(){
if(!Crafty.isPaused()){
Crafty.audio.stop();
Crafty.scene("home");
}
})
.bind("MouseOver", function(e){
this.toggleComponent("img_back, img_back_hover");
})
.bind("MouseOut", function(e){
this.toggleComponent("img_back_hover, img_back");
});
}
});
The code is simple, bind to three mouse events like before, the mouse over and out for toggling the
sprite, and this time the click event will stop the audio and load the home scene. Notice that there is
a constant in xy position, add it to the game manager object.

There is a point to notice here when toggling sprite components: The button will be in the
hover/selection state when clicked, so when you toggle the sprite, make sure you toggle the _hover
component.


Now I can add an entity with the 'button_back' component to the scene. The scene code now looks
minimal and stylish.

Crafty.scene( "game", function(){

// create bricks
for (var row=0; row<7; row++) {
for (var col=0; col<7; col++) {
rand = Crafty.math.randomInt(0, game_man.bricks_colors-1);
Crafty.e("DOM, 2D, brick, img_brick_"+rand)
.attr({x:col*32 + 40, y:row*16+40, w:32, h:16, points:10});
game_man.bricks_on_screen++;
}
}

Crafty.e("button_back");

Crafty.background("#000");
Crafty.audio.play("music", -1);

});
Let's add the mute button the same way as back button. This time the click will mute/unmute the
audio. And we will need to add the 'muted' variable to the game manager, since the framework does
not mention any API to check if the audio is muted. And do not forget the position constants.

Crafty.c("button_mute", {
init: function(){
this.requires("DOM, 2D, Mouse, img_mute")
.attr({x:game_man.mute_x, y:game_man.mute_y, z:3, w:24, h:24});
this.bind("Click", function(){
if(game_man.muted){
this.toggleComponent("img_unmute_hover, img_mute_hover");
game_man.muted = false;
Crafty.audio.unmute();
}
else{
this.toggleComponent("img_mute_hover, img_unmute_hover");
game_man.muted = true;
Crafty.audio.mute();
}
});

this.bind("MouseOver", function(e){
if(game_man.muted)
this.toggleComponent("img_unmute, img_unmute_hover");
else
this.toggleComponent("img_mute, img_mute_hover");
})
.bind("MouseOut", function(e){
if(game_man.muted)
this.toggleComponent("img_unmute_hover, img_unmute");
else
this.toggleComponent("img_mute_hover, img_mute");
});
}
});

And the pause button is almost the same code.

Crafty.c("button_pause", {
init: function(){
this.requires("DOM, 2D, Mouse, img_pause")
.attr({x:game_man.pause_x, y:game_man.pause_y, z:3, w:24, h:24});
this.bind("Click", function(){
if(Crafty.isPaused()){
this.toggleComponent("img_play_hover, img_pause_hover");
Crafty.audio.play("music", -1);
Crafty.pause(false);
}
else{
this.toggleComponent("img_pause_hover, img_play_hover");
Crafty.audio.stop();
Crafty.pause(true);
}
});

this.bind("MouseOver", function(e){
if(Crafty.isPaused())
this.toggleComponent("img_play, img_play_hover");
else
this.toggleComponent("img_pause, img_pause_hover");
})
.bind("MouseOut", function(e){
if(Crafty.isPaused())
this.toggleComponent("img_play_hover, img_play");
else
this.toggleComponent("img_pause_hover, img_pause");
});
}
});
Now add the mute and pause buttons to the scene. Still clean and stylish.

Crafty.scene( "game", function(){

// create bricks
for (var row=0; row<7; row++) {
for (var col=0; col<7; col++) {
rand = Crafty.math.randomInt(0, game_man.bricks_colors-1);
Crafty.e("DOM, 2D, brick, img_brick_"+rand)
.attr({x:col*32 + 40, y:row*16+40, w:32, h:16, points:10});
game_man.bricks_on_screen++;
}
}

Crafty.e("button_mute");
Crafty.e("button_pause");
Crafty.e("button_back");

Crafty.background("#000");
Crafty.audio.play("music", -1);

});


Bat:


The code is getting a bit harder as we move on. Let's see the bat component (the one moving right
and left).

Crafty.c("bat", {
init: function(){
this.requires("DOM, 2D, Keyboard, img_bat")
.attr({x:game_man.bat_start_x, y:game_man.bat_start_y, w:64, h:16,
moving:false, speed:5, direction:1});
this.bind('KeyDown', function () {
if (this.isDown('RIGHT_ARROW')){
this.moving = true;
this.direction = 1;
}else if (this.isDown('LEFT_ARROW')){
this.moving = true;
this.direction = -1;
}
});
this.bind('KeyUp', function () {
if (!this.isDown('RIGHT_ARROW') && !this.isDown('LEFT_ARROW')){
this.moving = false;
}
});
this.bind('EnterFrame', function(e) {
if (this.moving) {
this.x = this.x + this.speed*this.direction;

// within limits
if (this.x < 0) {
this.x = 0;
}
else if ( this.x + this.w > game_man.game_width) {
this.x = game_man.game_width - this.w;
}
}
});
}
});

The bat component will need the Keyboard component to detect the key events. In the code
initialization we add three attributes:

speed: The number of pixels the bat moves per frame.
direction: Right (1) or left (-1)
moving: Because the key event will only detect the keydown event once until the keyup
event occurs, I want a flag to indicate that a key is still down to be used in the 'enterframe'
event (explanation coming next).

The bat listens to three events:
KeyDown: Check if the right or left arrow is pressed and set the flag and direction
accordingly
KeyUp: Set the moving flag to false if none of the two arrows is pressed.
EnterFrame: It occurs at every frame: if the key is still down, move the bat in the specified
direction. And make sure the bat stays within the screen borders.
[more about Keyboard]

Now that the bat code is finished, add the entity to the scene.

Crafty.e("bat");

Ball:



The most difficult of our components is the ball. Because all the big events happen and are handled
by it.

Crafty.c("ball", {
init: function(){
this.requires("DOM, 2D, Collision, img_ball")
.attr({x:game_man.ball_start_x, y:game_man.ball_start_y, w:16, h:16,
moving:false, speed:3, xdir:1, ydir:-1});

this.bind('EnterFrame', function(e) {
if (this.moving) {
this.x = this.x + this.speed*this.xdir;
this.y = this.y + this.speed*this.ydir;

// hits roof
if (this.y < 0){
this.ydir = this.ydir * -1;
}

// hits sides
if ( (this.x < 0) || ((this.x + this.w) > game_man.game_width) )
{
this.xdir = this.xdir * -1;
}

if (this.y > game_man.game_height) {
// take a life
game_man.take_life();

// reset position and direction
this.x = game_man.ball_start_x;
this.y = game_man.ball_start_y;
this.xdir = 1;
this.ydir = -1;

// stop for a while
this.moving = false;
me = this;
setTimeout(function() {
me.moving = true;
}, 1000);

}
}
});

this.onHit("bat",
function(objs){
bat = objs[0].obj
// hits the upper half of the bat
if ( this.intersect(bat.x, bat.y, bat.w, bat.h/2) ) {
this.ydir = -1;
}
}
);

this.onHit("brick",
function(objs){

/* act depending on the hit direction */
brick = objs[0].obj

//hit from top
if ( this.intersect(brick.x + brick.w/4, brick.y, brick.w/2,
brick.h/2) ){
this.ydir = -1;
}
// hit from bottom
else if ( this.intersect(brick.x + brick.w/4, brick.y +
brick.h/2, brick.w/2, brick.h/2) ) {
this.ydir = 1;
}
// hit from left
else if( this.intersect(brick.x, brick.y, brick.w/4, brick.h) )
{
this.xdir = -1;
}
// hit from right
else if( this.intersect(brick.x + 3*brick.w/4, brick.y,
brick.w/4, brick.h) ) {
this.xdir = 1;
}

// notify game manager
game_man.brick_hit(brick);
}
);

// start moving after some time
me = this;
setTimeout(function() {
me.moving = true;
}, 1000);
}
});
The ball component uses the 'collision' component for collision detection. The ball attributes
include:

moving: because there are some cases to come where the ball would stop.
speed: number of pixels to move per frame
xdir, ydir: two variables to move the ball in the four directions. Each variable can take
values of 1 and -1

Now let's come to the event bindings, the ball component bonds to three bindings:

EnterFrame: it updates the coordinates of the ball to keep it moving in the currently set
directions. And it changes direction depending on the wall it hits (top, right, left). It also
checks hitting the ground to take a life from the player. This part of taking a life is on the
game manager side. Let's keep it for later, just remember the game_man call for now. In the
case of taking a life, the ball resets it's position and direction and waits for a little time
before moving again. It is not a must but it looked better than moving immediately. That's
why the 'moving' flag is used.

onHit(bat): the collision detection uses the component's name 'bat' to subscribe to collision
with it. The collision event passes an array of objects hit, so i'll take the first in the list (and
actually the only one since there is one bat) and check if the ball intersects with it's upper
half. I said the upper half because hitting the lower half of the bat means that the ball is
already on its way to the ground. This is a game login part that can be changes according to
taste.

onHit(brick): remember the part of creating bricks? I added two components to the brick
component: one with the name 'brick' and one with the name 'brick_number'. Now the 'brick'
word will come to help. Instead of added events for every brick type, I'll just subscribe to the
general 'brick'. The brick collision is a bit tricky than the bat. Because the brick can be hit
from top/bottom/left/right. For an easier collision code, I broke the brick area into for
imaginary parts: left quarter for left collision, right quarter for right collision, top middle
half for top collision, and bottom middle half for bottom collision. With every kind of
collision, the ball with change it's x direction or y direction. Then in each collision, the ball
reports to the game manager that it hit a brick and passes its reference.

Then the final part of the ball initialization is to actually start moving the ball but setting the
'moving' flag to true, but after some time delay of course. Do not forget to add the ball entity to the
scene

Crafty.e("ball");

One final note before moving on is that the onHit event will return an array of hit bricks. I chose to
only access one brick at a time and leave the others for later events. This is because if I handle all
the bricks at the same time it will look weird as two bricks may be destroyed at the same time. This
is ,again, a matter of UX taste. But in other games it may be a must to handle all objects, like a
game of falling objects that you may have no other chance to hit again in the next frame.

This leaves us with only three easy parts: game manager ,score bar, and life bar.

Game Manager:

As mentioned before, the game manager has all the game constants, and manages the calls between
components. It has five functions:
brick_hit: it is called by the ball whenever a brick is hit. Then it is the game manager's duty
to decrease the bricks counter, add brick points to the total score, destroy the brick, and end
the game if the bricks number reaches zero.
add_points: increases the total score and calls the score bar to update the view.
take_life: called by the ball if it hits the floor. It decreases lives counter then end the game if
zero is reached or otherwise calls the life bar to update the view.
game_ended: it stops the music and goes to the final score scene.
reset: it resets all game manager counters at the start of every game scene, otherwise we will
start the game with the data of the last played game.

var game_man = {
muted: false,
total_score: 0,
lives: 5,
bricks_on_screen: 0,
bricks_colors: 8,
game_width: 300,
game_height: 400,
bat_start_x: 50,
bat_start_y: 340,
ball_start_x: 120,
ball_start_y: 280,
score_x: 10,
score_y: 10,
life_x: 150,
life_y: 10,
back_x: 10,
back_y: 370,
mute_x: 50,
mute_y: 370,
pause_x: 90,
pause_y: 370,
brick_hit: function(brick) {
this.bricks_on_screen--;
this.add_points(brick.points);
brick.destroy();
if(this.bricks_on_screen == 0) {
this.game_ended();
}
},
add_points: function(points) {
this.total_score += points;
Crafty("score_bar").each(function(){this.update();});
},
take_life: function() {
this.lives -= 1;
if(this.lives < 0){
//game over
this.game_ended();
} else {
Crafty("life_bar").each(
function(){
this.life_taken();
}
);
}
},
game_ended: function() {
Crafty.audio.stop();
Crafty.scene("total_score");
},
reset: function() {
this.muted = false;
this.total_score = 0;
this.lives = 5;
}
};
Score Bar:




The score bar is very simple, it is a text component that shows the total score and updates the text
whenever called by the game manager.

Crafty.c("score_bar", {
init: function(){
this.requires("DOM, 2D, Text");
this.attr({x:game_man.score_x, y:game_man.score_y, z:1, w:300});
this.textColor("#ffffff");
this.textFont({ size: '30px', weight: 'bold'});
this.text("Score: " + game_man.total_score.toString());
},

update: function(){
this.text("Score: " + game_man.total_score.toString());
}
});
Life Bar:



The life bar is responsible for showing the number of lives remaining. It creates heart images with
names similar to the way we did in bricks. And when a life is taken and the game manager reports it
to the life bar, it removes the heart component corresponding to the life taken.

Crafty.c("life_bar", {
init: function(){
this.requires("DOM, 2D");
this.attr({x:game_man.life_x, y:game_man.life_y, z:1});

Crafty.e("2D, DOM, Text")
.attr({x:game_man.life_x, y:game_man.life_y, w:50})
.textColor("#ffffff")
.textFont({ size: '30px', weight: 'bold'})
.text("Lives:");

var heartX = this.x + 40;
var heartY = this.y + 2;
for (var i=0; i< game_man.lives; i++){
Crafty.e("DOM, 2D, heart_"+i+", img_heart")
.attr({x: heartX, y:heartY});
heartX += 20;
}

},

life_taken: function(){
Crafty("heart_"+game_man.lives).each(
function(){
this.destroy();
}
);
}
});
Section 10: 'Final Score' Scene:


The final scene to show is the 'total_score' scene. It is a very simple one: black background, a text
component to show the score, and the same back button of the game scene that moves back to the
home scene.

Crafty.scene( "total_score", function(){
Crafty.background("#000");

Crafty.e("button_back");

scoreText = "Total Score: (" + game_man.total_score.toString() + ")";
Crafty.e("DOM, 2D, Text")
.attr({x:50, y:180, w:200, h:50})
.textFont({ size: '30px', weight: 'bold' })
.textColor("#ffffff")
.text(scoreText)
});





Conclusion:

Now you have a fully working game in less than 500 lines of code! You can make lots of 2D games
with what you already know at this moment. But for extra knowledge and power, here are some
bonus features that you can implement in this game:

Add an explosion effect to the bat when the game is over before moving to the score scene.
Make the background music global, i.e, working in all scenes. And, of course, the sound
button should come along.
Change the game to Arabic. Not only changing the text, but items alignment too.
There is a bug in the code: When the pause button is clicked, the sprite does not change. Try
to find the reason and fix it.
Make a better progress view in the loading scene.

You might also like