Friday, July 17, 2015

Speed Based Animation with Phaser

I'm still working on my game for the Amphibian.com 404 page. Initially I had an idea of where I wanted to go with it, but after making a demo of the frog kicking a soccer ball my daughters decided that a frog soccer game would be awesome. Since they outnumber me, I have decided to go with their idea and implement the rest of the game as frog soccer.

The goal for this weekend: add goals.

But before I get to that, I wanted to add a simple animation to the soccer ball. I want to add animation to the frog too, but it's been a busy week. Anyway, I just wanted the ball to spin when the frog kicks it, and the speed of rotation to slow down as the forward motion of the ball slows down.

Here is an animated GIF I created of the game to illustrate what I mean.


So it behaves like a real soccer ball - it appears to roll in a more-or-less realistic manner.

Here's what I had to do in order to achieve this effect.

First, I needed to create a sprite sheet of all the frames of the ball's rotation. I used Inkscape and the GIMP to create a 45x360 pixel image of the ball at different phases of spin. A single frame of the animation is 45x45.

soccer ball spritesheet
Going back to my game code from Wednesday, I replaced the image load for the ball with a spritesheet load which specifies the frame size (line 5 below).

function preload() {

    game.load.image('tree2', 'images/tree2.png');
    game.load.image('frog', 'images/frog.png');
    game.load.spritesheet('ball', 'images/ball_animation.png', 45, 45);

}

I then added another variable for the animation, and put it in the proper scope to be referenced by both the create and update functions. After creating the ball sprite like normal, I create the animation named "roll" by calling ball.animations.add("roll").

var frog;
var tree;
var ball;
var group;
var cursors;
var anim;

function create() {

    group = game.add.group();

    // ... create other sprites ...

    ball = group.create(300, 300, 'ball');
    game.physics.enable(ball, Phaser.Physics.ARCADE);
    ball.body.bounce.set(1);
    ball.body.drag.set(20);
    ball.body.allowGravity = false;
    ball.body.setSize(45, 35, 0, 8);
    ball.body.collideWorldBounds = true;

    anim = ball.animations.add("roll");

    // ... other stuff ...

}

The tricky part comes in the update function. I check and use the velocity of the ball to determine if the animation should be playing and how fast it should run. Look at the code below:

if (ball.body.velocity.x == 0 && ball.body.velocity.y == 0) {
    anim.stop();
} else {
    var speed = Math.min(1, Math.max(Math.abs(ball.body.velocity.x),
                Math.abs(ball.body.velocity.y)) / 200) * 9;
    if (anim.isPlaying) {
        anim.speed = speed;
    } else {
        anim.play(speed, true);
    }
}

If the ball has no velocity on either the x or y axis then it is not moving at all. In that case, stop the animation. Otherwise, I calculate the animation speed. In Phaser, animation speed is specified in frames per second and has a minimum value of 1. I want the maximum frame speed to be 9, and I want to use that when the velocity of the ball is equal to or greater than 200. Velocities under 200 will calculate an animation frame rate as a ratio, but not go under 1. A velocity of 100 will result in a frame rate of 4.5, for example. If the animation is already playing, I just change the speed. If the animation is not currently playing, I start it at the calculated speed.

I am very happy with how it turned out.

I know I haven't made this code available on GitHub yet, but I will soon. Like tomorrow. For today, though, just read the comic.

Amphibian.com comic for 17 July 2015