Game Query

download Game Query

of 22

Transcript of Game Query

Tutorial 1 - Introduction y Introduction y First words y Tools y Game description y Final Result y Step 1 - Sprites and Animations y Step 2 - Object Model y Step 3 - Game Logic and Control y Step 4 - Warp up First words In this tutorial I will try to guide you through the making of a simple javascript game from start to finish. I've chosen for this first tutorial a very basic side scrolling shooter. To focus on the most basic tasks of game development I've made this game to contains only a small sub-set of what you would find in a full-featured game. The final result that you can find bellow is not really that much fun to play but again, the aim is to learn how to use jQuery and gameQuery to make a javascript game, nothing else! In the current revision (0.2.6) gameQuery is best suited to make simple 2d sprite based games, so that's what we're gonna use it for. The way you make those sprite is entierly up to you, you can, for exemple, draw them by hand, scan them and then refine them in your favorite graphic editor. You can draw them directly on you computer like I did for the first demo. On the bottom of every page there is a comment section. Don't hesitate to post anything here but keep in mind that it's shared amongst all the tutorial so if you're talking about a specific page say which one in you comment! Prerequisit I'd try my best to make this tutorial understandable by anyone, even those without prior knowledge in programming or web design. Some pointer will be given to allow you to learn more about things when they are introduce if you feel the need for it. The result will be a little boring for the experimented programmers so feel free to juste take a look a the final source code instead, it should be pretty self explainatory! The codes snipets have been enhanced to provide a contextual link to the gameQuery API documentation. When you click one of the keyword a small frame will appears with the corresponding documentation. To make this frame disappear just click on it. This allows to make clear what in the codes belongs to gameQuery and what doesn't. Warning English is not my mother thongue so be indulgent for the many spelling errors or typos and don't hesitate to send a mail to point them out to me! I tried my best to make things understandable for a beginer and in an excess of oversimplification I may have said something you think is not exact, don't hesitate to say so in the comment to let the other readers know about it! Tools This is a series of tools that can come-in handy. I will list free, open-source, software here but some great proprietary software can be found too. Graphics: y The Gimp - A great graphic editor. I'm not a fan of the multi windows interface but it's definitively a must have. Even when you use 3d modeling for your sprite it's usefull to give some polish to your rendered work. y Blender - An incredible 3D modeler and renderer, you can even use it to make 3d games. y Inkscape - If you need some vector art this is a great software.(I didn't use it for the game but for the illustration in this tutorial) Development: y Firefox - Well obviously you will need a web-browser at some time ! I use Firefox because I Firebug is such a great tool. Safari, Chrome and Opera all have this kind of tool but I haven't try them yet. If you want to use IE and need some help debugging your code take a look at this. y Firebug - This tool will change you life, really! :) DOM explorer, CSS, break-points, network stats, what could you want more. y A good code editor - There are so many of them, juste choose one. But you will realy benefit from using a specialized text editor instead of a general purpose one like windows notepad! Game Description

The player controls a spaceship facing right and he must avoid or destroy enemies comming from the right side of the sceen thoward him. There is three kind of enemies, each one of them with different kind of movement and weapon. Whenever the player's spaceship is touched by an enemy or one of its missile it looses a unit of it's shield. If the spaceship has no more shield when it's hit it will explode and the player will loose a life. When the spaceship explode it will reapear if the player has still some "life" left, if he doesn't the game is over. After such a resurection the spaceship will be granted a temporary imunity for 3 seconds. The first kind of enemy is the most frequent, we will call them "minion". They move in a straight line. They will not fire any missile. The second kind of enemy are a little bit more resistent and move in a less predictable way, we will call them "brainy". They will fire a missiles in at a random rate. The last kind of enemy are some sore of "end level boss". They are bigger and stronger but also far less frequent than the two other kind. They don't move much but fire missiles. When one of these appears no more enemies will appear until you've destroyed it. This game will have no end, no score and will be totaly random, remember that it's juste a tutorial! It should be very easy to add those features if you want to and it will make a good exercie :) . Final Result This is what we will obtain at the end of the tutorial. Use a,d,w,s to move the spaceship around and k to fire. Game screen organization 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 positionedelement. Each of these has it's 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 thing: if you move a group around all of it's content will move too, if you delete a group its content will be deleted too. They can also be use as some kind of mask: if you specify overflow to hiddenin your group options the sprite outside of its boundaries won't be visible

