Book Image

HTML5 Game Development Hotshot

By : Seng Hin Mak, Makzan Makzan (Mak Seng Hin)
Book Image

HTML5 Game Development Hotshot

By: Seng Hin Mak, Makzan Makzan (Mak Seng Hin)

Overview of this book

Table of Contents (15 chapters)
HTML5 Game Development HOTSHOT
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

Managing the game scene


In this task, we create four scenes and display different scenes based on the game flow.

Prepare for lift off

The following figure is our planning of the four scenes and the flow, showing on how they should link together:

The following figure shows three scenes of what we will create in this task:

Engage thrusters

We code the management part of the scene via the following steps:

  1. The scenes are DOM elements, so we will have the following HTML elements defined inside the tag with the game ID:

    <div id="game-scene" class="scene out">
      <a href="#" id="gameover-btn">Game Over</a>
      <a href="#" id="finish-btn">Finish</a>
    </div>
    <div id="start-scene" class="scene">
      <a href="#" id="start-btn" class="button">Start Game</a>
    </div>
    <div id="summary-scene" class="scene out">
      <a href="#" id="next-level-button" class="button">Next</a>
    </div>
    <div id="gameover-scene" class="scene out">
      <a href="#" id="back-to-menu-button" class="button">Back to menu</a>
    </div>
  2. Now, we need to import our newly created scenes.js file into the HTML file, before the game.js file:

      <script src='js/scenes.js'></script>
      <script src='js/game.js'></script>
    </body>
  3. In the scene.js file, we add the following code to define the scene's object and its instances:

    (function(){
      var game = this.colorQuestGame = this.colorQuestGame ||{};
      // put common scene logic into 'scene' object.
      var scene = {
        node: document.querySelector('.scene'),
        show: function() {
          this.node.classList.remove('out');
          this.node.classList.add('in');
        },
        hide: function() {
          this.node.classList.remove('in');
          this.node.classList.add('out');
        }
      };
    
     // scene instances code to go here.
    )();
  4. Then, we create an instance of the game scene. Put the following code right after the scene object code. The following code creates two temporary links to finish the level and complete the game:

    var gameScene = game.gameScene = Object.create(scene);
    gameScene.node = document.getElementById('game-scene');
    gameScene.handleInput = function() {    
      document.getElementById('finish-btn').onclick = function(){
        game.flow.finishLevel();
      };
      document.getElementById('gameover-btn').onclick = function(){
        game.flow.gameOver();
      };
    };
  5. The start scene instance comes after the game scene code. The following code handles the clicking of the start button that links to the game scene:

    var startScene = game.startScene = Object.create(scene);
    startScene.node = document.getElementById('start-scene');
    startScene.handleInput = function() {    
      document.getElementById('start-btn').onclick = function(){
        game.flow.nextLevel();
      };
    };
  6. Then, we have the summary scene. The summary scene has a button that links to the game scene again to show the next level:

    var summaryScene = game.summaryScene = Object.create(scene);
    summaryScene.node = document.getElementById('summary-scene');
    summaryScene.handleInput = function() {    
    document.getElementById('next-level-button').onclick = function() {
        game.flow.nextLevel();
      };
    };
  7. At last, we add the game over scene code to the scenes.js file. When the game is over, we bring the player back to the menu scene after the back button is clicked:

    var gameoverScene = game.gameoverScene = Object.create(scene);
    gameoverScene.node = document.getElementById('gameover-scene');
    gameoverScene.handleInput = function() {
      var scene = this;
      document.getElementById('back-to-menu-button').onclick = function() {
        game.flow.startOver();
      };
    };
  8. Now, we will define a game flow in the game.js file that will help us control how to show and hide the scenes:

    // Main Game Logic
    game.flow = {
      startOver: function() {
        game.startScene.hide();
        game.summaryScene.hide();
        game.gameoverScene.hide();
        game.gameScene.hide();
        game.startScene.show();
      },
      gameWin: function() {
        game.gameScene.hide();
        game.summaryScene.show();
      },
      gameOver: function() {
        game.startScene.show();
        game.gameScene.hide();
        game.gameoverScene.show();
      },
      nextLevel: function() {
        game.startScene.hide();
        game.summaryScene.hide();
        game.gameScene.show();
      },
      finishLevel: function() {
        game.gameScene.hide();
        game.summaryScene.show();
      },
    }
  9. The init function is the entry point of the game. Inside this function, we will register the click input listeners:

    var init = function() {
      game.startScene.handleInput();
      game.summaryScene.handleInput();
      game.gameoverScene.handleInput();
      game.gameScene.handleInput();
    }
  10. At last, we need some styling for the scenes to make them work. Put the following CSS rules at the end of the game.css file:

    #game {
      width: 480px;
      height: 600px;
      margin: 0 auto;
      border: 1px solid #333;
      text-align: center;
      position: relative;
      overflow: hidden;
    }
    
    .scene {
      background: white;
      width: 100%;
      height: 100%;
      position: absolute;
      transition: all .4s ease-out;
    }
    .scene.out {top: -150%;}
    .scene.in {top: 0;}
    
    .button {
      width: 145px;
      height: 39px;
      display: block;
      margin: auto;
      text-indent: 120%;
      white-space: nowrap;
      overflow: hidden;
      background-repeat: no-repeat;
    }
    .button:hover {
      background-position: 0 -39px;
    }
    .button:active {
      background-position: 0 0;
    }
    
    #start-scene {background: url(images/menu_bg.png);}
    
    #start-btn {    
      background-image: url(images/start_btn.png); 
      margin-top: 270px;
    }
    
    #game-scene {background: url(images/game_bg.png);}
    
    #game-scene.out {
      opacity: 0;
      top: 0;
      transition-delay: .5s;
    }
    
    #summary-scene {background: url(images/summary_bg.png);}
    
    next-level-button {
      background-image: url(images/next_btn.png);
      margin-top: 370px;
    }  
    #summary-scene.in {
      transition-delay: .5s;
    }
    #gameover-scene {
      background: url(images/gameover_bg.png);
    }
    #back-to-menu-button {
      background-image: url(images/restart_btn.png);
      margin-top: 270px;
    } 

