Chapter 3

image

Crafty

3.1 Source Code

All of the source code and examples are available at the website http://HTML5GameEnginesBook.com/. All the code, graphics, and sound are licensed free for personal and commercial use (MIT and CC-BY-3.0). Crafty is dual licensed MIT or GPL.

3.2 Introduction

Crafty is an open-source JavaScript game library developed by Louis Stowasser [54]. It renders to either Canvas or DOM. Its emphasis is on being lightweight and easy to use. For that reason, it is a good candidate to rewrite the Pong game from the previous chapters.

In this chapter, not only do we rewrite Pong, we will introduce a few more HTML5 considerations and best practices that are more fitting to be discussed here than in Part I.

3.3 Crafty Pong

Our previous Pong game was completed in just a few hundred lines. This time around, we want to have audio and graphics. Our new game will be called Crafty Pong. When finished, Crafty Pong will have a similar line count as before, but it will support far more features. The code itself also will look completely different, as it will have essentially been rewritten from scratch. One problem with game engines is that they have their own way of doing things, which means very little code gets reused. Crafty is no exception. Therefore, it is important to find an engine that will do what you need. Otherwise, you may be abandoning a lot of code at failed attempts.

From Part I, our header needs to be modified to add the Crafty and Modernizr libraries. Modernizr provides HTML5 feature detection for us so our code works better across multiple browsers. Since we are moving our app to an external file, we also are going to make tweaks to the meta tags to tell our browser not to cache the files. See Listing 3.1 for these changes.

<head >
  <meta charset ="UTF -8">
  <meta http - equiv ="cache - control " content ="max -age =0" />
  <meta http - equiv ="cache - control " content ="no - cache " />
  <meta http - equiv =" expires " content ="0" />
  <meta http - equiv =" expires "
 content ="Tue , 01 Jan 1980 1:00:00 GMT " />
  <meta http - equiv =" pragma " content ="no - cache " />

  <title >Crafty Pong </ title >
  <script src =" crafty_v0 .5.3. js"></ script >
  <! --
 For  convenience , we could just  link  directly to
  http://craftyjs.com/release/0.5.3/crafty-min.js
  -->
  <script src =" modernizr -v2 .6.2 - dev .js"></ script >

Listing 3.1. Crafty Pong header.

Note that Crafty hosts minified versions of its library on its website that we could link directly. Also, note again, the absence of type=’text/javascript’. This is no longer necessary with HTML5. Also, we are now adding no-cache clauses in our meta tags. This is very useful when working with graphics and external JavaScript files. On production servers, you would allow the browser to cache and instead use versioning. See below for example versioning of external JavaScript files.

<script src =" crafty .js?v =0.5.3 "></ script >
<script src =" modernizr .js?v =2.6.2 "></ script >
<script src =" pong_crafty .js?v =1.0 "></ script >

When you release a new version, change the v= to the new version description, and the next time your user visits, the browser will fetch and cache the new file. You should use this technique for all external assets (.js, .css, images, etc).

If you are using a scripting engine, such as PHP, you can have the server do the versioning for you. Below is an example of how to do this:

 <script src =" app .js?v=<? php
 echo filemtime ("app.js"); ?>></ script >
 <link rel=" stylesheet "
 href =" style . css?v=<? php echo filemtime (" style . css "); ?>">
</head >
<body >
 <img src =" myimage . png ?v=<? php
  echo filemtime (" myimage . png "); ?>>

Now, PHP will append the file’s modified time to the end of the v= parameter. A change in file modification will change the value for v. This will automatically force browsers to fetch new versions. This is very useful since browsers tend to cache aggressively.

If you are using a web server, such as XAMPP, WAMP, or MAMP mentioned in Part I, your server supports PHP. I recommend switching to the automatic versioning method now. Browsers will sometimes cache even if you try to tell them not to with no-cache headers. However, they always fetch when given new version numbers. A stale cache can cause lots of lost time during development.