anymore. You can nest groups into group. A correct usage of groups can in some situation leads 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 annotation 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 realy encourage you to learn more about HTML and CSS but be prepared to lost you soul into 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}); A very, very, very short introduction to jQuery 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 part: selection and modification. The selection is done by using CSS selector and will find in the page all the elements that correspond to the selector. The modification changes, adds or removes the elements. The way you write you code is by chaining those two kind of command. For example if you want to remove all the element 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 delete them. We'll get more into the detail when we will need them. Let's the fun begin! It could be a good idea to first do a sketch of what you think your game should look like as far as sprite 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() methode take two arguments, the first is the CSS selector that describe the HTML element we want to hold the game. The second is a serie of values that configures the gamescreen, here we've specified the height and width of the playgroud. It's a good principle to allways use variable for your constant value because that way it's easier to change thier value later if you need to. It also make 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 serie 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 line 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 group with sprites! Background To give a sens of depth to the background we will use many layers of sprites moving at differant speed. This is called a parallax effect. This effect is base on the fact that object further away from the viewer cover less screen distance 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 image that constitute a sprite is allways an animation, even when the sprite is not animated! You just need an animation with only one image. I've choosen 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 want to create a movement from hidden at the right of the screen to hidden 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 obtaine values from an interval and loop amonst it a usefull way is to use the modulo operation: %. This opperation give 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 rest, so 15 % 6 = 3. Not to compicated. Now let say that you want to go through numbers from 0 to 20 by an increment of 1 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 give two time a value to the myValue variable. It's way simpler to use the modulo: 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 methode 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 bellow. $.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 sprite, pretty neat isn't it ? Player 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 independenty 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! Enemies and missiles 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.

Objects in Javascript Javascript is not a classic object oriented programming language, if you are used to one, you may want to take extra care to keep in mind it's small pecularities. If you are new to OO programming this shoulnd't bother you that much. We won't go too deep into the detail so if you feel the need, there are some great tutorialsand documents on the net about this. Object Oriented programming is a way to regroup your code around the logical entity it relates to and to manage what you do and what you don't want to show to the user of you code (encapsulation). The description of what the features of an entity are (attributes) and how this entity reacts to the outher world (methods) is called a class. The actual value of a unique entity is called an object. An object is an instanciation of a class. When you instanciate a class you make a call to what's called a constuctor. To create an instance of a class you use the new keyword, just like you did for animations in step 1. var myObject = new myClass(some, important, arguments); In javascript an object is an instance of a function, yes you read right, a function! so when you define a class it look exactly like when you write a function. In fact, when you write a class in javascript you sorte of wite it's constructor. A very simple class would look like this: var myClass() = function(var some, var important, var arguments){ this.someVisilbeAttribute = some + arguments; someLessVisiblAttribute = important + arguments; this.tickleMe = function(var name){ alert("hello "+name+" you should now that "+this.someVisibleAttribute); }; return true; } This code defines a class called myClass with two attributes named someVisibleAttribute and someLessVisibleAttribute and a methode namedtickleMe. At the same time in this code we defined the way the constructor works, defining the values of the object attributes with a combination of the constructor argument. As you can see in this example there are many way to delcare attributes in the class. When you use the var keyword you create a local variable in the object this means that any code that you executes in the object (like methodes) can access the attribute by it's name. By using the this keyword you make the attribute visble from the outside of the object too. This doesn't mean that there isn't any way to access to the attributes defined with the var keyword from the outside, just that it's harder and less straight forward. The code below show you how to access to the visible attribute from outside of the object: myObject.someVisibleProperty = "a new value"; alert(myObject.someVisibleProperty) Furthermore it may be usefull to know that the variable that you passes to the constructor remains accessible for the code executed in the object like if they have been declared with var. You can change the object after it has been instanciated to add some new attributes or method. When you do this you have to choose if you want to change every object of the same class or just this object. The following example shows the latter myObject.lastMinuteMethode = function(){/*Do something here*/}; myObject.aNewProperty = "aValue"; Sometime you may want to change not just one object but all of it's "sibling" too. To do this you can use the object's prototype. The prototype is like the class definition of the object and can be accessed throught the .prototype attribute. Any instance of the same class share the same prototype so when you change something in one object you change it for all instance. var firstObject = new myClass(1,2,3); var secondObject = new myClass(4,5,6); firstObject.prototype.aNewMethode = function(){alert("I'm new");} secondObject.aNewMethode(); Inheritance in javascript When a class inherit from another it receives all the methode and attributes from the its parent. Then it can add or rewrite some methode to differentiate itself. Inheritance as many forms and limitaions depending on the language you use. For javascript inheritance is not realy part of the language so many hacks exist to make it happends. Which hack you choose to use depend of which parts of inheritance features you want.