Objective complete – mini debriefing

We have created scenes in this task. Now let's take a closer look at each block of code to see how they work together.

Creating buttons

Each button is of 145 pixels by 39 pixels in size and has two states: a normal and a hover state. Both states are combined in one image and thus the final image is of 78 pixels in height. The bottom part contains the hover state. We switch these states by setting the background's y position to 0 pixel for the normal state and -39 pixel for the hover state, as shown in the following screenshot:

Placing the scene logic and the namespace

We encapsulated the scene's management code in a file named scene.js. Similar to the game.js file, we start every logic file with a self-invoked anonymous function. Inside the function, we use the same namespace: colorQuestGame.

The transition between scenes

We use CSS to control the visibility of the scenes. The .in and .out CSS properties that apply to all the scenes have different top values. One is -150% to ensure it is not visible, and the other is top: 0; therefore, it is visible in the game element.

Then, we toggle each scene between the .in and .out class to control the visibility. In addition, we add transition to these CSS rules so changing the value from 0 to -150 percent and vice-versa becomes a smooth animation.

The scene object inheritance

There are four scenes in this game: the pregame start scene, game scene, game over scene, and level-completed summary scene. Each scene shares certain common logic; this includes showing and hiding themselves. In JavaScript, we can use object inheritance to share common logic. The scene is an object with default show-and-hide behaviors. It also defines a dummy sceneElement property that points to the DOM element of that scene.

A game scene is another object. However, we do not create it as a normal object. We use the Object.create method with this scene as the argument. The Object.create method will chain the argument as the new object's prototype. This is known as a prototype chain.

