In this first step of the tutorial we will learn what we need to compose the game display. If you’re acquainted with software like The Gimp or Photoshop you may want to think of your game screen as a series of supperposed layers like shown in the figure 1 below.
To generate the display in gameQuery you basically manipulate two kind of objects: sprites and groups. To render sprites gameQuery uses absolutley positioned element. Each of these has its own layer. By default the order you add your sprite to your screen defines which comes in front of which, i.e. you “stack” them.
So what if at some point in the game you want to add an new sprite “behind” an exsiting one? The easiest solution is to use groups, as shown on the figure 1 below. If you add a sprite to a previously defined group (groupA
for example) it will apear behind the elements in front of this group (sprite3
and sprite4
). Alternatively you can manipulate the “depth” of the sprite by using the “z-index” CSS property.
Groups are good for other things for instance, if you move a group around all of its content will move too, if you delete a group, its content will be deleted too. They can also be use as some kind of a mask i.e. if you specify overflow
to hidden
in your group options the sprite outside of its boundaries won’t be visible anymore. You can nest groups into a group. A correct usage of groups can, in some situations, lead to better performance by reducing the number of elements you need to access, to create a movement or to check for collisions.
You may wonder what the # in front of the names mean on figure 1? It’s there because groups and sprite are basically HTML elements. An HTML element is what a webpage is made of and to make easier to identify them, two sort of annotations exists: You can give a unique name to your element, it’s called an id
and you can give it a category, it’s called a class
. An element can have both a class and an id. Those are usually used when writing a CSS stylesheet. In CSS you give information about how to display the HTML elements of your page. In CSS a class name is preceded by a . and an id by a #. So in the figure 1 #groupA means the HTML element with id equals to groupA
this notation is called CSS Selector. I really encourage you to learn more about HTML and CSS but be prepared to lose you soul to this
Just to give you a first glimpse at what’s to come, the code to generate the situation presented in figure 1 would look like the code below.
$("#playground").playground({height: PLAYGROUND_HEIGHT, width: PLAYGROUND_WIDTH}) .addSprite("sprite1",{animation: animation1}) .addGroup("groupA") .addSprite("sprite2",{animation: animation2}).end() .addSprite("sprite3",{animation: animation3}) .addGroup("groupB",{overflow: hidden}) .addSprite("sprite4",{animation: animation4});
gameQuery is based upon and extends jQuery, so I will start with a very quick introduction to the ways of jQuery. It’s a gross over-simplification but it’s just what you need to get started here. For more information look at the documentation pages of jQuery.
jQuery has two parts: selection and modification. The selection is done by using CSS selector and it will find all the elements that correspond to the selector, on that particular page. The modification changes, adds or removes the elements. The way you write you code is by chaining those two kind of commands. For example if you want to remove all the elements with the class foo
from you page you could write:
$(".foo").remove();
Here $(".foo")
is a selection of all the elements with the foo
class and .remove()
is a modification that deletes them. We’ll delve deeper into the details when we will need them.
It could be a good idea to first do a sketch of what you think your game should look like as far as the sprites and groups are concerned. For this tutorial this may look like the figure 2 below. As you can see this game is so simple that we don’t need a really complex structure:
Let’s write the code to generate this game screen: First we need to tell gameQuery where to draw the game. This is done by the .playground()
method. Every method of gameQuery is accessed through jQuery. That’s the reason why the line below starts with $()
.
The playground()
method takes two arguments, the first is the CSS selector that describes the HTML element we want should hold the game. The second is a series of values that configures the gamescreen, here we’ve specified the height and width of the playground. It’s a good principle to always use variables for your constant value because that way it’s easier to change thier value later if you need to. It also makes the code more readable.
var PLAYGROUND_HEIGHT = 250; var PLAYGROUND_WIDTH = 700; $("#playground").playground({height: PLAYGROUND_HEIGHT, width: PLAYGROUND_WIDTH});
Now, we need to create the groups that will hold our sprites (as seen in figure 2). The background is the farther away from the viewer, so we must create it first. We can simply write:
$.playground().addGroup("background", {width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT});
Here we call the playground()
method again but without giving any parameter, this will return the previously defined HTML element. We then add a new group by calling addGroup()
with two parameters. The first one is the name we want to give to our group (it will also be the id
of the HTML element representing the group). The second argument is a series of values that configures the group (like before with the playground method), here the size of the group, the same as for the playground.
If the two last lines are called one after the other there is a convenient way to re-write it by using chaining:
$("#playground").playground({height: PLAYGROUND_HEIGHT, width: PLAYGROUND_WIDTH}) .addGroup("background", {width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT});
This looks a lot more like the code for figure 1. The use of carriage return and tabulation are here just to make the code easier to read, they don’t change the meaning of the code.
Then we can simply continue to add the other groups that we want:
$("#playground").playground({height: PLAYGROUND_HEIGHT, width: PLAYGROUND_WIDTH}) .addGroup("background", {width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT}).end() .addGroup("actors", {width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT}).end() .addGroup("playerMissileLayer",{width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT}).end() .addGroup("enemiesMissileLayer",{width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT});
Notice the .end()
method, it’s a way to go back to the previous selection. Some modificators change the current selection: for example addGroup()
return the group as a selection, so when you chain a command after this you will modify the newly created group. This allows you to easily add sprites to this group. Now if you want to nest a group into the previously created one you can simply call addGroup()
a second time but that’s not what we want here. What we want is to add another group to the playground, so we call end()
to get the playground back and continue our chaining. Now, we need to fill those groups with sprites!
To give a sense of depth to the background we will use multiple layers of sprites moving at different speeds. This is called the parallax effect. This effect is based on the fact that farther is the object from the viewer , lesser is the screen distance it covers in a given time than a nearer object travelling at the same speed.
We want the background to move non-stop, to make this effect we will use two sprites for each layer of the background. When only one layer is shown on the screen we can move the other one back to the end of the first and so on as shown in figure 3 below.
The images that constitute a sprite are always an animation, even when the sprite is not animated! You just need one image to make it an animation! I’ve chosen to use six sprites here (two per layer) to make the scrolling less repetitive. Then we need to create the six sprites by adding them to the "#background"
group. Notice that you define the size of the sprite when creating a sprite not when creating an animation!
var background1 = new $.gameQuery.Animation({imageURL: "background1.png"}); var PLAYGROUND_WIDTH = 700; var PLAYGROUND_HEIGHT = 250; var background2 = new $.gameQuery.Animation({imageURL: "background2.png"}); var background3 = new $.gameQuery.Animation({imageURL: "background3.png"}); var background4 = new $.gameQuery.Animation({imageURL: "background4.png"}); var background5 = new $.gameQuery.Animation({imageURL: "background5.png"}); var background6 = new $.gameQuery.Animation({imageURL: "background6.png"}); $("#background"). .addSprite("background1", {animation: background1, width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT}) .addSprite("background2", {animation: background2, width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT, posx: PLAYGROUND_WIDTH}) .addSprite("background3", {animation: background3, width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT}) .addSprite("background4", {animation: background4, width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT, posx: PLAYGROUND_WIDTH}) .addSprite("background5", {animation: background5, width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT}) .addSprite("background6", {animation: background6, width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT, posx: PLAYGROUND_WIDTH});
Now we need to make these sprites move. For every actions that you may want to execute at an interval you can use the registerCallback()
method. By doing this you sechdule a function to be called every nth miliseconds. We will do this to move the sprite we’ve just created like shown in figure 3. To make a sprite move you don’t need to use any gameQuery specific functions, jQuery gives you all you need for this out of the box! The easiest way is to manipulate the CSS properties of the sprite, by changing the top
property to move the sprite vertically and by changing the left
property you move it horizontally.
For each background sprite we need to create a movement from a hidden point at the right of the screen to a hidden point at the left of the screen. It’s a horizontal movement so we will change the value of left
from PLAYGROUND_WIDTH
to -PLAYGROUND_WIDTH
. We don’t need to change the top
property. Whenever you need to obtain values from an interval and loop, a useful way is to use the modulo operation: %
. This operation gives you the rest of the integer division: for example if you make 15 % 6
you will have 2 has the result of the integer division (15 / 6 = 2
) and as 2 * 6 = 12
you will still have 15 - 12 = 3
as a remainder, so 15 % 6 = 3
. Not to complicated. Now lets say that you want to go through numbers 0 to 20 with an increment of 1. So, you could write something like this:
while(condition){ myValue++; //equivalent to myValue = myValue + 1; if(myValue >= 20){ myValue = 0; } }
For this you need a condition check and you assign a value to the myValue
variable twice. It’s way simpler to just use the modulo operator:
while(condition){ myValue = (myValue + 1) % 20; }
We will need to get the actual horizontal position of the sprite and modify it. To access the css property from an element we use the jQuery method css()
, the first time with only one parameter to get the current value and the second time with two to set the new value. Now you should have all the information you need to understand the code below.
$.playground().registerCallback(function(){ //Offset all the pane: var newPos = (parseInt($("#background1").css("left")) - smallStarSpeed - PLAYGROUND_WIDTH) % (-2 * PLAYGROUND_WIDTH) + PLAYGROUND_WIDTH; $("#background1").css("left", newPos); newPos = (parseInt($("#background2").css("left")) - smallStarSpeed - PLAYGROUND_WIDTH) % (-2 * PLAYGROUND_WIDTH) + PLAYGROUND_WIDTH; $("#background2").css("left", newPos); newPos = (parseInt($("#background3").css("left")) - mediumStarSpeed - PLAYGROUND_WIDTH) % (-2 * PLAYGROUND_WIDTH) + PLAYGROUND_WIDTH; $("#background3").css("left", newPos); newPos = (parseInt($("#background4").css("left")) - mediumStarSpeed - PLAYGROUND_WIDTH) % (-2 * PLAYGROUND_WIDTH) + PLAYGROUND_WIDTH; $("#background4").css("left", newPos); newPos = (parseInt($("#background5").css("left")) - bigStarSpeed - PLAYGROUND_WIDTH) % (-2 * PLAYGROUND_WIDTH) + PLAYGROUND_WIDTH; $("#background5").css("left", newPos); newPos = (parseInt($("#background6").css("left")) - bigStarSpeed - PLAYGROUND_WIDTH) % (-2 * PLAYGROUND_WIDTH) + PLAYGROUND_WIDTH; $("#background6").css("left", newPos); }, REFRESH_RATE);
This is the result that we get with those six sprites, pretty neat isn’t it ?
For the background we didn’t use any proper animation, just a static images. For the player spaceship we will use many true animations. The idea is to give a visual feedback to the user of what the spaceship is doing right now: going up, down, left or right. To do this we will use a static image representing the spaceship and superimpose animations for the boosters. In order to avoid moving each of those sprite independently when moving the spaceship, we will add all those into a group.
In gameQuery the many frame of an animation are all contained in a single image a little bit like in a film reel. You can either put the frame side-by-side (horizontal animation) or one below another (vertical animation). If the sprite has the same size as one frame of the animation you can chose both but when the sprite is bigger than the animation in one direction then you would want to make the animation frame stack in the other direction. When you create a multi-frame animation in gameQuery you need to provide the number of frame, the distance in pixels between frames (delta
) the time laps between two frame in miliseconds (rate
) and the type of the animation (if it’s a vertical or horizontal animation for example).
// Player spaceship animations: playerAnimation["idle"] = new $.gameQuery.Animation({imageURL: "player_spaceship.png"}); playerAnimation["explode"] = new $.gameQuery.Animation({imageURL: "explode.png"}); playerAnimation["up"] = new $.gameQuery.Animation({imageURL: "boosterup.png", numberOfFrame: 6, delta: 14, rate: 60, type: $.gameQuery.ANIMATION_HORIZONTAL}); playerAnimation["down"] = new $.gameQuery.Animation({imageURL: "boosterdown.png", numberOfFrame: 6, delta: 14, rate: 60, type: $.gameQuery.ANIMATION_HORIZONTAL}); playerAnimation["boost"] = new $.gameQuery.Animation({imageURL: "booster1.png" , numberOfFrame: 6, delta: 14, rate: 60, type: $.gameQuery.ANIMATION_VERTICAL}); playerAnimation["booster"] = new $.gameQuery.Animation({imageURL: "booster2.png"}); // Initialize the background $.playground().addGroup("actors", {width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT}) .addGroup("player", {posx: PLAYGROUND_WIDTH/2, posy: PLAYGROUND_HEIGHT/2, width: 100, height: 26}) .addSprite("playerBoostUp", {posx:37, posy: 15, width: 14, height: 18}) .addSprite("playerBody",{animation: playerAnimation["idle"], posx: 0, posy: 0, width: 100, height: 26}) .addSprite("playerBooster", {animation:playerAnimation["boost"], posx:-32, posy: 5, width: 36, height: 14}) .addSprite("playerBoostDown", {posx:37, posy: -7, width: 14, height: 18});
There will be a sprite for the back booster that will have three state: idle (medium power), off (the spaceship is going back) and full throttle (the spaceship is going forward). Two other sprites are needed for the small up and down booster each with a on and off animation. The switch between the animations should be triggered by the user keyboard inputs. jQuery offers a very convienent way to respond to the user action by using events listener. You can choose a function to be executed when some kind of event happens, for example a mouse click or a key press. Notice how we create some sprite without an animation, it’s because they are juste placeholder for the animation when we will need them.
Now let’s take care of the binding between the keyboard and the animations. Here we find a small limitation of running a javascript game in a browser: the up/down/left/right key are usually used for scrolling the page. So if your game is on a page that extend beyond the limits of the browser windows pressing those key will result in a scrolling of the page too. They may be some way to disable this programaticaly but I think it’s not a great idea to break the browser behavior so we will use other keys to control the game instead. The best pratice would be to let the user configure himself what keys he want to bind to which actions, this is not really hard but I’d like to keep this tutorial as simple as possibles.
//this is where the keybinding occurs $(document).keydown(function(e){ switch(e.keyCode){ case 65: //this is left! (a) $("#playerBooster").setAnimation(); break; case 87: //this is up! (w) $("#playerBoostUp").setAnimation(playerAnimation["up"]); break; case 68: //this is right (d) $("#playerBooster").setAnimation(playerAnimation["booster"]); break; case 83: //this is down! (s) $("#playerBoostDown").setAnimation(playerAnimation["down"]); break; } }); //this is where the keybinding occurs $(document).keyup(function(e){ switch(e.keyCode){ case 65: //this is left! (a) $("#playerBooster").setAnimation(playerAnimation["boost"]); break; case 87: //this is up! (w) $("#playerBoostUp").setAnimation(); break; case 68: //this is right (d) $("#playerBooster").setAnimation(playerAnimation["boost"]); break; case 83: //this is down! (s) $("#playerBoostDown").setAnimation(); break; } });
The animation change occures when a key is pressed or released. Sometime we want to simply remove the animation without replacing it with another one, to do so you can simply use the setAnimation
method without any argument. To find out which keyCode corespond to the key you want to detect you can use thispage. The explosion of the spaceship will be taken care of int the step 3. The result is shown below, remember that at this stage we’ve only took care of the animation, so it’s normal that the ship isn’t moving!
We will use only two animations per enemy, normal movement and explosion. The movement and generation of the enemies will be treaded in the next steps. The only new thing here is the use of the ANIMATION_CALLBACK
type. A callback is a function to call later on. In this situation at then end of the current animation. We use this for the explosion: when the animation has been played once we want to remove the sprite (we will see how this is done on step 3). When you need to give two or more type to an animation you can use the |
operator between the types. For example you can have type: ANIMATION_CALLBACK | ANIMATION_ONCE | ANIMATION_VERTICAL
to have a vertical animation play once and call a function after that! This could be usefull when you want to make a transition animation between two looped animation. In this situation you would change the animation to the second looped one in the callback (not used in this tutorial).
var enemies = new Array(3); // There are three kind of enemies in the game //... /// List of enemies animations : // 1st kind of enemy: enemies[0] = new Array(); // enemies have two animations enemies[0]["idle"] = new $.gameQuery.Animation({imageURL: "minion_idle.png", numberOfFrame: 5, delta: 52, rate: 60, type: $.gameQuery.ANIMATION_VERTICAL}); enemies[0]["explode"] = new $.gameQuery.Animation({imageURL: "minion_explode.png", numberOfFrame: 11, delta: 52, rate: 30, type: $.gameQuery.ANIMATION_VERTICAL | $.gameQuery.ANIMATION_CALLBACK}); // 2nd kind of enemy: enemies[1] = new Array(); enemies[1]["idle"] = new $.gameQuery.Animation({imageURL: "brainy_idle.png", numberOfFrame: 8, delta: 42, rate: 60, type:$.gameQuery.ANIMATION_VERTICAL}); enemies[1]["explode"] = new $.gameQuery.Animation({imageURL: "brainy_explode.png", numberOfFrame: 8, delta: 42, rate: 60, type: $.gameQuery.ANIMATION_VERTICAL | $.gameQuery.ANIMATION_CALLBACK}); // 3rd kind of enemy: enemies[2] = new Array(); enemies[2]["idle"] = new $.gameQuery.Animation({imageURL: "bossy_idle.png", numberOfFrame: 5, delta: 100, rate: 60, type: $.gameQuery.ANIMATION_VERTICAL}); enemies[2]["explode"] = new $.gameQuery.Animation({imageURL: "bossy_explode.png", numberOfFrame: 9, delta: 100, rate: 60, type: $.gameQuery.ANIMATION_VERTICAL | $.gameQuery.ANIMATION_CALLBACK}); // Weapon missile: missile["player"] = new $.gameQuery.Animation({imageURL: "player_missile.png", numberOfFrame: 6, delta: 10, rate: 90, type: $.gameQuery.ANIMATION_VERTICAL}); missile["enemies"] = new $.gameQuery.Animation({imageURL: "enemy_missile.png", numberOfFrame: 6, delta: 15, rate: 90, type: $.gameQuery.ANIMATION_VERTICAL}); missile["playerexplode"] = new $.gameQuery.Animation({imageURL: "player_missile_explode.png", numberOfFrame: 8, delta: 23, rate: 90, type: $.gameQuery.ANIMATION_VERTICAL | $.gameQuery.ANIMATION_CALLBACK}); missile["enemiesexplode"] = new $.gameQuery.Animation({imageURL: "enemy_missile_explode.png", numberOfFrame: 6, delta: 15, rate: 90, type: $.gameQuery.ANIMATION_VERTICAL | $.gameQuery.ANIMATION_CALLBACK});
And that’s it for the animations! In the next step we will take a look at the object model we will use in this game. So, more low level javascript, less gameQuery.