For this tutorial we use a very simple and limited form of javascript inheritance. But I think anything more would be an overkill. This methode consist in injecting an instance of one class into another. Then you can extend the class by modifying the prototype without impacting the parent. function inheritingClass(){}; inheritingClass.prototype = new myClass(); inheritingClass.prototype.soneOnlyMethode = function(){/*Do something here*/}; inheritingClass.prototype.tickleMe = function(){/*Change the behavior of the parent methode*/}; For what we want to do here that's all you need to know BUT if you consider writing a more complex programme, or in another OO language I recomend that you look into this more in detail! Player Object The player is a simple class that will have only one instance (since it's a single player game) so using a OO notation here is only usefull to make the code easier to read. In the player object we will need attributes to describe the player's spaceship state, for example does it has some sheild left or some life left? We will also have some methode to help us do some basic action on the ship. Since in this game we don't use a physic engine to manage the player movement you just need to increment / decrement the position, I choosed not to have any methodes to manage the space ship movements. The first methode, damage() is a way to deal with damage. When a projectile hits the space ship we will call this methode and depending on the returned value we will know if the ship has died or not. The methode hides the management of the sheild from the outside. If you're wondering about the variable- notation, it means variable = variable - 1 and works for other opertators too. function Player(node){ this.node = node; //this.animations = animations; this.grace = false; this.replay = 3; this.shield = 3; this.respawnTime = -1; // This function damage the ship and return true if this cause the ship to die this.damage = function(){ if(!this.grace){ this.shield--; if (this.shield == 0){ return true; } return false; } return false; }; //... The second methode, respawn() takes care of what happens when the space ships dies (the previous methodes returned true) and the player has some life left. In this situation the shield must be replenished and the player is reapearing in a grace mode where enemies can not hit him. The grace periode last only for 3 seconds so we need to mesure the time when it as been activated in order to desactivate it after the right time, that's what the this.respawnTime = (new Date()).getTime(); line does. To give the player a feedback that he is in the grace periode we make the spaceship slightly transparent with jQuery's fateTo() methode. This methode take as first argument the time it should take to reach that opacity (here imediatly so 0ms) and the second argument is the opacity we want to optaine (where 1 means not transaprent at all ant 0 totaly invisible). // this try to respawn the ship after a death and return true if the game is over this.respawn = function(){ this.replay--;

if(this.replay==0){ return true; } this.grace = true; this.shield = 3; this.respawnTime = (new Date()).getTime(); $(this.node).fadeTo(0, 0.5); return false; }; //... The last part of the class take care of checking if the grace is over. This methode should be called at every timestep. The return true is the only way to end porperly an object in javascript. //... this.update = function(){ if((this.respawnTime > 0) && (((new Date()).getTime()this.respawnTime) > 3000)){ this.grace = false; $(this.node).fadeTo(0, 1); this.respawnTime = -1; } } return true; } Enemies Objects We have three kind of enemy and they will all have common ancester. We will write in this class all the default behaviour and specialize them for each inheriting class the method we want. Since we want latter to call each methode without having to think which class the object is an instance of, we will only overwrite existing method in the childe classes. Like the player the enemies need a method to manage the damage they can take. You will notice that's it exactly the same method that the one from Player except for the grace check. function Enemy(node){ this.shield = 2; this.speedx = -5; this.speedy = 0; this.node = $(node); // deals with damage endured by an enemy this.damage = function(){ this.shield--; if(this.shield == 0){ return true; } return false; }; //... The enemies have an automated movement control with some very basic logic. We will then need some method to make the enemies move. Here we hve done a main update method that call a method for the movenment on each axis. This allows a sub-class to overwirte only one of them easily. You can see that the default behaviour is to move at a constant speed along each axis. If you look at the updateX and updateY method you will recognize the same kind of statement we use to make the background move. // updates the position of the enemy this.update = function(playerNode){ this.updateX(playerNode); this.updateY(playerNode); }; this.updateX = function(playerNode){ var newpos = parseInt(this.node.css("left"))+this.speedx; this.node.css("left",""+newpos+"px"); }; this.updateY= function(playerNode){ var newpos = parseInt(this.node.css("top"))+this.speedy; this.node.css("top",""+newpos+"px"); }; }

