29 November 2011
Previous experience working with Dreamweaver and setting up a local host server is recommended. Prior knowledge of HTML5 and JavaScript code is also required.
Additional required products
All
In this article, you'll learn how to work with Impact and Dreamweaver CS5.5 to build HTML5 games. ImpactJS is a JavaScript game framework created by Dominic Szablewski. It takes advantage of the modern browser’s Canvas element in order to create high-performance 2D games on the web and even mobile platforms. Impact is very easy to pick up, comes with very good code examples, has an active community, and includes a very robust level editor called Weltmeister. The only barrier of entry is the licensing fee for the software because it is not open source. After purchasing a license you recieve the full source code, the Weltmeister editor, and free updates. While there are many other open source and free JavaScript game frameworks out there, Impact has an extra level of polish I haven’t encountered in the other HTML5 game frameworks I've tested.
Perhaps one of the most appealing factors of buying Impact is the inclusion of a sample Objective-C project that allows you to compile your Web game into a native iOS app, which takes advantage of OpenGL for graphics and OpenCL for sound. This solution gives your game native performance on iOS, and it can be packaged up and sold in the Apple App Store just like a native app.
Note: Impact works very well running on WebKit browsers, especially Chrome, but any modern browser with support for Canvas will also work.
Before building an HTML5 game with Impact, you'll need to prepare the web development environment, so that you can take full advantage of the features included in Impact and its level editor. Let’s take a look at configuring Apache, install the necessary tools, and create a new project in Impact.
There are many resources out there for installing Apache and PHP on your operating system of choice. Here are some simple one-click solutions to help get you up and running as quickly as possible.
Mac OSX users can use an all-in-one solution such as MAMP. MAMP is a one-click solution for setting up Apache, PHP, and MySQL on your Mac. Likewise, you can also use the built-in version of PHP that comes with OS X, but you will need to do some manual configuration of Apache to get it working. Simply do a search for Enabling PHP in Mac OS X to find instructions on how to do it.
Just like on Mac, XAMPP, there are some excellent one-click solutions for setting up Apache, PHP and MySQL. I have used XAMPP in the past and have had excellent success with it.
Impact is a self-contained project. Each game you create will require you to copy the default Impact project file (which you get once you buy a license) into a new location on your Apache server and start from scratch. For your first project, you could easily do all your work from your local host, but when it comes to building multiple games at the same time or organizing your web projects, it is best if you set up a VirtualHost in Apache. To keep things simple, we are going to use a local host. For the purposes of this sample project, I've set up a local host called html5gameworkshop.local.
To get started, copy the Impact project folder into your local host. You will see the following files (see Figure 1):
Here is a quick breakdown of the files in the Impact folder:
You should now have everything you need to run your first game. Open a browser and navigate to your localhost to see the following page (see Figure 2):
Sprite sheets are going to be the primary way that we display and animate artwork in Impact games. A sprite is a single bitmap image that is drawn to the display (in this case, our Canvas element). To help organize them better, sprites are grouped together into a single image called a sprite sheet. The following is an example of a sprite sheet from the game we are going to build (see Figure 3).
Sprite sheets are usually set up in an easily dividable way. So, in this case, each sprite is 16 x 16 pixels. This spacing allows us to simply divide the sprite sheet by 16 and automatically figure out that there are 10 sprites. Generally, when doing animation from a sprite sheet, we would tell the animation engine which sprite is part of each animation set. Here is an example of how that works in Impact:
this.addAnim( 'idle', 1, [0] );
this.addAnim( 'run', 0.07, [0,1,2,3,4,5] );
this.addAnim( 'jump', 1, [9] );
this.addAnim( 'fall', 0.4, [6,7] );
We will explore this a little later on when we begin setting up our game. As you can see, sprite 0 is our idle animation, while 0-5 represent our run animations. It is important to note that JavaScript’s Arrays are zero-based; we must take this into account when it comes to registering sprite sheet animation. So, the first sprite is always 0.
Sprite sheets are also good for non-animated graphics, such as tiles for a level. The following sprites are tiles that will make up the background of our game (see Figure 4).
So, instead of registering animations manually, we can tell the game engine which sprites represent walls, decorations or any other art you would need for your level’s design. Here is an image from Impact’s level editor that illustrates how these tiles will look in the game (see Figure 5):
Now that we have a basic understanding of sprite sheets, let’s look at how to actually use them in our game.
Let’s open up Weltmeister, Impact’s level editor and create a level. Follow these steps:
weltmeister.html page in the root of your project. When you open up Weltmeister for the first time, you will see that an untitled.js file has been created for you but that the level is empty.
When a new layer is created, its bounding box shows the borders of the layer. This is based on the layer’s dimensions. You can’t draw tiles outside of this box. We will also have to configure the layer’s properties. Here is a summary of what each property represents:
So, now we are ready to make our first level. Follow these steps:
Using the tile painter, you can select the tile you want to use. Once you make your selection, you can start drawing down tiles.
Notice the empty yellow box at the beginning of your sprite sheet. Select the yellow box when you want to erase tiles you have painted. Weltmeister automatically creates the empty tile for you, so you don’t need to add it to your sprite sheet.
I know this level doesn’t look very exciting yet, but don’t worry, we will get into more complex level creation later on.
In this section, we save maps and then load them in Weltmeister. Since we just created a level in the previous section, it's important to save the recent changes. We should save our map really fast by hitting Save at the top. You will notice that, by default, Weltmeister automatically puts levels in the lib/game/levels/ directory .
This location is very important because it is the folder where Impact will automatically look for your game’s levels. Later on, we will be able to simply tell Impact to load your level by its name, instead of being required to pass it the full directory path.
Let’s name our the first level test.js in the Save Level field and click Save.
Note: You must add the .js file extension as you name the level. The file is actually a JSON file. The Weltmeister editor will display an error if you forget to add the correct file extension.
When saving, you may get the following error (see Figure 11):
This is easy to fix; it’s just a permissions error. Navigate to the lib/game/levels directory on your computer and fix the read/write permissions. (see Figure 12).
It’s also important to note that you must have PHP set up in order to save levels. The editor itself is a JavaScript application, but the save scripts use PHP to function.
You can also easily load any level you have created by selecting Load in the top menu (see Figure 13).
You will see a list of all the maps you have created. By default, Weltmeister automatically attempts to load the last level you were working in.
There is one thing we have to do, though, before we can start building our game and that is set up the collision layer. This tells Impact what tiles are passible and impassible to the player and other game objects. To set this up, we need to create a new level called collision and load the collision Tileset: media/collisiontiles-8x8.png. This comes in the default Impact project. Once you have the collision layer in place, let’s start painting.
As you can see, there are several collision types set up for us. We are going to focus on solid, which is the pink S tile. The other collision tiles represent slime, and water. You can also add your own tiles. Use the tiles to paint on top of the main level’s walls (see Figure 15).
Usually, I put the CollisionMap underneath the main layer. You can reorder layers at any time by simply dragging them around. Now, when we create our player, Impact game enginewill make sure they don’t fall through the floor.
It looks like we are finally ready to start building our game!
When setting up a new Impact project from the template project, you will see a main.js file in your game directory. The main class is the entry point to your application and will contain some of the core logic such as binding keyboard events and logic for making the camera follow the player. Most importantly, the main file also defines and loads any required files or global functions in your game. Here is the default main.js class you will start with:
ig.module(
'game.main'
)
.requires(
'impact.game',
'impact.font'
)
.defines(function(){
MyGame = ig.Game.extend({
// Load a font
font: new ig.Font( 'media/04b03.font.png' ),
init: function() {
// Initialize your game here; bind keys etc.
},
update: function() {
// Update all entities and backgroundMaps
this.parent();
// Add your own, additional update code here
},
draw: function() {
// Draw all entities and backgroundMaps
this.parent();
// Add your own drawing code here
var x = ig.system.width/2,
y = ig.system.height/2;
this.font.draw( 'It Works!', x, y, ig.Font.ALIGN.CENTER );
}
});
// Start the Game with 60fps, a resolution of 320x240, scaled
// up by a factor of 2
ig.main( '#canvas', MyGame, 60, 320, 240, 2 );
});
Let’s take a moment to go through some of the high-level code. Begin by reviewing the first function:
ig.module(
'game.main'
)
This represents the namespace of your game. As you can see, this also defines the name of your main class.
.requires(
'impact.game',
'impact.font'
)
This block of code tells the module what JS classes are needed by the game. These classes will automatically load when your game is run for the first time. First, you’ll see that impact.game is the class your game extends from; I’ll talk about this next. After impact.game comes impact.font,which is the font class. By default, the main class of a new project displays some text that says “It Works!” In order to display text, you will need the font class.
Finally, everything in the .defines(function(){ ... }) block of code is your game logic. As you can see, we set up a local variable for the font we use.
// Load a font
font: new ig.Font( 'media/04b03.font.png' ),
Then, we define some scaffolding code for init(), update() and draw(). The draw method is the only one with executable code in it. We need to re-render the font on each draw call, so this simply gets the x,y position of where the text field should go so the engine knows where to draw the font graphic.
Before moving on, there is one more thing that is important to highlight. Scroll down to the last few lines in the main.js file:
// Start the Game with 60fps, a resolution of 320x240, scaled
// up by a factor of 2
ig.main( '#canvas', MyGame, 60, 320, 240, 2 );
});
This is the code that initializes your game. As you can see, we pass a reference to the Canvas, a name for our game instance, the frame rate and size into the ig.main constructor. The last value of two represents the scale of your game. This will upscale all of your game’s graphics by two. Upscaling is an important technique in order to get better performance out of games. This is especially important when running in software render mode in the browser, which is done by default. You can turn off upscaling but be weary of the possible performance issue you may run into down the line when using larger sprites.
To get started, delete the font code, but make sure you leave this.parent(). We can also now delete the font variable from the beginning of the class.
The first thing we want to do is load the level we created and change the .requires(...) block to load the level, like this:
.requires(
'impact.game',
'game.levels.test'
)
Unfortunately, if you refresh your game now, nothing is going to display. We will need to tell the game to load our level. Add the following code to your init function:
init: function() {
this.loadLevel( LevelTest );
},
Now, refresh the game and see that the test level has loaded (see Figure 16).
Impact includes an easy-to-use input class, which can be found in the ig.input namespace. In order to capture keyboard events, you'll bind the key press event to the desired key in the input class. Place the following code at the beginning of the main.js init() function just above the load level code:
// Bind keys
ig.input.bind( ig.KEY.LEFT_ARROW, 'left' );
ig.input.bind( ig.KEY.RIGHT_ARROW, 'right' );
ig.input.bind( ig.KEY.X, 'jump' );
ig.input.bind( ig.KEY.C, 'shoot' );
For our game, we are going to track the left and right arrows along with the X and C keys. This code is all you need to add in order to set up controls for your game. There is a list of constants, which contains all the keys Impact can use in the ig.KEY class. You simply apply a custom label to that key so when the script polls for input in its update loop, it returns the corresponding value when the keys are pressed.
The first entity you'll build for the sample game is the player. Entities are any objects that exist in the level that are not part of the map. Monsters, bullets, doors, and triggers are all considered entities. The player class extends the main entity.js class, which allows it to run correctly within the Impact game engine. Begin by setting up a simple entity class, like this:
lib/game/entities directory (see Figure 17).
ig.module(
'game.entities.player'
)
.requires(
'impact.entity'
)
.defines(function(){
EntityPlayer = ig.Entity.extend({
});
});
This is the basic structure for creating entities in Impact. We set up the module name and any required classes and define the class and that it extends, such as Entity.
Unfortunately, nothing is actually going to happen if you refresh your game. We still need to configure the player, add them to the level and import them into our main.js class. Before we do all that, let’s finish adding some properties to this class.
EntityPlayer code block:animSheet: new ig.AnimationSheet( 'media/player.png', 16, 16 ),
The line of code above instructs the player class to use the player.png file located in the media folder. It also specifies that its tiles are 16 x 16 pixels.
You'll also need to define values for the size and offset of the player.
size: {x: 8, y:14},
offset: {x: 4, y: 2},
flip: false,
The size represents the actual size of the player and the offset tells the animation engine where to render the player sprite inside the bounding box. I’ll cover how this works a little bit later when we talk about the debugger. We just want to make sure that the collision size is smaller than the actual animation frame so we simply move the collision box over just a few pixels.
While we are adding values to the player, let’s set up some physics such as velocity, friction and jump properties.
maxVel: {x: 100, y: 200},
friction: {x: 600, y: 0},
accelGround: 400,
accelAir: 200,
jump: 200,
Now we have defined the physics of how our player can move in the environment. Impact handles all of this for us; we simply need to override a few base properties to make it work correctly in our game. Once we get the player up and running in the game, you should feel free to mess around with these values to see how they affect your game play.
With all the player’s core values out of the way, we can look into setting up animation.
init: function( x, y, settings ) {
this.parent( x, y, settings );
this.addAnim( 'idle', 1, [0] );
this.addAnim( 'run', 0.07, [0,1,2,3,4,5] );
this.addAnim( 'jump', 1, [9] );
this.addAnim( 'fall', 0.4, [6,7] );
},
As you can see, we first need to pass up the parent’s init() methods. This is very important since entities need to know their starting x,y positions and any settings assigned to them when being created in the level. You can actually pass in additional values through the level editor, which becomes attached to the settings object during the construction of the entities.
Impact makes it very easy to set up animations. Simply use the entity class’s addAnim() method and pass an ID for the animation, along with the duration and an array for the frames from the sprite sheet.
Before we move on, let’s make sure your player class looks like this:
ig.module(
'game.entities.player'
)
.requires(
'impact.entity'
)
.defines(function(){
EntityPlayer = ig.Entity.extend({
animSheet: new ig.AnimationSheet( 'media/player.png', 16, 16 ),
size: {x: 8, y:14},
offset: {x: 4, y: 2},
flip: false,
maxVel: {x: 100, y: 200},
friction: {x: 600, y: 0},
accelGround: 400,
accelAir: 200,
jump: 200,
init: function( x, y, settings ) {
this.parent( x, y, settings );
// Add the animations
this.addAnim( 'idle', 1, [0] );
this.addAnim( 'run', 0.07, [0,1,2,3,4,5] );
this.addAnim( 'jump', 1, [9] );
this.addAnim( 'fall', 0.4, [6,7] );
}
});
});
At this point, we are ready to switch back over to Weltmeister and add our player. When you load the editor back up, you should see our test.js level. If it’s not there, simply re-load. When you load the level, the entities layer should automatically be highlighted. This layer works just like the other layers we created.
Now, before you can see the player displayed in the game, it's necessary to add the player class to the main.js code.
'game.entities.player'
Take care to add this line of code to the end of the list of required classes that you add, because if you place it in other locations, Impact will present an error.
update: function() {
// move left or right
var accel = this.standing ? this.accelGround : this.accelAir;
if( ig.input.state('left') ) {
this.accel.x = -accel;
this.flip = true;
}
else if( ig.input.state('right') ) {
this.accel.x = accel;
this.flip = false;
}
else {
this.accel.x = 0;
}
// jump
if( this.standing && ig.input.pressed('jump') ) {
this.vel.y = -this.jump;
}
// move!
this.parent();
}
Note: Make sure to add a comma after the init() method because each method must be separated by commas in the class file.
Now, you are ready to refresh out of the game and test out moving the player (see Figure 21).
As you can see from testing the game, we can move our player but he doesn’t animate or fall off the ledges. We are going to need to set the gravity of the game.
gravity: 300
Now, if you go back to your game, you will be able to jump and fall off ledges. When you test it out, though, we will not have a clean-looking fall animation. Let’s add in some additional code to keep track of the player’s velocity in order to show the correct animation such as jump, fall, idle and run.
// set the current animation, based on the player's speed
if( this.vel.y < 0 ) {
this.currentAnim = this.anims.jump;
}
else if( this.vel.y > 0 ) {
this.currentAnim = this.anims.fall;
}
else if( this.vel.x != 0 ) {
this.currentAnim = this.anims.run;
}
else {
this.currentAnim = this.anims.idle;
}
Now, we should be able to jump and run with animation, but there is one thing missing. We need a way to tell the player to flip his animation based on the direction he is running.
this.currentAnim.flip.x = this.flip;
Now we have a fully functional player. Let’s give it one more test and make sure everything works. At this point, our level is kind of boring. In the next part we are going to add a few monsters to the game.
ig.module(
'game.entities.spike'
)
.requires(
'impact.entity'
)
.defines(function(){
EntitySpike = ig.Entity.extend({
});
});
As you can see, we simply changed the entity name and class name but everything else is the same as the code we used to build the player class. Now we are ready to add our monster’s animation and set its size and maximum velocity.
animSheet: new ig.AnimationSheet( 'media/spike.png', 16, 9 ),
size: {x: 16, y: 9},
maxVel: {x: 100, y: 100},
flip: false,
Now we need to set up the animations just like we did for the player. This is a simple monster, so there are only a few sprites representing its animation.
init: function( x, y, settings ) {
this.parent( x, y, settings );
this.addAnim( 'crawl', 0.08, [0,1,2] );
}
Now that the monster's default animation is in place, we can start adding instances of the monster to the level to test it.
Feel free to add a few of them to the map on different platforms (see Figure 23).
Before we can test the game to see the monsters, we need to import the spike entity into the main.js file.
'game.entities.spike'
Save the file and refresh the game in your browser. The instances of the monsters are displayed in the game. Unfortunately, they don’t do much right now.
Let’s add in some logic to make them walk back and forth but be smart enough to not fall off their platforms.
update: function() {
// near an edge? return!
if( !ig.game.collisionMap.getTile(
this.pos.x + (this.flip ? +4 : this.size.x -4),
this.pos.y + this.size.y+1
)
) {
this.flip = !this.flip;
}
var xdir = this.flip ? -1 : 1;
this.vel.x = this.speed * xdir;
this.parent();
},
The function shown above checks if the monster hits anything in the CollisionMap. If it does, we toggle the this.flip value.
As you test the game, notice that the direction and velocity are updated before parent is called. We will also need to define the monster’s friction and speed.
friction: {x: 150, y: 0},
speed: 14,
You will see the monster instances moving around, and when they hit the edge of a platform, they flip and go the other way (see Figure 24).
handleMovementTrace: function( res ) {
this.parent( res );
// collision with a wall? return!
if( res.collision.x ) {
this.flip = !this.flip;
}
},
This helps make sure that if the monster runs into a wall they also turn around. Collisions with walls and the CollisionMap are handled through the handleMovementTrace function. Now we have covered all our bases and made sure our monster will not fall off a platform, but we still have one issue. There is no collision detection between the monster and the player (see Figure 25).
Before we get into adding more code to the monster, we need to talk a little bit collision detection in Impact JS.
Since the Impact Game Engine has built-in collision detection, we can focus on setting up collision relationships instead of having to create all the necessary code from scratch. Impact’s collision detection is based on bounding boxes, which look at an entity’s rectangle vs. pixel perfect collision. This is why we have had to tweak the size and offset values of our entities to help make our collision look as clean as possible. This kind of collision detection is super fast and covers a good portion of the use cases you will probably need. To learn more about Impact’s collision detection, visit the Impact documentation.
Let’s take a look at how to add collision detection to entities in the sample game.
As you can see here, we are setting up all three collision properties for the player. We assign the player to TYPE.A, which will represent our friendly group. Next, we set .checkAgainst to NONE since we don’t want the player to apply damage to the monsters he runs into.
Note: If you were building a Mario-type game, you would set this to group B and do a check if the player lands on top of the monster verses running into it from the side.
Finally, we set .collides to COLLIDES.PASSIVE since we will not be moving the player or colliding entity back when there is a collision with the player.
Now it’s time to set up the collision properties for the monster class.
type: ig.Entity.TYPE.B,
checkAgainst: ig.Entity.TYPE.A,
collides: ig.Entity.COLLIDES.PASSIVE,
So we are setting our monster to the enemy group, which is TYPE.B. Since the player belongs to group A, we will check against that group for collisions. And finally, we also set the enemy .collides property to passive. If we run the game now, you will see that the player and monster appear to be ignoring the collision properties we just set up. We need to add some more code in the monster class in order to handle the collision when it is detected. Add the following method to the spike.js class:
check: function( other ) {
other.receiveDamage( 10, this );
}
Remembering back to our introduction about the .checkAgainst property you will remember that I talked about a check method that gets called when a valid collision of the correct type is detected. Here, we are simply overriding the .check() function, which gets called when this collision is detected and now applies damage to the entity it collides with. In this case, the player will lose 10 points of health.
Now, if you re-run the game when the player hits a monster he should be immediately killed. Basically, the player just disappears since we don’t have any death animations.
Each entity in the game has a health property. By default, the health property is set to 0. This value is incredibly useful if you are leveraging the built-in receiveDamage() method to subtract an entity’s health. Use the following line of code to set the health value in the class:
health: 10,
Right now, if we applied this to our player, the collision with the enemy would happen so quickly, since the monster doesn’t push the player back in any way, that it would appear as if there was no difference from what we had before. As we get into creating weapons for our player, we can tweak this value a little more in the monster class and get a better sense of how it will work.
Right now, our player is totally defenseless in the game. As soon as he hits a monster he dies and there is no way to kill anything. Well, that is about to change, but before we build our first weapon we will have to talk about setting up a special type of class.
When it comes to things like weapons, we don’t need these to show up in the editor. So, just like in other languages where you would use an inner class to help contain scope and hide it from the rest of the application, we can do the same in Impact JS. In this case, we will use an inner class to hide the player’s weapons from the level editor.
Creating an inner class is very similar to making a regular class with the exception that you will be adding it to the end of another class’s code block. They support inheritance as well. If you look at the player.js file you will see the two closing blocks of code for that class at the end.
When viewed in Dreamweaver, these closing blocks are located on lines 69 and 71. On line 70, in between the two closing block tags, is where we will be putting our inner class.
Note: Using Impact, you can add as many inner classes as you need.
EntitySlimeGrenade = ig.Entity.extend({
});
So, now you should have your slime grenade entity just before the end of the player.js class like this (see Figure 28):
Now we are ready to customize our player’s weapon.
As you can see, the EntitySlimeGrenade entity is similar to the other entities that you've created. It extends ig.Entity and has all the same properties and methods as the player and the monster. The weapon also requires a graphic, so you'll add that first:
size: {x: 4, y: 4},
offset: {x: 2, y: 2},
animSheet: new ig.AnimationSheet( 'media/slime-grenade.png', 8, 8 ),
type: ig.Entity.TYPE.NONE,
checkAgainst: ig.Entity.TYPE.B,
collides: ig.Entity.COLLIDES.PASSIVE,
So, in order for our grenade to move and bounce, we will need to add a few additional physic properties.
maxVel: {x: 200, y: 200},
bounciness: 0.6,
bounceCounter: 0,
Here, we are setting our grenade’s maximum velocity. Also, we are going to keep track of how many times it will bounce before being destroyed as well as its bounciness. You can tweak these values later once we enable the player to actually fire the grenade so you can test what effect the bounciness value will have on it.
init: function( x, y, settings ) {
this.parent( x, y, settings );
this.vel.x = (settings.flip ? -this.maxVel.x : this.maxVel.x);
this.vel.y = -50;
this.addAnim( 'idle', 0.2, [0,1] );
},
Here, we are going to determine the beginning x velocity based on a flip parameter that will be passing in via the setting parameter. Each entity accepts an object for additional values outside of the standard x and y position. When the player fires the grenade, we will set the player’s own flip value into a property of the settings object so that we know what direction to fire the grenade. Next, we offset the y velocity by -50, which will help add an arc to the grenade when it gets fired off. After that, we just set the idle animation to display sprite 0 and 1.
We are getting very close to testing out our slime grenade, but before we override the handleMovementTrace() method and write some logic to handle collisions, track the number of bounces and remove the grenade from the display if it bounces too many times.
handleMovementTrace: function( res ) {
this.parent( res );
if( res.collision.x || res.collision.y ) {
// only bounce 3 times
this.bounceCounter++;
if( this.bounceCounter > 3 ) {
this.kill();
}
}
},
The handleMovementTrace() gets called while an entity is moving. This method is associated with the CollisionMap, so we can detect when an entity hits a wall. As you can see, we check the res object parameter if a collision happens on the x or y values. If we detect a collision, we will increment the .bounceCounter by 1. If the .bounceCounter is greater than three then we kill the grenade. We will talk more about the entity kill() method later.
Now that we can handle tracking bounces, let’s add logic when the grenade collides with an enemy. Like we did before in the slime.js class, we are going to override the check function, which gets called when a .checkAgainst group has been detected.
check: function( other ) {
other.receiveDamage( 10, this );
this.kill();
}
We now have everything we need to create a slime grenade; all we need to do now is add some code to our player class so that the player can fire the weapon.
Since inner classes are just like any other class we would create in Impact JS, we can simply use the ig.game built-in spawnEnity() method to create a new instance of the grenade when the player presses the fire key.
Up until now, both our player and monster are created during the level parsing process, we have not had to manually instantiate an entity yet. The spawnEnity() function helps ensure that when we create a new entity it gets added to Impact JS’s render list.
player.js class in Dreamweaver and paste the following code under the jump logic in the update method:// shoot
if( ig.input.pressed('shoot') ) {
ig.game.spawnEntity( EntitySlimeGrenade, this.pos.x, this.pos.y, {flip:this.flip} );
}
As you can see, the code above listens for the shoot event, which we bound to the C key in our main class. This should be very straightforward; we tell ig.game that we are going to spawn a new entity. The spawnEnity() method needs a reference to the class we want to create, its starting x and y position along with any additional settings we want to pass to the new entity. Notice here that we create a generic object with a property called .flip with the player’s flip value. This is what tells the grenade what direction it should be fired.
So, now you should be able fire your weapon and kill the monsters. Also, check out what happens when you go to a higher level and fire slime grenades. The grenades bounce off the walls and disappear after three bounces (see Figure 30).
At this point, the monsters will die after a single shot. If you change the monster’s life property to something higher, like 100, it will take 10 shots to kill the monster. This is because on every collision the slime grenade detects with an enemy it calls receivedDamage() and passes in 10 as the value. Likewise, you can make the grenade stronger by changing the amount of damage it applies.
Right now, our level is really boring. Impact JS was designed to create side scrolling games, so let’s go back into our map editor and extend out the level so the player has some room to run around. Create an opening in the wall and add another room to the map. Make sure that you also increase the size of the main layer and the collision layer to match (see Figure 31).
Once you have extended the level, save and try to play it. You may notice something isn’t quite right (see Figure 32).
Did you see that the game’s camera is not following the player? We will need to set this up manually in the main.js class.
update: function() {
// 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;
}
// Update all entities and BackgroundMaps
this.parent();
},
The code above takes advantage of a method of the game class called getEntitiesByType(). This is a very important API when it comes to finding instances of entities in your game. Because we know that there is only a single instance of our player we can explicitly look for them. There are better ways of getting a reference to the player, but for now, we will just use this technique to keep things simple.
After we see if the player exists, we can get the screen resolution and player position to center the screen’s x,y values. By setting the screen.x and screen.y values the renderer will automatically adjust the camera to that position. You can also do a lot of cool tricks with this like easing the camera movement or limiting it so it doesn’t scroll off-screen.
Save the files and refresh the game in the browser. Test the game again. After making the recent changes, the camera now follows the player's movements through the level (see Figure 33).
After you've finished developing a game with the Impact game engine, the last task involves publishing it. Up until this point, you've been running the game in a local host. If you uploaded the game files as-is, it should work online just fine.
The one thing to keep in mind is that you don’t want people to see your source code, and you don’t want to distribute Impact’s source accidentally. To package up our app, we use a technique called baking.
Baking will combine all your game files into a single file, which helps shorten the download time and compress the game. You can run the bake script by doing the following:
Open up a terminal window, navigate to the tools/ directory and write ./bake.sh
For Windows, double-click the bake.bat file in the tools/ directory. Make sure that the bake.bat can find the php.exe file on your system. You can either add the installation path of PHP to your PATH environment variable or edit the bake.bat to point directly to your php.exe.
If you get an error message, make sure all the paths are correct. You can open the bake.bat or bake.sh with a text editor. The only two lines you should ever need to change are these:
SET GAME=lib/game/main.js
SET OUTPUT_FILE=game.min.js
The GAME variable should point to your game's main.js file while the OUTPUT_FILE determines where the baked script file will be written.
If the script finishes without errors, you can find the game.min.js file in the Impact directory. You can now load this .js file in your .html instead of the two files you had previously. Open up your index.html file and change the following lines:
<script type="text/javascript" src="lib/impact/impact.js"></script>
<script type="text/javascript" src="lib/game/main.js"></script>
to this:
<script type="text/javascript" src="game.min.js"></script>
Now, you are ready to upload or distribute the game.min.js source file along with your media directory.
We have covered a lot of material to help you build your first game with Impact. Right now, you should have a solid foundation of how Impact JS works and are probably ready to make this simple demo into something more fun to play. I highly suggest going through the example games that come with Impact and take a look at other cool things you can do with the game engine. The is a great source of information if you are stuck or looking for inspiration. At this point it shouldn’t be very difficult to build upon this game and make something you can call your own.
Check out the following links to learn more about Impact and setting up the development environment: