Chapter 5

image

Impact

5.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). Impact is proprietary and is not distributed. It may be purchased at http://impactjs.com.

5.2 Introduction

Impact is an HTML5 JavaScript game engine developed by Dominic Szablewski [63]. It is the only proprietary game engine reviewed in this book. Despite its proprietary nature, the engine is delivered in source form so you can modify it. Also, the barrier to entry is (as of this writing) a modest, no-royalty, one-time $99 fee. You get a lot for $99:

Impact

The engine itself.

Weltmeister

A well-organized level editor with tight Impact integration.

Source code for Impact and Weltmeister

Though these tools are not open source, you can still get benefits of low-level customization with access to the source code itself.

Demo games with source code

The examples demonstrate collisions, scrolling, and physics.

5.3 Setup

Setup is a bit different from the other game engines. Though Impact development mostly works standalone, Weltmeister saving and loading requires the use of a web server with PHP. Also, the files in your project have to be structured a particular way. Once again, this is mostly to serve Weltmeister. If you do not care to use Weltmeister, you have much more flexibility. However, Weltmeister is a really nice tool, and adhering to its requirements is a small price to pay.

Publishing with Impact requires access to command-line PHP. This process, called baking, is a minify utility that combines all your files into a single .js file for distribution. You must bake your project before publishing. Otherwise, you may accidentally distribute the Impact source code.

5.3.1 PHP Web Server

See page 32 for details on how to set up a web server for development on a Mac or Windows. For the purposes of developing with Weltmeister and Impact, you can do everything you need by going to http://localhost/. To bake, you need the direct command-line path to php.

If you do not have the php command in your system path, you will need to know the direct file path. For Windows users with WAMP, php.exe is located at:
wampinphpphp5.3.6php.exe

For Windows users with XAMPP, php.exe is located at:
xamppphpphp.exe

For Windows users with IIS, php.exe is located at:
C:ProgramFiles(x86)PHPv5.3php.exe

Mac users with a web server probably have PHP in their system path. If you do not, MAMP puts the php executable at:
/Applications/MAMP/bin/php/php5.3.6/bin/php

Note that your version of PHP may be slightly different. Replace “php5.3.6” with the correct version. Take the full path you found and modify “bake.bat” for Windows and “bake.sh” for Mac to use it.

5.3.2 Impact Project Structure

Every project developed with Impact requires its own copy of the Impact and Weltmeister libraries in a folder accessible by your web server (Figure 5.1). In this chapter, we are developing the game MechaJet. Therefore, I have a folder called “mechajet_impact” as a subdirectory of my web server root at “www”. Inside the project root there are a handful of files and directories. These serve a specific purpose.

Figure 5.1.

Figure showing Impact project file structure.

Impact project file structure.

lib

This contains the core logic for Impact, Weltmeister, and your game. All your code will go into the “game” subdirectory inside this directory. All projects have their own copy of Impact and Weltmeister.

media

Graphics, tile sheets, audio, and other media go into this directory. This directory is by convention. The actual media files can be anywhere. You point to them when declaring your entities.

tools

The tools required to minify (bake) your project for publishing are found here.

dev.html

This is a file I created with the minimal required code to run and test the unbaked game.

weltmeister.html

Run this in your browser to launch the Weltmeister level editor.

index.html and game.min.js

game.min.js gets generated when you minimize/bake your project. index.html is identical to dev.html except inside the file it points to game.min.js instead of the unbaked project. index.html gets uploaded to the public-facing server, while dev.html stays local on the machine. Do not upload Impact itself to your web server.

Within the lib/game directory, there is another project structure governed by Impact and Weltmesiter.

entities

The core logic for your game objects are here. You will spend most of your time programming here.

levels

This holds your level files that are saved by Weltmeister.

main.js

The boilerplate start-up code that ultimately launches your first level goes here. This also contains your code that doesn’t quite fit into a normal entity.

If you wish to change the arrangement of your files and have them still detectable by Weltmeister, there are some configuration options available inside lib/Weltmeister/config.js

5.4 Hello Impact

Impact is very easy to set up. When purchased, a link to download Impact will be emailed to you. Inside the zip package is a ready-to-go directory with all dependencies structured like mentioned above. Put the directory on your web server, go to index.html, and “It Works!” in Figure 5.2 is what you will see.

Figure 5.2

Figure showing Impact Hello World.

Impact Hello World.

Copy and paste the directory. Rename it to your game’s name, then edit it directly as your starting point. This is the recommended way to develop with Impact.

5.5 MechaJet Implementation