The family tree of the enemies is shown in the figure bellow. When you use inheritance the aime is to reduce code redundancy. So when two object have a behaviour in common it make sens to make on inherite from the other.

Minion The minion is not very different from the default enemy, it only tries to stay on the screen but that's all. So the code is pretty straight forward: function Minion(node){ this.node = $(node); } Minion.prototype = new Enemy(); Minion.prototype.updateY = function(playerNode){ var pos = parseInt(this.node.css("top")); if(pos > (PLAYGROUND_HEIGHT - 50)){ this.node.css("top",""+(pos - 2)+"px"); } } Brainy For the Brainies we want more shield, to do this we simply redefine the value in the constructor. Values defined in the sub class overwrite values from the super class and values given into the constructor overwrite them both. This category of enemies are a little bit smarter, they will try to aligne themself with the player. To allow this we overwirte the updateY function so that the vertical speed is added or substracted depending of the relative position of the player. Nothing new here. Notice that the Brainy class doesn't inherite from the Minion but from the Enemy class. function Brainy(node){ this.node = $(node); this.shield = 5; this.speedy = 1; this.alignmentOffset = 5; } Brainy.prototype = new Enemy(); Brainy.prototype.updateY = function(playerNode){ if((this.node[0].gameQuery.posy+this.alignmentOffset) > $(playerNode)[0].gameQuery.posy){ var newpos = parseInt(this.node.css("top"))-this.speedy; this.node.css("top",""+newpos+"px"); } else if((this.node[0].gameQuery.posy+this.alignmentOffset) < $(playerNode)[0].gameQuery.posy){ var newpos = parseInt(this.node.css("top"))+this.speedy; this.node.css("top",""+newpos+"px"); } } Bossy The Bosses will have the same kind of behaviour as the brainy, they will aligne with the player. But we change something: they will stop to move toward the player at some point on the screen to let time to the player to kill them (since they have much more shield). function Bossy(node){ this.node = $(node); this.shield = 20;

this.speedx = -1; this.alignmentOffset = 35; } Bossy.prototype = new Brainy(); Bossy.prototype.updateX = function(){ var pos = parseInt(this.node.css("left")); if(pos > (PLAYGROUND_WIDTH - 200)){ this.node.css("left",""+(pos+this.speedx)+"px"); } } The enemies generation, destruction and missile fireing are taken care of outside of the objects, look at the next step to see how! Game State Every game a little more than trivial need some sorte of game state to determine when which actions are allowed or supposed to occure. In this game we have a very simple game state but if you want an example of a more complex one you can look a the code of the first demo. We need three information: y First is the game over? We don't want to continue to do too much work once the game is over. y Second is there a Bossy enemy on the screen? When those kind of enemies are present on the screen we want to stop producing new enemies to let the player kill this one. y Third is the spaceship exploding? When the player shield ran out and the ship is exploding we want it to fall to the bottom of the screen. During this time the player should not be able to control his spaceship. There is another hidden game state that we've seen before, it's the grace variable in the player object. To store thode informations we will simply use three boolean variables. To store these information we will use three boolean variable (so four counting the hidden one). We could have it reduce to two since only the boss mode can occures at the same time as some of the other ones but it may have renderd the code slightly less readable (yeah I know, we could have used only one variable, but then we'll have some pretty inintuitive states). Enemies generation To generate the enemies we will register a callback. It will only be called every second and we will randomly decide to create a enemy or not. To make this random decision we will use the Math.random() method that generate a pseudo-random number evenly distributed between 0 and 1 (javascript contains some predefined functions worth looking at). We will need a name to give to the newly created sprite, to obtain a more or less unique one we use two javascript functions . TheMath.random() function and the Math.ceil() function, this function returns an integer rounded up from the float you give to it. Combine like above we will obtaine a random number between 0 and 1000. It doesn't mean we will never have two sprite with the same id at the same time but it should not occure to often Once we created an enemy with the addSprite method that you should start getting used to, we need to associate it with an instance of the correct enemy classes we defiend earlier. It's a good practice not to keep a separate collection of objects to model the game because it makes the housekeeping way trickier. Off course you need to select your node each time you need to access to the object but it's a small price to pay for the peace of mind and ease to debug that it will bring to you! To store something into the node from the jQuery selection you must use the [0] notation, if you don't you won't be able to access it the next time you select it. Keep it mind that for it to make sense you need to have selected only one object! //This function manage the creation of the enemies $.playground().registerCallback(function(){ if(!bossMode && !gameOver){ if(Math.random() < 0.4){ var name = "enemy1_"+Math.ceil(Math.random()*1000); $("#actors").addSprite(name, {animation: enemies[0]["idle"], posx: PLAYGROUND_WIDTH, posy: Math.random()*PLAYGROUND_HEIGHT, width: 150, height: 52}); $("#"+name).addClass("enemy");