3.3.1 Hello Crafty

Crafty was chosen at this point for revised Pong because it is the lightest of the engines, but all the engines being reviewed in Part II could easily implement this simple game. Crafty just happens to be a good candidate for this sort of quick task. We will need to refactor our code for the Crafty way of doing things. For now, let’s initialize our Canvas and display some text. This verifies we are set up correctly. See Listing 3.2 for the code and Figure 3.1 for the output.

function onload () {
 if(Modernizr . canvas && Modernizr . canvastext) {
 Crafty . init (320 , 480);
 Crafty . background ('# dbdbdb '),
 Crafty .e(" HelloWord , Canvas , 2D, Text ")
  . attr ({x: 20, y: 20, w: 100 , h:  20})
  . text (" Hello Crafty !");
} else {
 var yes = confirm (" Download a better browser ?");
 if(yes)
 {
  window . location = "http://google.com/chrome";
}
}
}

Listing 3.2. Initialize Craft.

Figure 3.1.

Figure showing Hello Crafty Chrome.

Hello Crafty Chrome.

When Crafty initializes, it creates an additional container element called cr-stage. If you wish to center and style your canvas game, add this to your style sheet:

#cr - stage {
 border :2 px solid black ;
 margin :5 px auto ;
 color : white ;
}

We have Modernizr to check to see if the browser supports Canvas. Normal usage would have a graceful degradation, but for now, we are redirecting unsupported browsers to download Chrome.

For clarity, this is the last we will see of Modernizr in this book. Just assume all the remaining examples in this book have a check to see if Canvas is supported. The other Modernizr feature we may want to use, audio format detection, is already provided to us by the Crafty engine. All the engines in this book provide audio format detection.

We have verified proper setup, but before moving forward, a brief discussion of how Crafty is organized is needed. Crafty is divided into three main pieces:

Entity

A crafty entity is an object that can be placed onto the screen and react to events. For those familiar with object-oriented programming, this can be thought of as a “class.” Entities are declared using Crafty.e. In our pong game, the paddles, ball, and score are entities.

Components

Entities are comprised of components. This can be thought of as methods and properties in object-oriented programming. In our pong game, our sprites and audio declarations are components that get attached to our entities when we declare them. We also will use built in components such as SpriteAnimation, Collision, Mouse, and Touch.

Events

Components react to events. Some events are specially defined, such as the Collision component, which will make available an event called onHit. Some are built in to the entity system, such as the EnterFrame event when the game loop restarts.

The entity declaration when finished with the game ball will look like this:

Crafty .e(" gameBall ,2D,Canvas , Collision , SpriteAnimation , ball0 ")

gameBall is the name of the entity. It is actually an empty component used to reference this entity. The rest of the string tells Crafty to add support for 2D, Canvas, Collision, SpriteAnimation, and ball0, which is a sprite sheet component that was declared earlier. This full entity declaration will be pieced together as we build Crafty Pong.

Also, Crafty supports chaining similar to jQuery. See below:

// jQuery chaining 2 commands
$('#foo '). addClass ('off '). removeClass ('on '),

Throughout this example, chaining will be used to immediately modify properties directly after each other.

3.3.2 From HTML5 Pong to Crafty Pong

It is time to re-create the paddles and the ball. Beacause it is so easy to add with Crafty, we will go ahead and add the game loop and keyboard controls. Note that we do not specify FPS. This is all handled internally by Crafty using either requestAnimationFrame() or setInterval() depending what works best on the client’s browser. See Listing 3.3 for this rewrite.

