Apr 20, 2015

Creating Side-scroller Game in HTML5 and Javascript

Recently I completed my first game, Penguin Walk, with help of free art in Javascript and it gave me empirical experience of a known-fact surrounding game development – “The hardest thing about developing a game is finishing it.”

Although, the game is dead simple and code runs in only few hundreds of lines, it took a lot of effort to finish it. The reason? I believe the hard thing related to game development, is that you are attempting to build something complex out of very simple elements (pixels). The same reason why building a programming language is hard – you are trying to ensure that a stream of characters obeys formal grammatical rules and convert them into something that can execute. In this post, I will describe how I created my side-scroller game.

Is Game Programming hard?

Think about it. What does it take to project a bird from a slingshot, show it flying in projectile motion, hitting a set of object which are then animated to move accurately, in accordance to laws of physics? Well, a lot. You can only tell computer what to paint on the screen, rest of all the higher level abstractions, you build on your own.

Although, using a library might help a lot; after all, who can be expected to complete a desktop application if he has to program all controls on his own but if you compare the two, there are two complications that arise in game development –

I believe, programming a game, involves thinking at multiple levels of abstractions, which makes it challenging (and fun!).

Getting the art

“Only! only if I could do art, I would start shipping games.” — A recurring thought that must have went through many programmers’ head and to some extent it might be true. Getting art done, isn’t a piece of cake but guess what, you need not to. Plenty of free and non-free resources are available to help you get started.

Although, it might not give you a highly polished look you fancy but if you get a decent prototype, it is easy to iterate with professional help to take a step further. So don’t whine about your non-artistic background, start working with available art and focus on the gameplay. I used free art from HasGraphics and OpenGameArt and free sounds effects from Soundbible.

Building the game

Animating using viewport and a larger window

The first step in making our side-scroller would be to working out the animation part. You are given number of objects, how would you animate them? One obvious choice would be to work out the position, the visible part of each object in every frame and paint them on the canvas. Another approach, which I used, is to have a longer canvas and a moving viewport that shows only the part of the former and keep updating it with newer object. We refer the longer canvas as “scene generator”.

The init() function constructs two canvas objects, one a bigger window, another a viewport which is a sliced-off view of the bigger window. The latter is just meant for processing and hence isn’t appended in the document.

Loading Resources

To ensure that all our resources are available when rendering, we must make sure they are loaded successfully before we initialize our game. Since, resource loading is asynchronous, we must take help of callbacks to trigger something when all resources have been loaded. loadResources() function monitors the number of objects that have been loaded successfully, compares them to those which are required to load and fires the callback (init) when the process is finished.

Using sprites

Canvas’ drawImage function allows you to paint an image (or part of it) on canvas. Using drawImage we can use a larger sprite image and define what objects are where in sprite. Sprite object defines methods to paint the objects on canvas. The init() function initializes the Sprite with the definition of objects (X, Y, Width, Height) :

sprites.addObjects({
  "gground": [36, 19, 107, 90],
  "sground": [36, 171, 107, 90],
  "gstone": [36, 281, 107, 90],
  "sstone": [36, 409, 107, 90],
  "background": [0, 0, 1, 1]
});

The initialized class is used to create a Scene object where Sprite methods are called to paint on the scene generator:

var scene = new Scene(sceneGenContext, sprites, [imgObj.Cloud, sfxSounds]);

Creating a scene

Now we know how to create sprites, the next step is to paint a scene using them. Recall that, we are using “moving viewport” approach, so, all our painting would happen on the scene generator whose length is twice that of viewport. There are four basic objects in our game – the background, clouds, lands and the animated penguin. The first three form part of our scene while the animated penguin is painted after the rendering the scene. The paintClouds(), paintBackground() methods are self-explanatory. The drawLand() function paints the land of given unit height, by first series painting “soil” objects then painting “ground” object for their top. Each land’s data is stored in landPoints array so that we can check out penguin is going the right way.

The drawLands() function creates three land objects, each of length two. However, with these a little bit of canvas is left out, which we draw as a partial object, i.e, of length less than two. So we now have our basic scene, which looks like this,

image

Animating the scene

As we already discussed, we would be using “moving viewport” approach to have an animating scene. To do this, we have “curX” variable in Scene object which dictates the portion of the scene generator to be painted on the viewport which is basically [curX, curX + viewport.width]. The actual painting of the Scene on the viewport is done by showScene() in the Game object with:

realContext.drawImage(scene.sceneGenContext.canvas, scene.curX, 0, CONFIG.CANVAS_WIDTH, CONFIG.CANVAS_HEIGHT, 0, 0, CONFIG.CANVAS_WIDTH, CONFIG.CANVAS_HEIGHT);