What if we want to show a different effect for hiding a game scene? It depends on whether your effects are done in CSS or JavaScript. If it is a CSS-only effect, you can just add rules to the #game-scene.out scene. In the management part of the scene, .in is to display the scene and .out is for rules that hide the scene.

For the CSS approach, assume that we want a fade-out effect; we can do so using the following CSS rule:

#game-scene.out {
  opacity: 0;
  top: 0;
}

Prototype chaining

JavaScript is an object-oriented programming language without the requirement of a class definition. It uses a prototype chain for object inheritance. Each object comes with a special prototype property. The prototype defines what this object is based on; you can think of it as inheritance in traditional object-oriented languages.

Let's take a look at how JavaScript accesses object properties. When we call Scene.show(), it takes a look at the property list and finds a property named show, which is of the type function.

Imagine now that the show method is not there. The JavaScript runtime looks it up in the prototype. If it is found inside the prototype, it returns the method. Otherwise, it keeps looking up prototype's prototype until it reaches a generic object.

This is the meaning of prototype chaining. We build an object based on another object by putting the other object into the prototype. The following screenshot shows the startScene property and the properties in its prototype (scene):

In order to attach the object to prototype, we use Object.create. The original object's prototype is attached to the new object. It allows us to directly inherit an object's instance into a new object without going through the traditional abstract class definition.

Another approach to put a different object into the prototype is using the Function and new approaches. When we define a function, we can use new to create an object from the function. For example, observe the following function:

function Scene() {}
Scene.prototype.show = function() {}
Scene.prototype.hide = function() {}

Now, when we create a scene object instance by using new Scene(), the instance has the method show and hide. If we want to have a GameScene definition based on the Scene, we can do that:

function GameScene() {}
GameScene.prototype = new Scene();

In this function, we have added an instance of Scene into GameScene. Therefore, the GameScene instance now has all the functionalities of Scene.

Note

More explanation on the difference between the new instance approach and the Object.create approach can be found in the following link to a post from the Mozilla Developer Network:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create

Classified intel

Besides changing the CSS .in and .out classes of the scenes, we can also add extra animation or logic when the scene shows and hides. Instead of the plain scene movement, we can further enhance the transition by defining the game objects in and out of the CSS rule. For example, we can make the buttons fade out during the transition, or we can drop the quest element to the bottom to create an illusion of it being unlocked; this can be done using the following code:

// an example of custom hide function
gameScene.hide = function() {
  // invoke the hide function inside the prototype chain. 
  // (aka. super.hide())
  Object.getPrototypeOf(this).hide.call(this);
  /* extra */
  // add the class for the out effect
  var questView = document.getElementById('quest');
  questView.classList.add('out');
  /* end extra */
}

Since, we have overridden the hide method from the scene object, we need to call the prototype's hide using the scope of gameScene. Then, we add our extra logic to add the out class to the quest DOM element.

We define the dropping effect with CSS transform and transition:

#quest.out {
  transition: all .8s ease-out;
  transform: translateY(800px) rotate(50deg);
}

The out object of the game scene is a delayed fading out transition:

#game-scene.out, #summary-scene.in {
  transition-delay: .5s;
}

In addition, we used the transition delay to make sure that the drop animation is displayed before the scene goes out and the next scene goes in.

Tip

Some new properties of CSS are not stable yet. When a vendor adds support to these styles, they add a vendor-prefix to the property. The vendor prefix indicates that the user should use that property with caution.

In this project, we will omit all the vendor-prefixes for clarity when showing the code in the book. In order to make sure that the code works in a browser, we may need to add the following vendor prefixes: - webkit- (for Chrome and Safari), -moz - (for Mozilla Firefox), - o- (for Opera), and -ms - (for Internet Explorer).

If you find adding prefixes troublesome, you may use some tools for help. The tool prefix-free (http://leaverou.github.io/prefixfree/) is one that can help. Compile tools will add these prefixes for you, such as CSS preprocess compilers.