function onload () {
 var BACKGROUND_COLOR = '#dbdbdb ';
 var PADDLE_WIDTH = 100;  var PADDLE_HEIGHT = 10;
 var PADDLE_COLOR = '#000000 ';  var BALL_COLOR = '#000000 ';
 var BALL_RADIUS = 10;

 Crafty . init (320 , 480);
 Crafty . background (BACKGROUND_COLOR);

 Crafty .e(" topPaddle , 2D, Canvas , Color ")
  . attr ({x: 100 , y: 10,
  w: PADDLE_WIDTH , h: PADDLE_HEIGHT})
  . color (PADDLE_COLOR);

 Crafty .e(" bottomPaddle , 2D, Canvas , Color , Multiway ")
  . attr ({x: 100 , y: 460 ,
   w: PADDLE_WIDTH , h: PADDLE_HEIGHT})
  . color (PADDLE_COLOR)
  . multiway (4, {LEFT_ARROW : 180 , RIGHT_ARROW : 0});

 Crafty .e(" gameBall , 2D, Canvas , Color , Collision ")
  . attr ({x: 40, y: 240 , w: BALL_RADIUS , h: BALL_RADIUS})
  . color (BALL_COLOR);
}

Listing 3.3. Pong Game Board with Crafty.

For just 20 lines of code, we are now initializing a 2D canvas, starting the game loop, and we can already control the bottom paddle with arrow keys. Crafty is doing a lot of heavy lifting. However, there is one small problem, which you can see in Figure 3.2.

Figure 3.2.

Figure showing Pong Crafty square ball.

Pong Crafty square ball.

You will notice that the game ball is now a square. This is because Crafty does not support circles for entities. This is one of the downsides of game libraries; we can only do what the engine supports. That is, unless we wish to write extra code to extend the engine. We could do this since Crafty is open source [55]. What we will eventually do instead is use the Crafty sprite engine to load a round graphical ball. For now, a square is sufficient. Next, we need to add movement to the ball. This can be done by binding to the EnterFrame event. While we are at it, we will add hit detection to the ball. This is a lot easier with Crafty than it was with our old method. See below for the code:

Crafty .e(" gameBall , 2D, Canvas , Color , Collision ")
 . attr ({x: 240 , y: 240 , w: BALL_RADIUS , h: BALL_RADIUS ,
  xspeed : 2, yspeed : 3
 })
 . color (BALL_COLOR)
 . bind (' EnterFrame ', function () {

  this .x += this . xspeed ;
  this .y += this . yspeed ;

})
 . onHit (' bottomPaddle ', function () {
  this . yspeed *= -1;
  this .y = 460 - BALL_RADIUS ;

})
 . onHit (' topPaddle ', function () {
  this . yspeed *= -1;
  this .y = 10+ BALL_RADIUS ;
});

Crafty lets us name entities, and then it uses its collision engine to see if the entities collide. This makes our job a lot easier. Notice we are using the same trick as before. We force the “y” value of the ball to something meaningful so it doesnt get “stuck” inside the paddle if a user slides into it. We almost have a working pong game. We now need to add a score. This requires a new entity with score variables. See below:

Crafty .e(" scoreValue , 2D, Canvas , Text ")
 . attr ({x: 5, y: 12, w: PADDLE_WIDTH , h: PADDLE_HEIGHT ,
  pointsPlayer :0, pointsComputer :0
 })
 . bind (' EnterFrame ', function () {
  this . text (" You :" + this . pointsPlayer +
  " CPU :" + this . pointsComputer);
 });

The scores need to be updated. The code for this is not in a figure as it is simply incrementing the points variable when the ball leaves the stage. Next is the computer AI. It will be the same as before. The difference is that the AI is now inside the EnterFrame event of the top paddle. See below:

Crafty .e(" topPaddle , 2D, Canvas , Color ")
 . attr ({x: 100 , y: 10, w: PADDLE_WIDTH , h: PADDLE_HEIGHT})
 . color (PADDLE_COLOR)
 . bind (' EnterFrame ', function () {
  var gameBall = Crafty (" gameBall "); // get gameBall
  if(gameBall . yspeed < 0)
  {
   if(gameBall .x < (this .x + PADDLE_WIDTH / 2))
   {
    this .x --;
   } else {
    this .x ++;
   }
  }
  if(this .x  <= 0)
  {
   this .x = 0;
 }
  if(this .x >= (320 - PADDLE_WIDTH))
  {
   this .x = 320 - PADDLE_WIDTH ;
  }
  });