In this chapter, we will create MechaJet, our example game for Impact. Here is the problem statement:

“We want a 1-player action sidescroller written in HTML5 that demonstrates physics, tilesheets, animations, levels, and other abilities of Impact.”

Impact is compact and well organized and, like before, little from the previous chapters will be reused in order to follow the Impact way of doing things.

To support both keyboard and touch controls in the sidescroller, “touch arrows” are being added. The best way to explain is to show the final screenshot. See Figure 5.3.

Figure 5.3

Figure showing MechaJet screenshot.

MechaJet screenshot.

5.5.1 Tile Map, Collision Map

In all the previous examples, we simply hard-coded the location of all our objects (particularly with our EaselJS Tic-Tac-Toe example). This is convenient for small games where tweaking values is not a big deal. However, our MechaJet game is going to be more complex. We want to have multiple levels (just two for this example), and we do not want to have to go through and tweak a lot of values to move an object around. Ideally, we would like to separate the level design from the game engine itself.

The answer to this problem is a “tile map”. In our Crafty Pong game, we alluded to the use of a tile map. In this chapter, we will make a very simple one using Weltmeister. See Figure 5.4 for the tilesheet used in the map. Not shown is that most tile maps have an underlying collision map. Collision maps are the areas that are impenetrable from the user. Weltmeister solves this problem by having the collision layer have its own tiles that are drawn on top the tile map. This will be discussed later.

Figure 5.4

Figure showing Tiles for MechaJet.

Tiles for MechaJet.

5.5.2 Entities

With Impact, every sprite that the player interacts with is an entity. In our game, we have four main entities with several subentities. The main entities are:

Player

The human-controlled player. The player can fly, run, get hurt, shoot, die, and respawn. Whenever the player shoots, it spawns another entity (PlayerBullet). For convenience, the bullet entity can be defined in the same file as player. However, only the root entity with the file named after it will appear in Weltmeister. See the sprite sheet in Figure 5.5. The sprites are tiny because Impact will be double-sizing everything. This helps performance. There is animation for rolling, flying, falling, and a death sequence.

Figure 5.5

Figure showing MechaJet robot sprite sheet.

MechaJet robot sprite sheet.

Buzzard

The buzzard flies back and forth, applies damage to the player, and dies in one hit. It also has a death sequence. See the sprite sheet in Figure 5.6. There is animation to fly and explode.

Figure 5.6

Figure showing MechaJet buzzard sprite sheet.

MechaJet buzzard sprite sheet.

Corridor

The corridor to the next level simply has a glow effect. Its sprite sheet is in Figure 5.7.

Figure 5.7

Figure showing MechaJet corridor sprite sheet.

MechaJet corridor sprite sheet.

Bomb

Our bomb explodes when the player touches or shoots it. Instead of a sprite sheet, it is just a static image. It will be replaced with another static image when it explodes. These images are shown in Figure 5.8, and 5.9.

Figure 5.8

Figure showing MechaJet bomb image.

MechaJet bomb image.

Figure 5.9

Figure showing MechaJet bomb boom image.

MechaJet bomb boom image.

What you don’t see being defined above are the entities for our HUD (our “Heads-Up-Display,” the health bar and arrow controls). Unfortunately, there is no convenient spot within Impact to put a HUD.

We could declare our health and arrows to be immortal entities with no gravity and hit detection. That is what we did with Crafty Pong, and that worked fine. Reusing that strategy might work here, but my experience has always found this path difficult. Impact and Weltmeister makes lots of assumptions for you, and if you aren’t careful, your health bar may scroll off the screen along with your other entities. Instead, I have found the best way to do a HUD with Impact is to pull that logic out of the entities and directly apply the updates manually within the main game loop (defined in main.js). That way, we regain the control we need, and there is no chance Impact may misinterpret our intent with it. This technique will be shown later.

5.5.3 Buzzard Entity

We are finally ready for code. As mentioned before, you must write your entities in a certain way for them to work properly with Weltmeister, and we want to work with Weltmeister so our level design is significantly easier. This is an in-depth explanation of the buzzard entity broken into several parts because Impact is both dense and verbose. The examples following the buzzard will be a bit more high level. See Listing 5.1.