$("#"+name)[0].enemy = new Minion($("#"+name)); } else if (Math.random() > 0.5){ var name = "enemy1_"+Math.ceil(Math.random()*1000); $("#actors").addSprite(name, {animation: enemies[1]["idle"], posx: PLAYGROUND_WIDTH, posy: Math.random()*PLAYGROUND_HEIGHT, width: 100, height: 42}); $("#"+name).addClass("enemy"); $("#"+name)[0].enemy = new Brainy($("#"+name)); } else if(Math.random() > 0.8){ bossMode = true; bossName = "enemy1_"+Math.ceil(Math.random()*1000); $("#actors").addSprite(bossName, {animation: enemies[2]["idle"], posx: PLAYGROUND_WIDTH, posy: Math.random()*PLAYGROUND_HEIGHT, width: 100, height: 100}); $("#"+bossName).addClass("enemy"); $("#"+bossName)[0].enemy = new Bossy($("#"+bossName)); } } else { if($("#"+bossName).length == 0){ bossMode = false; } } }, 1000); //once per seconds is enough for this We make the enemies appear outside of the visible part of the playground, on the right. The addClass() method is from jQuery, it add the given argument as a class of the sprite. We will need this to make the updating of the position simplier and the collision detection faster. When we generate a Bossy kind of enemy we want to set the proper game state and in order to detect when this enemy is destroyed we need to store it's name too. Notice how the enemy generation occurs only when the boss is not present and how we test for the presence of the bosse to reste the state of the game to a normale one. Enemies movement Most of the work to make the enemies move has already been done, we just need to call the update() method on the object embeded on the sprites nodes. To do this we register a callback that will be called every 30ms. In this callback we select with jQuery all the sprites representing an enemy. Then by using theeach() function we can execute a function for each one of them. Ofcourse we only do this as long as the game is not over. $.playground().registerCallback(function(){ if(!gameOver){ //Update the movement of the enemies $(".enemy").each(function(){ this.enemy.update($("#player")); var posx = parseInt($(this).css("left")); if((posx + 150) < 0){ $(this).remove(); return; } //Test for collisions var collided = $(this).collision("#playerBody,.group"); if(collided.length > 0){ if(this.enemy instanceof Bossy){ $(this).setAnimation(enemies[2]["explode"], function(node){$(node).remove();}); $(this).css("width", 150); } else if(this.enemy instanceof Brainy) {

$(this).setAnimation(enemies[1]["explode"], function(node){$(node).remove();}); $(this).css("width", 150); } else { $(this).setAnimation(enemies[0]["explode"], function(node){$(node).remove();}); $(this).css("width", 100); } $(this).removeClass("enemy"); //The player has been hit! if($("#player")[0].player.damage()){ explodePlayer($("#player")); }}});} }, REFRESH_RATE); We have to take care of the enemies going out of the playground from the left side. Indeed they won't come back nor being shot by the player anymore so it would be useless to keep then in the game. The jQuery remove() function does this and the return keyword tell the function that it could stop here. The second part takes care of the collision between the enemies and the player's spaceship. The gameQuery collision method return a list of element colliding with the selected ones. The string given as an argument is a filter. If you give one it means that only the elements matching it would be considered for the collision detection. Since the collision detection is a very expensive function you should really avoid checking to many nodes. Here we check for groups and the player, gamquery adds the class group to every node created with the addGroup() method. We need to check for groups since the player is nested in a group but the result list will only contain sprites. In the current version of gameQuery collision are detected with the suposition that sprite are strictly boxes (this means no pixels-per-pixels collision) and that can lead to some unplesent artifacts like shown in the figure bellow. hopefully more options will be impelemented in the future.

