Tutorial 1 - Game Logic

Game State

Every game a little more than trivial need some sort of game state to determine when which actions are allowed or supposed to occur. 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:

There is another hidden game state that we’ve seen before, it’s the grace variable in the player object. To store this information 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 occur at the same time as some of the other ones but it may have rendered the code slightly less readable (yeah I know, we could have used only one variable, but then we’ll have some pretty unintuitive 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 . The Math.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 obtain 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 occur 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 defined 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 simpler 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 boss to reset the state of the game to a normal 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 the each() 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, gameQuery 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 collisions are detected with the supposition that sprite are strictly boxes (this means no pixels-per-pixels collision) and that can lead to some unpleasant artefacts like shown in the figure bellow. hopefully more options will be implemented in the future.

Whenever a collision occurs 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 case 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 method that we have written 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 approach 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 written 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. As Bossy 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 assign the proper animation. At the very end you can see something unusual: we change the position of the sprite too, not only the width and height. Depending of the animation that you’ve decided to use it may be necessary 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 spaceship. 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 different it is from the keyup and keydown events we’ve seen in step 1, there was a passive approach and here is an active one. Javascript doesn’t offer such a mechanism 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 corresponding 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 show function. 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('<div style="position: absolute; ... >');
                  $("#restartbutton").click(restartgame);
                  $("#actors,#playerMissileLayer,#enemiesMissileLayer").fadeTo(1000,0);
                  $("#background").fadeTo(5000,0);
                } else {
                  $("#explosion").remove();
                  $("#player").children().show();
                  $("#player").css("top", PLAYGROUND_HEIGHT / 2);
                  $("#player").css("left", PLAYGROUND_WIDTH / 2);
                  playerHit = false;
                }
              } else {
                $("#player").css("top", ""+ posy +"px");
                $("#player").css("left", ""+ posx +"px");
              }
            }
          }
        }, REFRESH_RATE);

What should we do when the game is over ? Well we can display a nice message and slowly hide the content of the game. To do this we can simply add a HTML node to the playground that displays what we want. The jQuery append() function is perfect for this, it generates the content describe by the string given as an argument a the end of the already existing element of the selected element(s). By calling this on the playground node we will simply add a div that contains the following html code.

<div style="position: absolute; top: 50px; width: 700px;
                                                    color: white; font-family: verdana, sans-serif;">
          <center>
            <h1>Game Over</h1>
            <br>
            <a style="cursor: pointer;" id="restartbutton">Click here to restart the game!</a>
          </center>
        </div>

It’s nice to offer the possibility to the player to restart the game. This is a sensible subject since gameQuery doesn’t offer a simple way to reset the state of the game. For my last game and this one I’ve use a small trick: the window.location. This javascript object allows to manipulate the URL of the browser and amongst other things reload the page. This would be good enough for us here.

// Function to restart the game:
        function restartgame(){
        };

So once the nodes have been added we register an event to trigger the reload. Like we did with the keydown and keyup before we can react to a click on a text. The jQuery function for this is click, like the keydown it take a function as parameters that will be executed when the event is triggered.

The jQuery fadeTo method makes the game layers disappear progressively, the first argument is the time it takes to fade (in milliseconds) and the second one is the opacity goal (from 0 for transparent to 1 for opaque).

Well’s that’s it! Congratulation if you have keep with me up to this point! The last part is only a series of small thing that make the game complete but nothing too fancy!

next step: Warp up

Fork me on GitHub