Our last step is to prevent the user from scrolling the paddle off the screen. We need to modify the EnterFrame event of the bottom paddle. A pattern should be emerging. All the entities have an EnterFrame available to them that is convenient for adding logic. See below for the bottom paddle:

Crafty .e(" bottomPaddle , 2D, Canvas , Color ,  Multiway ")
 . attr ({x: 100 , y: 460 , w: PADDLE_WIDTH , h: PADDLE_HEIGHT})
 . color (PADDLE_COLOR)
 . multiway (4, {LEFT_ARROW : 180 , RIGHT_ARROW : 0 })
 . bind (' EnterFrame ', function () {
  if(this .x <= 0)
  {
  this .x = 0;
 }
  if(this .x >= (320 - PADDLE_WIDTH))
  {
  this .x = 320 - PADDLE_WIDTH ;
 }
 });

We have now completely replicated the original HTML5 Pong game in Crafty (with the small exception of a square ball). We went from 230 lines to 120 lines. The use of the game engine also makes the code much easier to update, which we will demonstrate by adding graphics and sound.

3.4 HTML5 Game Graphics

Most games use a “sprite sheet.” This is one big image containing lots of little images. To use a sprite sheet, you tell the game engine to load a single file, and then you tell it to display one small slice of the file. This is a very efficient way to handle graphics because the engine only has to perform one asset fetch (often a costly operation) and hold just one asset in memory. Also, since graphics are often repeated (particularly with overhead-style RPGs), having one image to rule them all saves memory overall. See Figure 3.3 for the sprite sheet used in our Crafty Pong game.

Figure 3.3.

Figure showing Crafty Pong sprites.

Crafty Pong sprites.

Writing a sprite sheet system for a game is difficult. You need to handle loading, unloading, and a coordinate system to slice up the image. Then you need to handle timed repetition if you want animation. Fortunately, we don’t have to worry about that because all the game engines in this book have a sprite sheet engine. We just need to learn each system’s way of managing it.

To use external assets in HTML5, such as a sprite sheet, you need to preload them. Browsers will happily show blank images if your game assets have not finished loading. A preload step makes sure the graphic is available in memory before applying it to the canvas. See Listing 3.4 for Crafty’s loading and applying a sprite sheet.

  Crafty . load ([" pong_sprites .png"], function () {
 console .log (" assets loaded ");
 Crafty . scene (" main "); // go to main scene
 });

  Crafty . sprite (16 ," pong_sprites . png", {
   floor0 : [0 ,0 ,1 ,1] , // location =320 ,64 , height =1, width =1
   floor1 : [0 ,1 ,1 ,1] ,
   floor2 : [1 ,1 ,1 ,1] ,
   wall1 :  [6 ,0 ,1 ,1] ,
   wall2 :  [7 ,0 ,1 ,1] ,
   ball0 :  [2 ,1 ,1 ,1] ,
   toppaddle :  [0 ,2 ,4 ,1] ,
   bottompaddle : [0 ,3 ,4 ,1]
 });

Listing 3.4. Crafty preload sprite assign.

The load step tells the browser to fetch the sprite image. After it has fetched, it goes to the next scene, which is main. This strategy of having a load step before launching the game is used in every engine in this book (whether directly or indirectly via the way the engine is designed).

While assets are being fetched, Crafty reads the sprite declarations. The sprites ask for pixel size of each block, which is 16 pixels by 16 pixels for our purposes. Declaring them requires block coordinates, length, and height. If length or height is not specified, Crafty will set each value to 1.

We will now tile our background. Normally, if you have a complex map, you would have a tile map that goes along with your sprite map. You would parse the map and drop the tiles. Tile maps will be introduced with Impact (Chapter 5). For now, since this is just a one-level stage, we will hard-code the background tiles. See Listing 3.5 for code to draw the background.

