Friday 24 April 2015

Animating Your Game Characters With Sprites

Welcome back to building a 2D mobile game with Cordova and Canvasquery/Playground.

In the last episode, we got a simple scene up and running, where you could control a red box on a html5 canvas by using the keyboard arrows.

See the demo here

This is a really simple example of what can be done with the Playground and Canvasquery libraries.

Let's take this a step further by replacing our red square with a picture of a bird, or better yet a series of pictures that will lead to the illusion of a moving bird.

You can see this in action here

Animations are made up of frames, and each frame is swapped out at ever step for the next one in the queue.
If you play the frames fast enough, it gives the illusion of a moving character.

How we will achieve this is by using what is called a sprite.

A sprite is a 2D strip of images of an object at different stages of movement.
All of the images are glued together to form one image.

It looks something like this:


You can create your own sprites or download one from various free libraries on the web.

If you crop the right parts of the image when you display it at each stage, you can select a certain frame from the sprite.
The most common way to make an animation from this is to crop the image from left to right, moving over one image length at a time.

Ok, so now to the implementation details.

We can load images into our canvasquery app by using:

playground.loadImages("bird_right")

where playground is the instance of your playground app you created with the playground() constructor.

The text you pass to loadImages will be the name of the image in the public/images directory, without the file extension, that you are trying to load.

The best place to call this method this is in the "enter" callback of your game state, so that the image will be ready for the render step.

add this code to your game_screen.coffee file from the last tutorial:

enter: ->
      game.loadImages "bird_left"
      game.loadImages "bird_right"

      @player = new Player(new Point(100, 100))

This code loads the images into our app into a hash called images. The keys in this hash are the names that you passed into loadImages.

So for example, you can access the bird_right image with game.images.bird_right, where game is the playground instance we create.

You can download the sprite from above and place it in the src/images directory in the project.

I have added a grunt task (grunt copy:assets) to the project that will copy all assets from the source images directory into the public/images directory where they will then be served from. Have a look in the tagged source

Now that we have our sprite loaded, we want to paint it at the current position of our square.

What I would recommend doing for this is abstracting away the position of the square into a Player class, as we will use this class to store more attributes relating to the character at a later stage.
We will also make a Point class which will be used to store the x, y coordinate of the player.

With these classes created (you can reference the tagged source for this), we can now retrieve the position of the player and draw a portion of the image sprite instead of the red square.

To do this, we have to rotate over the frames in the sprite. So, we create a method in the Player class getNextSprite()

Player::getNextSprite = ->
   delta = (Date.now() - @lastTick) / 1000
   frame = 8 * (delta % @duration / @duration) | 0
   
   offset = 19

   sprite = [
     offset + (frame * 109)
     28
     109
     100
   ]
   
   #console.log(frame + " " + sprite);
   sprite

This function returns a set of cropping dimensions for our sprite.
It is specific to that sprite. It can be tedious to get this perfect,
but you can use a tool like gimp to help you out.

The formulas in there basically calculate what frame should be returned at any time, in such a fashion that it makes corrections for CPU lag etc.

We now take those dimensions and crop the sprite using them in the render step, with the following code:

render: (delta) ->
      game.layer.clear("#7EC0EE")
      #.fillStyle("red")
      #.fillRect(@player.position.x, @player.position.y, 50, 50)

      game.layer.drawRegion @player.getImage(), @player.getNextSprite(), @player.position.x, @player.position.y

The drawRegion method of Canvasquery takes as arguments the image to be drawn, the dimensions to crop the image, then the x and y coordinates to draw the cropped image. See Here

The x and y coordinates we simply read from the Player class.

The image, we load from our images hash we talked about earlier.
But we see that we are calling out to the player class here to ask for the correct image.

The reason we do this is that I have actually loaded two game sprites.
One for when the player is facing right and one for when he is facing left.

Depending on the direction, we return the appropriate image, to make it look like the player is actually flying in the right direction.

Again, take a look at the demo to see this in action!

I think we will leave it there for this post.

It is plain to see that this simple technique makes a big difference in bringing your game to life.
Already, we can see that we are getting the basic shape of the game together.
In the next post, I will talk about smooth animations with the requestAnimationFrame() browser API.

Until Then...

No comments:

Post a Comment