At every frame, curX changes and different portion gets painted on viewport. Additionally, updateLandPoints() checks if the current land hasn’t gone out of focus with (this.landPoints[0].posX + this.landPoints[0].width - this.curX) < 30, in which case, the first landPoint is removed from the array and the new focus land is repainted as ground one. After curx becomes larger then viewport’s width, the first half of larger window gets spliced to the first half and second half is regenerated. Something which happens in generateScene():

this.generateScene = function()
{
  // Splice second half to first
  sceneGenContext.drawImage(sceneGenContext.canvas, CONFIG.CANVAS_WIDTH, 0, CONFIG.CANVAS_WIDTH, CONFIG.CANVAS_HEIGHT, 0, 0, CONFIG.CANVAS_WIDTH, CONFIG.CANVAS_HEIGHT);
  spriteObj.paintBackground( CONFIG.CANVAS_WIDTH );

  // Assign new positions to spliced lands
  var i = 0;
  while( typeof this.landPoints[i] !== "undefined" )
  {
      this.landPoints[i].posX -= CONFIG.CANVAS_WIDTH;
      i++;
  }

  // Regenerate scene.
  this.paintClouds(CONFIG.CANVAS_WIDTH);
  this.drawLands(CONFIG.CANVAS_WIDTH);
  this.curX -= CONFIG.CANVAS_WIDTH;
};

So out animation logic in a nutshell is, keep updating viewport with new coordinates over the larger window and when it is about to finish, recreate the larger window. The penguin is animated over the viewport by painting a new frame of penguin’s animation every time penguin.paintFrame is called.

The Game

Now finally, we arrive at building our game logic. So what does our game have? Change land height on “Up” / “Down” key press, check if penguin is walking on levelled land, and trigger game over it isn’t.

Changing Land Height

When a key is pressed, it triggers keyHit() method:

this.keyHit = function( keyCode )
{
  switch(keyCode)
  {
      case CONFIG.ACTION_MAP.LAND_UP:
          if( this.gameBegun ) scene.updateHeightLand(1);break;

      case CONFIG.ACTION_MAP.LAND_DOWN:
          if( this.gameBegun ) scene.updateHeightLand(0);break;

      case CONFIG.ACTION_MAP.GAME_START:
          this.beginGame();break;
  }
};

When LAND_UP and LAND_DOWN keys, call updateLandHeight() which repaints lands to a newer height. I faced a problem while building this. Earlier I directly used to modify landHeight without storing original generated height. Since the logic is to modify height of all lands on the same level, it used to modify lands which were not originally, on the same level. For eg, lets say height of generated lands are – 1, 1, 2, 3. After pressing “Up”, the new heights are “2, 2, 2, 3”. Without storing original height, the logic would also modify the height of the third one. The solution was to store original height separately, i.e, use origHeight.

var  i = 0;
while( typeof this.landPoints[i] !== "undefined" && oldOrigHeight == this.landPoints[i].origHeight )
{
  if( inc && this.landPoints[i].landHeight < 4 )
      ++this.landPoints[i].landHeight;

  if( !inc && this.landPoints[i].landHeight > 1 )
      --this.landPoints[i].landHeight;

  this.clearLand(this.landPoints[i].posX, oldHeight, this.landPoints[i].landLength);
  this.drawLand(this.landPoints[i].landHeight, this.landPoints[i].landLength, this.landPoints[i].posX, true, "ground");

  i++;
}

The use of clearLand() is simply to clear out part of land when its height is decremented.

Checking Game Over

The checkGameOver() function checks if height of two consecutive is uneven and triggers gameOver when visible length of first land has fallen before threshold. we have used two different thresholds. One to check when penguin collides with a land and the other when penguin falls from land of higher height. For eg,

image

Because two lands are uneven and first land’s visible length is almost below threshold, gameover is triggered immediately after. triggerGameOver resets the state of the game, and our game can be restarted.

The Game Loop

The showScene() also is our main execution loop. Using requestAnimationFrame() we repetitively request and paint scene on our canvas. We also call checkGameOver() to see if there has been any collision and reset the state of the game, in that case.

Conclusion

So that sums up our mini game. It was a fantastic experience seeing it to the finish. There are many game frameworks available that can save you from the trouble of creating abstractions, be sure to check them out. The most important lesson I learned was to stick around and see it to finish.

So if you wish to build a game, build it like a MVP. Take free art, or maybe just circles and polygons, and focus on building game play. Once you have something good, you can start iterating with better art and visual effects but the important thing is to get started.


Follow Me!

I write about things that I find interesting. If you're modestly geeky, chances are you'll find them too.

Subscribe to this blog via RSS Feed.

Don't have an RSS reader? Use Blogtrottr to get an email notification when I publish a new post.