Whenever a collision occures we will need to make the enemy explode and the player lose some shield. To make the enemies explode we will simply replace their animation with a explosion animation (remember to change the size of the sprite if you need to). But this animation should be played only once and the sprite should be removed after that. That's here that the Animation.CALLBACK will be used! When setting an animation you can give a function as second argument. This function will be called at the end of the animation. In this cas we give a very simple function that erase the node. There is however a small trick here, we need to remove the class enemy from the sprite otherwise the next time this function is called it will consider the explosion as an enemy! The damage created to the player are manage by the methode that we have writen in step 2, when it returns true it means that the spaceship should explode. The code to make the spaceship explode has been put into a separate function since it's used in several part of the code. function explodePlayer(playerNode){ playerNode.children().hide(); playerNode.addSprite("explosion",{animation: playerAnimation["explode"], playerHit = true; }

Since the player is a group containing a series of sprites we can't simply change an animation. What we do instead is to hide the normal sprites and replace them with a new one. jQuery's hide function hides all the node selected, we call it on the list of nodes contained in our group obtained by jQuery's children method. Then we just need to set the correct state so that the games knows that the spaceship is exploding. Missiles To make the spaceship shoot we will use the classic event driven aproache as we want to react each time the key is pressed. This means adding a small piece of code to the event listener we've used in step 1 to change the animation of the player spaceship. switch(e.keyCode){ case 75: //this is shoot (k) //shoot missile here var playerposx = parseInt($("#player").css("left")); var playerposy = parseInt($("#player").css("top")); var name = "playerMissle_"+Math.ceil(Math.random()*1000); $("#playerMissileLayer").addSprite(name,{animation: missile["player"], posx: playerposx + 90, posy: playerposy + 14, width: 20, height: 5}); $("#"+name).addClass("playerMissiles") break; We read the position of the spaceship as w've done before. We then add a sprite to the missiles layer at the correct position. To obtain an id for the sprite we use the same method as for the enemies. We do a last thing: we add a class to the newly created sprite, this will allow us to easily check for collision later. For the missiles shooted by the enemies we need to extends the update callback we have write above. We need to make the enemy shoot only if it's a Brainy or a Bossy enemy. To find out which one it is we can use the instanceof operator. This return true if the left member is an instance of the right member. AsBossy inherits from Brainy an instance from the first is also an instance of the later. Once again we use the random number generator to make the enemies shoot randomly. //Make the enemy fire if(this.enemy instanceof Brainy){ if(Math.random() < 0.05){ var enemyposx = parseInt($(this).css("left")); var enemyposy = parseInt($(this).css("top")); var name = "enemiesMissile_"+Math.ceil(Math.random()*1000); $("#enemiesMissileLayer").addSprite(name,{animation: missile["enemies"], posx: enemyposx, posy: enemyposy + 20, width: 15,height: 15}); $("#"+name).addClass("enemiesMissiles"); } } If you compare the two code excerpts above they look almost the same. Shooting missile is one thing but we still need to make this missile do some damage when they hit something. Let's first take care of the enemies missiles: the code to do this will be situated in a callback and will look a lot like the code checking for the collision between the enemies and the player. For every missiles shooted by the enemies we need to test for collision with the player spaceship. Like before we use the player's object method to decrease the shield and test if we need to make the ship explode. If the missile touched the player we need to make it explode, with an explode animation and a callback like before. And don't forget,like for the enemies we need to remove the missiles that did get out of the screen. $(".enemiesMissiles").each(function(){ var posx = parseInt($(this).css("left")); if(posx < 0){ $(this).remove(); return; }

$(this).css("left", ""+(posx-MISSILE_SPEED)+"px"); //Test for collisions var collided = $(this).collision(".group,#playerBody"); if(collided.length > 0){ //The player has been hit! collided.each(function(){ if($("#player")[0].player.damage()){ explodePlayer($("#player")); } }) $(this).setAnimation(missile["enemiesexplode"], function(node){$(node).remove();}); $(this).removeClass("enemiesMissiles"); } }); For the player missiles it's more or less the same here again we need to check the class of the enemy to assigne the proper animation. At the very end you can see something unusual: we change the position of the sprite too, not only the widht and height. Depending of the animation that you've decided to use it may be necesary to do so to avoid that you sprite jump on the screen, like shown in the figure bellow.

Of course you can choose to use only one size of animation for all the animation of you sprite... but since I'll rather code than draw I choose this solution :) //Update the movement of the missiles $(".playerMissiles").each(function(){ var posx = parseInt($(this).css("left")); if(posx > PLAYGROUND_WIDTH){ $(this).remove(); return; } $(this).css("left", ""+(posx+MISSILE_SPEED)+"px"); //Test for collisions var collided = $(this).collision(".group,.enemy"); if(collided.length > 0){ //An enemy has been hit! collided.each(function(){ if($(this)[0].enemy.damage()){ if(this.enemy instanceof Bossy){ $(this).setAnimation(enemies[2]["explode"], function(node){$(node).remove();}); $(this).css("width", 150); } else if(this.enemy instanceof Brainy) { $(this).setAnimation(enemies[1]["explode"], function(node){$(node).remove();}); $(this).css("width", 150); } else { $(this).setAnimation(enemies[0]["explode"], function(node){$(node).remove();}); $(this).css("width", 100);

} $(this).removeClass("enemy"); } }) $(this).setAnimation(missile["playerexplode"], function(node){$(node).remove();}); $(this).css("width", 38); $(this).css("height", 23); $(this).css("top", parseInt($(this).css("top"))-7); $(this).removeClass("playerMissiles"); } }); Player controls If the game is not yet over and his spaceship is not exploding we want to let the user control to movements of the saceship. To make the player move we want to know each 30ms if the user is pressing a key because the spaceship should keep moving once the key is pressed. Notice how differant it is from the keyup andkeydown events we've seen in step 1, there was a pasive approache and here is an active one. Javascript doesn't offer such a mechanisme out of the box but gameQuery does! To enable it you just need to pass the keyTracker: true parameter when assigning the node to the playground just like shown in the example bellow. $("#playground").playground({height: PLAYGROUND_HEIGHT, width: PLAYGROUND_WIDTH, keyTracker: true}); Once enabled you can access the key states by reading the jQuery.gameQuery.keyTracker array at the index coressponding to the keycode you're interested in. If the value is true then the key is currently pressed. This allows you to register a simple callback to manage the player controls. To make the sprite move we will use the same technics you've seen before. // this is the function that control most of the game logic $.playground().registerCallback(function(){ if(!gameOver){ //Update the movement of the ship: if(!playerHit){ $("#player")[0].player.update(); if(jQuery.gameQuery.keyTracker[65]){ //this is left! (a) var nextpos = parseInt($("#player").css("left"))-5; if(nextpos > 0){ $("#player").css("left", ""+nextpos+"px"); } } if(jQuery.gameQuery.keyTracker[68]){ //this is right! (d) var nextpos = parseInt($("#player").css("left"))+5; if(nextpos < PLAYGROUND_WIDTH - 100){ $("#player").css("left", ""+nextpos+"px"); } } if(jQuery.gameQuery.keyTracker[87]){ //this is up! (w) var nextpos = parseInt($("#player").css("top"))-3; if(nextpos > 0){ $("#player").css("top", ""+nextpos+"px"); } } if(jQuery.gameQuery.keyTracker[83]){ //this is down! (s) var nextpos = parseInt($("#player").css("top"))+3; if(nextpos < PLAYGROUND_HEIGHT - 30){ $("#player").css("top", ""+nextpos+"px"); } } } else { At this point you should be used to all of this :) , the only trick is to check that the spaceship doesn't go out of the playground. When the player is exploding we need to make it fall of the screen. If it's already out of the screen and there is still some life left we need to make it respawn, otherwise the game is over. To make the player respawn we need to remove the sprite containing the explosion and unhide the sprite representing the spaceship. This is simply done with jQuery showfunction. Don't forget to place the spaceship at the correct position again and reset the state of the game to the normal. //.. } else { var posy = parseInt($("#player").css("top"))+5;

var posx = parseInt($("#player").css("left"))-5; if(posy > PLAYGROUND_HEIGHT){ //Does the player did get out of the screen? if($("#player")[0].player.respawn()){ gameOver = true; $("#playground").append('