Monday, August 17, 2015

Frog Animations with Phaser

I'm still putting the finishing touches on my 404-page Frog Soccer game. One of the things that I've been putting off is doing the animations for the frogs themselves. I didn't think the code changes would be that difficult, but drawing the frogs in all those positions could be very time consuming. I didn't already have images for the frog jumping toward the viewer and away from the viewer, but now I do. Three hours later...


Anyway, once I had these awesome sprite sheets of frogs jumping in every possible direction, it was time to use them with Phaser. Setting up sprite sheets in the preload function is easy.

function preload() {

    // ... load other stuff ...

    // spritesheets for frog animation
    game.load.spritesheet("frog", "images/frog_ani.png", 79, 60);
    game.load.spritesheet("frog2", "images/frog_ani2.png", 79, 60);

    // ... load some more stuff ...

}

The last parameters to the function calls are the width and height of each image in the sheet. The images actually contain four rows of three frames each, each 79x60, while the overall image size is 237x240. Phaser will break it up for me into individual frames that I can reference in the code.

Also note that I have two separate sheets. In one all the frogs are green and in the other they are all orange. This is another change I am making so it's easier to tell which frog is yours while playing. It could get confusing sometimes.

Now I can add the animations in the create function.

function create() {

    // ... do other create stuff ...

    frog = group.create(940, 400, "frog");
    // ... other frog setup here ...
        
    frog.animations.add("left", [0, 1, 2], 10, true);
    frog.animations.add("right", [3, 4, 5], 10, true);
    frog.animations.add("front", [6, 7, 8], 10, true);
    frog.animations.add("back", [9, 10, 11], 10, true);
    frog.animations.currentAnim = frog.animations.getAnimation("left");
        
    otherFrog = group.create(720, 400, "frog2");
    // ... other otherFrog setup here ...

    otherFrog.animations.add("left", [0, 1, 2], 8, true);
    otherFrog.animations.add("right", [3, 4, 5], 8, true);
    otherFrog.animations.add("front", [6, 7, 8], 8, true);
    otherFrog.animations.add("back", [9, 10, 11], 8, true);
    otherFrog.animations.currentAnim = otherFrog.animations.getAnimation("right");

    // ... more create stuff ...

}

For each color frog, I create four animations. Each row of images in my sprite sheets represents the three frames of a frog jumping while facing a particular direction. The left-jumping frames are in the top row, positions 0, 1, and 2. The right-jumping frames are in the second row, positions 3, 4, and 5. And so on. You get the idea. When adding the animations, the array specifying which frames make up your animation is the second parameter. If your sprite sheet ran up-and-down instead of left-to-right like mine does, you'd just use a different set of numbers. It's flexible. The particular frames that make up a single animation could be mixed up all over the place in your sprite sheet - that would be confusing but you could handle it just by listing them all in the array.

The third parameter is the animation speed in frames-per-second. I am making the opponent a little slower than you in the game, so I use a slightly slower animation speed. The fourth parameter, which I set to true, is whether or not the animation should loop.

Also note that immediately after creating the animations, I set currentAnim to a specific one. If I don't do this, the animation starts out with whatever one was the last created. That could mean my frogs would have their backs to me instead of facing the ball!

Making a particular animation loop play is easy. I could just call frog.animations.play("left") to play the left-jumping loop, for example. It would place until I call frog.animations.stop(). But of course I want the animation to match the direction in which the frog is actually travelling!

The first thought is to set the animation based on which arrow key the user is holding down. That works, but can get tricky. For example, what if both the up and left keys are being held down? Down and right? Down and left? There are a lot of potential combinations and the logic gets deep. And there's one other consideration - the opponent frogs needs animation set and I definitely can't use the arrow keys for that one.

The solution I came up with was to create a function that I could call from within update and pass both frogs as parameters. Using the physics velocity of the frog, I decide if animation should be completely stopped, or which set to play. If the frog is moving diagonally, it will set based on which cardinal direction has the higher velocity.

function update() {

    // ... lots of other stuff ...

    setAnimation(frog);
    setAnimation(otherFrog);

    // ... still more stuff ...

}

function setAnimation(f) {

    if (f.body.velocity.x == 0 && f.body.velocity.y == 0) {

        f.animations.stop(null, true);

    } else {

        if (Math.abs(f.body.velocity.x) >= Math.abs(f.body.velocity.y)) {

            if (f.body.velocity.x > 0) {
                f.animations.play("right");
            } else if (f.body.velocity.x < 0) {
                f.animations.play("left");
            }

        } else {

            if (f.body.velocity.y > 0) {
                f.animations.play("front");
            } else if (f.body.velocity.y < 0) {
                f.animations.play("back");
            }

        }

    }

}

One thing to note about the animations.stop(null, true) function above - the first parameter, where I send null,represents the name of the animation to stop. It is supposed to be optional. The second parameter indicates if the animation should reset to the first frame, and defaults to false. I wanted the frog to go back to the first frame (sitting) but I didn't need to specify an animation name - I just wanted to stop whatever might be playing. Like I said, the first parameter is supposed to be optional according to the documentation, but it didn't work for me unless I explicitly provided the null first argument. Could be a bug.

In the end, I was happy with the results. Both frogs look better jumping around instead of just gliding across the field. You'll be happy with yourself if you read today's comic...and try clicking (or tapping) on the "naked" frog in the third frame.

Amphibian.com comic for 17 August 2015