ig. module (
 'game . entities . buzzard '
)
. requires (
 'impact . entity '
)
. defines (function () {
EntityBuzzard = ig. Entity . extend ({
 size : {x:16 , y:16} ,
 gravityFactor : 0,
 collides : ig. Entity . COLLIDES . PASSIVE ,
 type : ig. Entity . TYPE .B,
 checkAgainst : ig. Entity . TYPE .A,

Listing 5.1. Impact buzzard header.

Impact uses a file include structure that may have been inspired by Google Closure (for those who may have seen it). When you create an entity, you are actually creating a module that gets loaded into the Impact engine and Weltmeister. Wherever this entity is used, you must include the module in its ig.module list.

The module declaration is the file path minus the .js. The entity name is called Entity<Name Of Entity>. Both parts are capitalized. You can have other entities in the file, but the name of the core entity is the same as the file name.

That means our “buzzard” entity is declared as EntityBuzzard in a file located at game/entities/buzzard.js. This is the structure required to work with Weltmeister.

Inside the file, we have already declared some settings for our entity. Impact has lots of built-in parameters. Only a handful are actually required as it will make assumptions for whatever is missing. In our buzzard entity, we declared the size to 16 pixels by 16 pixels. The gravityFactor is 0 meaning Impact’s gravity simulation has no effect on the buzzard.

Impact’s built-in collision system is far smarter and more fluid than the crude method we used for EaselJS. It is actually a two-step system where it collides with the world and moving objects. Whereas our own collision system checked after we collided and then moved the object, Impact checks before it moves and has separate reactions depending on the collision settings. The different reactions are:

ACTIVE

An ACTIVE will collide with ACTIVE or PASSIVE and both will be separated after collision. Normally, players and enemies are declared PASSIVE. The buzzard is declared ACTIVE so it gets a bounce effect (determined by the “bounciness” variable) to look lightweight when colliding with a PASSIVE player. Separating the two also prevents the player from taking multiple hits.

PASSIVE

There is no separation when PASSIVE collides with PASSIVE. The units may walk through each other. The Bomb is declared PASSIVE so it does not slow down the PASSIVE player.

FIXED

Nothing will move this entity. This mode is used for the corridor.

LITE

Collides with ACTIVE and FIXED.

NEVER

Does not collide. The entity spawned by bomb explosion is declared NEVER.

The other portion of collision detection is declaring types. Friendlies should be declared ig.Entity.TYPE.A while baddies should be declared ig.Entity.TYPE.B. When an entity hits a type declared in its checkAgainst, that unit’s check() function will be called. In there, the unit can give the other entity damage. See Listing 5.2 for the next round of code and explanation.

 flip :false ,
 update : function () {

   if(this . vel .x == 0)
   {
  if(this . flip)
  {
  this . flip = false ;
  this . vel .x = -10;
 } else {
  this . flip = true ;
  this . vel .x = 10;
 }
  }

   if(this . vel .x < 0)
   {
  this . currentAnim . flip .x = false ;
  } else {
  this . currentAnim . flip .x = true ;
  }

   this . parent ();
},

Listing 5.2. Impact buzzard update.

At the top we have a new variable, called flip. This is used by us to track the direction of the entity. When the entity stops and we want to go in the opposite direction, we need to know the previous direction. This movement change decision happens in the update() function, which is Impact’s way of processing code every frame. It is equivalent to Crafty’s EnterFrame event and EaselJS’s createjs.Ticker.addEventListener(’tick’, myFunc) function.

Our frame event tells the buzzard to move to and fro and change direction and spritesheet animation whenever it hits a wall. Impact is the first engine we have examined with a nice built-in physics system that saves us a lot of effort. Before, we were manually moving the sprites ourselves every frame. Now, we have a velocity variable exposed. We can give “vel.x” or “vel.y” as speed, and Impact will move the entity each frame for us. If we are traveling less than zero, we flip the sprite sheet over using this.currentAnim. Lastly, we don’t bother looking or testing for collisions. We just look to see if we have stopped. If we have stopped, we can assume it is because the engine made us stop because we hit something.

The last piece calls this.parent(). Now that we’ve made our modifications, we pass control back to Impact. See Listing 5.3 for the rest of our buzzard entity.

   animSheet : new ig. AnimationSheet (
  ' buzzardbaddie .png ', 16, 16),

   check : function (other) {
  other . receiveDamage (20, this);
  },
   kill : function () {
  ig. game . spawnEntity (EntityBuzzardBoom ,
   this . pos .x, this . pos .y);
  this . parent ();
  },

   init : function (x, y, settings) {
  this . parent (x, y, settings);
  this . addAnim ('idle ', 0.4 , [0 ,1]);
  this . vel .x = -10;
  }
});

Listing 5.3. Impact buzzard init.

Impact’s animation system slices a spritesheet from left to right labeling each image as 0, 1, 2, 3, etc. When it reaches the end of a row, it continues its numbering on the next row. To set up animation with Impact, give AnimationSheet() an image and the size of your sprites. For our buzzard, our sprite sheet image is “buzzardbaddie.png”, and each section of our sprite sheet is 16 × 16 pixels. Inside the init function, we set up each animation. Our buzzard only has one animation, called idle. The delay between each frame is 0.4 seconds, and the frames used in the animation are located at 0 and 1.

Back to collisions. Earlier, we declared this entity to checkAgainst ig.Entity.TYPE.A. When it collides with type A, the check() function gets called. Here, we assign 20 damage to other, which would be the player.

The last piece is kill. Every entity has a kill function that gets called as it is being destroyed (similar to the idea of a “‘destructor” in object-oriented programming). For our buzzard example, we are going to spawn EntityBuzzardBoom at the current location. This is a small explosion effect/entity declared in the same file and presented in its entirety in Listing 5.4.

EntityBuzzardBoom = ig. Entity . extend ({
 size : {x:16 , y:16} ,
 collides : ig. Entity . COLLIDES .NONE ,
 killTimer :null ,
 animSheet : new ig. AnimationSheet (' buzzardbaddie .png ',
  16, 16),
 update : function () {
 this . parent ();
 if(this . killTimer . delta () > 1) {
  this . kill ();
}
},
 init : function (x, y, settings) {
 this . parent (x, y, settings);
 this . killTimer = new ig. Timer ();
 this . killTimer . reset ();
 this . addAnim ('idle ', 0.5 , [2 ,3]);
}
});

Listing 5.4. Impact EntityBuzzardBoom.

spawnEntity() is the preferred way to dynamically create entities within a game. Settings can be optionally overloaded by creating a settings object to be passed to the spawnEntity() function. The Player entity uses this to control bullet direction. Weltmeister also uses it to allow you to change settings on-the-fly within the editor, which will be demonstrated with the corridor later.

The BuzzardBoom entity has no collision and uses the explosion animation section of our buzzardbaddie.png spritesheet. The notable edition killTimer has been added. Impact has a Timer object that is updated every frame to measure seconds of game time (not real time). Using the Impact Timer, the BuzzardBoom entity calls its own kill() function after appearing on the screen for 1 second. Note that because Impact adds default gravity, BuzzardBoom will “fall” out of the sky.

5.5.4 Bomb Entity

Our bomb entity is almost identical to the buzzard entity. It deals damage to the player when touched, and it explodes in its kill() event. The only subtle difference is that it doesn’t move, it dies immediately when touched, and it plays a sound when it explodes. Listing 5.5 shows the complete bomb entity. Other than being slightly larger and using a different sprite sheet, BombBoom is identical to BuzzardBoom, so it is not shown.

EntityBomb = ig. Entity . extend ({
 size : {x:16 , y:16} ,
 collides : ig. Entity . COLLIDES . PASSIVE ,
 type : ig. Entity . TYPE .B,
 checkAgainst : ig. Entity . TYPE .A,
 boomSound : new ig. Sound ('bombboom .*'),

 animSheet : new ig. AnimationSheet ('bombitem .png ', 16, 16),

 kill : function () {
 ig. game . spawnEntity (EntityBombBoom , this .pos .x -16 ,
  this . pos .y - 16);
 this . boomSound . play ();
 this . parent ();
},

 check : function (other) {
 other . receiveDamage (50, this);
 this . kill ();
},

 init : function (x, y, settings) {
 this . parent (x, y, settings);
 this . addAnim ('idle ', 1, [0]);
}
});

Listing 5.5. Impact EntityBomb.

First, the collision type is PASSIVE so it doesn’t slow down the player. Second, the check() function gives the player 50 damage and then calls kill() on itself. Like the buzzard, the kill function is overloaded. It spawns a no-collision “boom” entity called EntityBombBoom. Some offsets are applied because the bomb’s boom is significantly larger than the bomb item. This puts the core of the explosion in the center.

Like all the previous engines, Impact will load the appropriate audio (.ogg or .mp3) depending on the browser’s capabilities. This is performed with declaring the audio bombboom.*. The correct file is played whenever play() is called.

5.5.5 Corridor Entity

The corridor is an immortal fixed entity that looks to see if it has collided with the player. This creates a simple file with the interesting portion shown in Listing 5.6.

  collides : ig. Entity . COLLIDES .FIXED ,
  type : ig. Entity . TYPE .B,
  tolevel : " Mechajetlevel2 ",
  checkAgainst : ig. Entity . TYPE .A,
  health :9999 ,
  check : function (other) {
 if (other instanceof EntityPlayer)
 {
  ig. game . loadLevelDeferred (ig. global [" Level " +
     this . tolevel]);
}
 },
  receiveDamage : function (amount , from){
 return ; // immortal
 },

Listing 5.6. Impact corridor.

Corridor is type B that checks against type A (our player). Its receiveDamage() has been overloaded to do nothing (in case something accidentally does assign it damage). Its check() function looks to see if other is EntityPlayer. If so, the special Impact function loadLevelDeferred() is called. This waits until the end of the update cycle and properly deconstructs our level and loads the next one specified.

In this example, we are loading a level created by Weltmeister. Similar to the entity naming format, all levels created by Weltmeister are in the format Level<Name Of Level> with the first letter of the level name capitalized. We have created a tolevel variable so we can change the level being loaded from within Weltmeister.

5.6 Weltmeister

Before starting on the player entity, now is an appropriate time to draw the first level with Weltmeister. We have enough information to put together a collision map/tile map. The level needs to come before the player because development of an entity as complex as our player entity will not go far without putting it in a level.

If you unzipped and dropped in your Impact directory as-is on to your web server, then your Weltmeister installation can be reached at: http://localhost/yourdevdirectory/weltmeister.html

Weltmeister will scan your game/lib/entities directory for *.js files. If you followed the naming structure stressed throughout the chapter, it will find your entities and make them available to you in the editor. See the screenshot in Figure 5.10 for a level in progress. If you are having problems with Weltmeister, make sure you have PHP working.

Figure 5.10

Figure showing Weltmeister building level 1.

Weltmeister building level 1.

When first going to Weltmeister, you will be presented with a blank screen with the always-available “entities” layer. The first thing you will want to do is select this entities layer and then press the space bar to see if all the entities you created in game/lib/entities are available. If they are, press space to cancel and then use the plus symbol near the top right to create two more layers and rename them. You will eventually have these three layers named “entities,” “main,” and “collision.” As you create these layers, you are given these options:

Name

The name of the layer. Note that the name “collision” is reserved for the collision layer.

Tileset

Select the image that represents your tiles.

Tilesize

It must be square. The default is 8, meaning 8 × 8 pixels.

Dimensions

Decides the size of your layer. The size is measured by tile size. A 20×30 dimension with a tilesize of 8 will be a 160 × 240 layer.

Distance

Determines scroll speed. “1” will scroll the same speed as the entities. “2” will scroll slower, and “3” will be even slower. This can be used to give a parallax scrolling effect.

Pre-Render in Game

Will pre-render your map. This can improve performance. The trade-off is that your tiles cannot be animated.

Repeat

Will repeat your tiles as it scrolls by. This can be used to create a repeating background effect.

Is Collision Layer

States the layer is a collision layer and will start using collision tiles allowing you to draw a collision map. Impact comes with a set of collision tiles you can use with your main layer tile set.

Link with Collision

Will allow you to create tiles that automatically have a solid collision tile behind it located on the collision layer. This can save you some mouse clicks.

Once your layers are configured, save your level. The convention is to save Impact levels into the directory lib/game/levels. Weltmeister will append “.js” to the name.

You are now ready to draw a level. First, you will probably want to draw collision tiles around the border to prevent the player from falling off the world. When working with the level editor, space will display the entity/tile selection box. Click to select. Then left click to paint. Holding right-click, drag the view around on your screen. Use the mouse wheel to zoom.

To erase an entity, click the entity layer (if not already selected), and then press the delete key. Erasing tiles is not so obvious. To erase a tile, click the appropriate layer, then press space to display tile selection, and then look at the top left corner of your tile selection box. There is a blank highlighted tile standing alone. Click the blank highlighted tile. Now paint the blank tile over the tiles you want to delete. This will delete them.

You now know the basics to build a level, but before we move on, here are a few handy keyboard shortcuts.

  • The “z” key is undo.
  • You can quickly clone an entity with the “c” key.
  • You can copy a tile by clicking it while holding down the shift key.
  • You can copy multiple tiles by dragging while holding down the shift key.

5.7 main.js

Like the name suggests, main.js is the entry point of the game. It contains the logic necessary to initialize the canvas, key binding, load the first level, and then start the game. Actually, right now, it won’t load the first level because Weltmeister just created it, and main.js does not yet know it exists. See Listing 5.7 for the minimum amount of code to launch your level with keyboard and mouse binding.

ig.module (
 'game .main '
)
.requires (
 'impact .game ',
 'game . levels . mechajetlevel1 ',
 'game . entities . player '
)
.defines (function (){
  MyGame = ig. Game . extend ({
  init : function () {
  ig. input . bind (ig. KEY . UP_ARROW , 'up '),
  ig. input . bind (ig. KEY . DOWN_ARROW , 'down '),
  ig. input . bind (ig. KEY . LEFT_ARROW , 'left '),
  ig. input . bind (ig. KEY . RIGHT_ARROW , 'right '),
  ig. input . bind (ig. KEY . MOUSE1 , " CanvasTouch ");
  ig. input . bind (ig. KEY .X, " xkey ");
  this . loadLevel (LeveMylevel);
}
 }
});
// 60 fps , 320 x240 , scaled by 2
ig. main ('#canvas ', MyGame , 60 ,480/2 ,320/2 , 2);
});

Listing 5.7. Impact main.js header.

Our level has been added to requires. It is the path to the JavaScript file. Then we launch our level inside init. Note the name that Weltmeister gave the level. You can always open the JavaScript file of the level to find the name.

Though these events do not do anything yet, we also binded the mouse, keyboard, and touch to our game. The reaction to these events will be processed in the player entity. For now, we need to add our arrow buttons so non keyboard users have control. We will be using the technique discussed earlier. These buttons are managed outside Impact entities. See Listing 5.8.

  leftButton : {x:0,y :320/2 -32 , l:32} ,
  rightButton : {x:32 ,y :320/2 -32 , l:32} ,
  upButton : {x:16 ,y :320/2 -64 , l:32} ,
  downButton : {x :480/2 -32 , y :320/2 -32 , l:32} ,
  xButton : {x :480/2 -32 , y :320/2 -64 , l:32} ,

  collisionDetect : function (object1 , object2)
  {
  // Same used in Tic -Tac - Toe
 },
  buttonCheck : function (theButton) {
 if (! ig. input . state (" CanvasTouch "))
   {
   return false ;
}
 return this . collisionDetect (theButton , ig. input . mouse);
 },
  hudSheet : new ig. Image ('fadedarrow .png '),
  draw : function () {
 this . parent ();
 this . hudSheet . drawTile (this . leftButton .x,
  this . leftButton .y, 4, 32);
 this . hudSheet . drawTile (this . rightButton .x,
  this . rightButton .y, 1, 32);
 this . hudSheet . drawTile (this . upButton .x,
  this . upButton .y, 0, 32);
 this . hudSheet . drawTile (this . downButton .x,
  this . downButton .y, 3, 32);
 this . hudSheet . drawTile (this . xButton .x,
  this . xButton .y, 2, 32);

Listing 5.8. Impact main.js header.

First, you will create the buttons as objects. This makes them easier to track and pass in to our collisionDetect() function. The collisionDetect() function is our standard bounding box check identical to what was used in Tic-Tac-Toe. We have to use our own collision detect because the arrow buttons are not entities. buttonCheck() simply checks to see that the mouse/touch is actually occurring before calling collisionDetect. Finally, the draw function was overloaded to draw our arrow keys after it finishes drawing the other sprites.

5.8 Player Entity

Our player entity wraps up all the techniques we used for the Bomb (a death sequence) and Buzzard (movement) as well as adding more (projectiles, control, and respawn). First, we will add just enough code so the player can be placed in the game by Weltmeister. See Listing 5.9.

ig. module (
 'game . entities . player '
)
. requires (
  'impact . entity '
)
. defines (function () {
EntityPlayer = ig. Entity . extend ({
 size : {x:16 , y:16} ,
 gravityFactor : 7,
 type : ig. Entity . TYPE .A,
 checkAgainst : ig. Entity . TYPE .B,
 flip : false ,
 collides : ig. Entity . COLLIDES . PASSIVE ,
 animSheet : new ig. AnimationSheet ('robot .png ', 16, 16),
 init : function (x, y, settings) {
 this . parent (x, y, settings);
 this . addAnim ('idle ', 0.1 , [0 ,0]);
 this . addAnim ('roll ', 0.1 , [0 ,1]);
 this . addAnim ('fly ', 0.1 , [12 ,13]);
 this . addAnim ('death ', 0.7 , [24 , 25 ,26] , true);
}
});
});

Listing 5.9. Impact player header.

Our entity doesn’t do anything, but at least Weltmeister can see it, and we can put it in our level. With our sprite available, we will be developing main.js and player.js to make all the controls available. Because we’ve already binded the keyboard and tracked buttons in main.js, we add the control logic to the player.js. There is a lot of sprite manipulation happening with the movement. Our player can roll, fly, or fall. This can happen in either direction. Therefore, we need to keep track of the flip state and if the player is on the ground and swap out animation accordingly. For clarity, only the X button and left buttons are shown in Listing 5.10.

  update : function () {
   // ig. log (this . friction .x, this . vel .x)
   if(ig. input . state ('left ') || this . leftButtonDown) {
  this . accel .x = -100;
  if (! this . vel .y)
  {
   this . friction .x = 35;
   this . currentAnim = this . anims . roll ;
 } else {
   this . friction .x = 0;
   this . currentAnim = this . anims . idle ;
 }
  this . currentAnim . flip .x = true ;
  flip = true ;
 }
  if (! this . vel .y && ! this . vel .x)
  {
  this . currentAnim = this . anims . idle ;
  this . currentAnim . flip .x = flip ;
 }
  if(ig. input . state ('xkey ') || this . xButtonDown) {
   var bulletsettings = {flip : this . currentAnim . flip .x};
   // this forces 1 bullet at a time
   var alreadythere =
  ig. game . getEntitiesByType (EntityPlayerBullet)[0];
   if(! alreadythere) {
  this . shootSound . play ();
  ig. game . spawnEntity (EntityPlayerBullet ,
    this . pos .x, this . pos .y, bulletsettings);
  }
}
   this . parent ();
 },

Listing 5.10. Impact player header.

Note that we are modifying the velocity variable like we did with the buzzard. Impact will look at the velocity and update the sprite on each frame accordingly, saving us a lot of hassle. Also, we are modifying friction. If we are in the air, we turn off friction. Otherwise, we slow down if we are on the ground. Additional variables have been added (leftButtonDown, xButtonDown, etc). These values are from the arrow sprites triggered by mouse/touch and are passed to the player from main.js.

The other piece to notice is spawning EntityPlayerBullet whenever X key or button is pressed. This entity is created in the same file just as we did buzzardboom and bombboom. The only difference is passing the flip state so we know which direction the bullet was fired. Lastly, we call this.parent() so impact can update the entity.

Now, we need to modify main.js to pass the touch and mouse clicks. See Listing 5.11.

. requires (
  'impact .game ',
  'game . levels . mechajetlevel1 ',
  'game . levels . mechajetlevel2 ',
  'game . entities . player '
)
/* REMOVE FOR CLARITY */

 update : function () {
  this . parent ();
  // screen follows the player
  var player = this . getEntitiesByType (EntityPlayer)[0];
  if(player) {
 this . screen .x = player . pos .x - ig. system . width /2;
 this . screen .y = player . pos .y - ig. system . height /2;

 // track button  pushes
 player . leftButtonDown  =
  this . buttonCheck (this . leftButton);
 player . rightButtonDown =
  this . buttonCheck (this . rightButton);
 player . upButtonDown =
  this . buttonCheck (this . upButton);
 player . downButtonDown  =
  this . buttonCheck (this . downButton);
 player . xButtonDown =
  this . buttonCheck (this . xButton);
 }

Listing 5.11. Impact player header 2.

There are a few things happening in the new main.js update function. First, we added the player entity to our requirements list. Next, in the update function, we fetch the player entity. Impact fetch returns an array. We only need the first. We check to see if the player exists. If it does, we center the screen on the player. Because we are doing this constantly with every update, this will have a smooth-scrolling effect. Impact is well optimized to handle smooth scrolling for this simple game, but note that a more efficient strategy would be to scroll only when the player is near the edge of the screen.

Finally, we check the state of our buttons with the collision checks we added earlier. The player looked at these button states in its update function back in Listing 5.10. The bullet-spawning section we added will need to be commented out, but other than that, our player should now be able to move around the screen by keyboard, mouse, or touch. The gravityFactor will cause the player to fall, and the collision tiles will cause the player to stop when it lands on a platform.

We are now ready to add the player’s bullets. See Listing 5.12 for the bullet entity.

EntityPlayerBullet = ig. Entity . extend ({
 size : {x:16 , y:16} ,
 collides : ig. Entity . COLLIDES . PASSIVE ,
 type : ig. Entity . TYPE .A,
 gravityFactor : 0,
 checkAgainst : ig. Entity . TYPE .B,

 animSheet : new ig. AnimationSheet ('bullet .png ', 8, 8),

 check : function (other) {
 other . receiveDamage (10, this);
 this . kill ();
},

 update : function () {
 this . parent ();
 if(this . vel .x == 0) {
  this . kill ();
}
},

 init : function (x, y, settings) {
 this . parent (x, y, settings);
 if(settings . flip) {
  this . vel .x = -100;

} else {
  this . vel .x = 100;
}
 this . addAnim ('idle ', 0.1 , [0 ,1]);
}
});

Listing 5.12. Impact player header 3.

EntityPlayerBullet is essentially a reduced version of EntityBuzzard. The only needed changes were changing type B to type A, checkAgainst to type B, and the sprite sheets. As mentioned earlier, settings.flip looks to see which direction to travel. The check function has been overloaded to give 10 damage to whatever it hits and then call its own kill function.

Next, we need to add the player death sequence. Unlike with BuzzardBoom or BombBoom, we want the player to respawn. Rather than killing the player and then immediately spawning a one-off death entity, the player is going to show its own death sequence. See Listing 5.13.

  kill : function (){
 ig. game . startXY = this . startXY ;
 this . killTimer = new ig. Timer ();
 this . killTimer . reset ();
 this . killcallback = this . parent ;
 this . dead = true ;
 this . collides = ig. Entity . COLLIDES . NONE ;
 },
  update : function () {
 if(this . dead)
 {
  this . vel .x = 0;
  this . vel .y = 0;
  this . currentAnim = this . anims . death ;
  this . currentAnim . flip .x = flip ;
  if(this . killTimer . delta () > 2) {
  this . killcallback ();
  this . restartSound . play ();
  ig . game . spawnEntity (EntityPlayer ,
   ig. game . startXY .x, ig. game . startXY .y);
 }
} else {

Listing 5.13. Impact player respawn.

The kill() function has been overloaded for the death sequence. Instead of dying, player.dead has been flagged true and the parent kill function has been saved to killcallback. Collision has been turned off. killTimer has been started.

During the update function, all the normal player interactions have been bypassed so the only thing that happens is the death animation gets played. After 2 seconds, this.killcallback() gets called to complete Impact’s kill() process. The restart sound gets played and another player entity is immediately spawned to the original starting location.

5.9 Level 2

The first level of MechaJet is now playable. However, when the Corridor is reached, there is no level 2. The game will crash. We need to go back to Weltmeister and create a 2nd level. See the screenshot in Figure 5.11.

Figure 5.11

Figure showing Weltmeister building level 2.

Weltmeister building level 2.

Level 2 is built the same way as level 1. The actual level is not important. Look in the bottom far right. Corridor is selected. Inside the corridor parameters there are “Key:” and “Value:” fields. These let you overload default parameters declared in the entity. Remember the tolevel property declared when first programming the Corridor entity (Listing 5.6)? We are now overloading the default value from within Weltmeister. This will cause this corridor to go back to level 1 when it is reached. Any field can be overridden this way. This feature allows a great level of flexibility within Weltmeister.

With level 2 built, it needs to be added “main.js” so it knows what to load when told. See below:

ig . module (
  'game .main '
)
. requires (
  'impact .game ',
  'game . levels . mechajetlevel1 ',
  'game . levels . mechajetlevel2 ',
  'game . entities . player '
)

This completes the game. The player will go to the 2nd level when reaching the 1st corridor, and the player will be sent back to the 1st level when reaching the 2nd corridor. However, from a game design standpoint, the game is arguably not fully complete. There is no way to “win” the game. It is an endless loop from level 1 and level 2. However, it is complete enough for our tech demo.

5.10 Summary

In this section, we built MechaJet for Impact. We looked at the Impact folder structure and Impact’s general way of doing things, particularly with getting our Entities to work with Weltmeister, the Impact level editor. From there, one-by-one, we created each entity and put them in our level. We then created our 2-level simple action side-scroller that can be controlled by keyboard, mouse, and touch.

A topic that didn’t get touched on is Impact’s built-in debugger. By including the debugger in the requires list, the Impact debugger will be launched and shown as you play your game. See below:

ig . module (
  'game .main '
)
. requires (
  'impact .game ',
  'game . levels . mechajetlevel1 ',
  'game . levels . mechajetlevel2 ',
  'impact . debug .debug ',
  'game . entities . player '
)

The information shown includes direction vectors, object counts, FPS, draws, etc. This information can be really helpful to fine-tune your game’s performance. To remove this information, comment out the line in the requires list.

Next is our final, most complex (in both setup and development), game engine: The 3D Turbulenz game engine.

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

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