Crafty . scene (" main ", function () {
 for (var ytile = 0; ytile < 32; ytile ++) {
   for (var xtile = 0; xtile < 20; xtile ++) {
    // console . log (xtile * 16, ytile * 16);
    var usefloor = (xtile %2);
    if(xtile %  Math . round (Math . random ()*10))
    {
   usefloor = 2;
   }
    if(xtile == 19)
    {
   Crafty .e("2D, Canvas , wall , wall1 ")
    . attr ({x: xtile * 16, y: ytile * 16, z: -2});

   } else if(xtile == 0){
   Crafty .e("2D, Canvas , wall , wall2 ")
    . attr ({x: xtile * 16, y: ytile * 16, z: -2});

   } else {
   Crafty .e("2D, Canvas ,  floor "+ usefloor)
    . attr ({x: xtile * 16, y: ytile * 16, z: -2});
   }
  }
}

Listing 3.5. Crafty draw background.

We are looping through and creating sprite entities throughout the background. The floor+usefloor portion decides which sprite to use. Just to make the background a little more interesting, a random sprite is thrown in. Also, when we are at the edge of the stage, we use a wall sprite. To make our paddles and ball use the new sprites, we need to modify their declarations. See below for their new entity declarations:

Crafty .e(" topPaddle , 2D, Canvas , toppaddle ")
 . attr ({x: 100 , y: 10, w: PADDLE_WIDTH , h: PADDLE_HEIGHT})
 //  . color (PADDLE_COLOR)

Crafty .e(" bottomPaddle , 2D, Canvas , Multiway , bottompaddle ")
 . attr ({x: 100 , y: 460 , w: PADDLE_WIDTH , h: PADDLE_HEIGHT})
 //  . color (PADDLE_COLOR)

Crafty .e(" gameBall , 2D, Canvas , Collision , ball0 ")
 . attr ({x: 300 , y: 240 , w: BALL_RADIUS , h: BALL_RADIUS ,
  xspeed : 2, yspeed : 4
 })
 //. color (BALL_COLOR)

The new additions to our entity declarations are toppaddle, bottompaddle, and gameball. These were the sprites declared earlier. By adding them to our entities, our entities are now using sprite sheets.

Because we are using sprites, we removed the color component. We want our sprites to remain transparent. Because we are basing everything off powers of 2 (16 and 32 pixels), we need to go back and change the paddles and ball to fit. Crafty does automatic scaling, but the scaling looks bad without even sizes. We can see the tiled game in Figure 3.4, though it is not finished quite yet.

Figure 3.4.

Figure showing Crafty Pong tiled.

Crafty Pong tiled.

3.4.1 HTML5 Audio JavaScript

In the last chapter, we introduced the <audio> tag. HTML5 audio also can be controlled by JavaScript. This is crucial because we are no longer simply dropping an <audio> tag with multiple sources pointed to it. See Listing 3.6 on how to add our audio to the game. Crafty has the checks built in to correctly choose between mp3 and ogg.

// Add the files to the  loader .
  Crafty . load ([" spritesheet . png",
    " background_music .mp3", " background_music . ogg ",
    " hit . mp3 ", "hit. ogg",
    " hit2 . mp3 "," hit2 . ogg "
   ], function () {
 console .log(" assets loaded ");
 Crafty . scene (" main "); // go to main scene
 });
  Crafty . scene (" main ", function () {

 Crafty . audio .add (" backgroundmusic ", [
  " background_music .mp3 ", " background_music . ogg "
 ]);
 Crafty . audio .add (" hit ", [
  " hit . mp3 ", "hit .ogg"
 ]);
 Crafty . audio .add (" hit2 ", [
  " hit2 . mp3 ", " hit2 . ogg "
 ]);

 // -1 means loop background music forever .
 Crafty . audio . play (" backgroundmusic ", -1);

Listing 3.6. Crafty play audio.

Crafty lets you specify the number of repeats. If left off, it will play once. The “−1” means loop the sound forever. Those familiar with game design know the need of creating a nicely looping background song. When it gets to the end, it seamlessly transitions to the beginning. The user plays the game without noticing the restart transition. Unfortunately, an HTML5 audio loop is not seamless. There may be a slight pause as the game loops back to the beginning of the track. The break length may vary between browsers or even within restarts within the game. When designing HTML5 game music, make sure to account for a variable bit of silence at the end.

Now that we have added audio and graphics, we need to make the game mobile friendly. Crafty has built-in mouse events that double as touch events. To use them, we must bind our entities to them. The obvious first attempt is to add “Mouse” to the bottom paddle’s Crafty constructor list and then bind to the click event. See below:

Crafty .e(" bottomPaddle ,2D, Canvas ,Mouse , Multiway , bottompaddle ")
...
. bind (" Click ", function (e)
{
 this .x = Crafty . mousePos .x;
});

The problem is that this event will only be active if the user clicks/taps on the actual paddle. We want the paddle to follow the user’s finger even if it is not quite on the paddle. Crafty does not have a convenient global mouse binding mechanism. It only binds to entities. Therefore, to react to all mouse/touch events, we need to create a full Canvas-sized entity and bind the mouse to it. See below:

Crafty .e(" mouseTracking , 2D, Mouse , Touch , Canvas ")
 . attr ({w:320 , h:480 , x:0, y:0})
 . bind (" MouseMove ", function (e)
 {
  // get bottomPaddle
  var bottomPaddle = Crafty (" bottomPaddle ");

  bottomPaddle .x = Crafty . mousePos .x - bottomPaddle .w /2;
});

Our invisible mouseTracking entity takes up the entire screen and consumes the mouse and touch movements. It then fetches the bottom paddle entity and sets the X coordinate of the middle of the paddle to wherever the mouse/touch is.

This will react the same to both touch start and touch move since Crafty routes those events through the same code as its mouse events. This works well for our purposes, but keep in mind, touch is not the same as clicking. Mobile Safari has guidelines and rules regarding when it will send an actual “click.” It involves the speed of the finger, the type of object being touched, the potential result, etc. [56]. In some cases, we will need to bind to touchstart and mousedown separately.

3.4.2 Sprite Animation

The last step is sprite animation. Sprite animation works by rapidly showing images with a slight variation to make it appear to move. As alluded to earlier, this would normally be a difficult task. Fortunately, adding sprite animation with Crafty is as easy as just declaring the entity supports sprite animation and what sprites to use. See the new declaration for our ball:

Crafty .e(" gameBall ,2D,Canvas , Collision , SpriteAnimation , ball0 ")
 . attr ({x: 100 , y: 100 , w: BALL_RADIUS , h: BALL_RADIUS ,
   xspeed : 2, yspeed : 4
  })
 . animate (' BallBlinking ', 2 ,1 ,4) // setup animation
 . animate (' BallBlinking ', 5, -1) // start animation

The first animate() command sets up the animation. It states where and the range of sprites to use. The next animate() starts the animation and says to loop it forever. What was once a black square at the start of the chapter is now an animated fireball.

3.5 Summary

We built a fully functional HTML5 game that can run on all modern web browsers and mobile devices, thus capable of reaching a very large number of users. Our game has sound, graphics, scoring, AI, keyboard controls, and touch controls. The game was built entirely on standards-based technologies, so it will continue to be playable, and maintainable, for years to come.

In Part II, we started off by building it from scratch, and then, in the first chapter of Part II we migrated to a game engine when we were ready for more heavy lifting. Crafty was chosen because it was lightweight and flexible, which is what we need for a quick game of Pong. There are many HTML5 game engines of varying strengths and weaknesses. Next, we will build a Tic-Tac-Toe game with the heavier EaselJS engine.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset