Friday, November 20, 2015

Enemy Collisions in my Phaser Platformer

Earlier in the week I added some enemies to my 8-bit style Phaser platformer, but they were basically just images in the level. Bumping in to them didn't really do any damage. There was a little more work I had to do in order for them to become proper enemies.

If you read my post from Wednesday, you'd know that I created all the enemies from data in an object layer in the map. When I did that, I put all the enemy objects in a group called enemies. All of that took place in the game's create function. Checking collisions and hurting the frog takes place in the update function.

Here's what I wanted to happen. If the frog touches an enemy, he should get hurt. How much should probably vary by enemy but that's not extremely important at the moment. When the frog gets hurt, there should be some kind of visual indicator. For now, I've decided to set the frog's alpha channel to 50% for 500 milliseconds to indicate damage. For those 500 milliseconds, the frog can't be hurt again, and he should be thrown away from the enemy that inflicted the damage.

First, I added some fields to the frog object when I create it.

function createFrog() {

    // ... other stuff ...

    f.immune = false;
    f.health = 3;

    // ... other stuff ...    
    
}

The immune field is just something I made up that should indicate if the frog is currently in that 500 millisecond period where he can't get hurt again. He's immune from further damage. The health field is actually provided by Phaser. You can assign a health value to a sprite, and then change it by calling damage or heal. When health reaches 0, the sprite is killed. The maximum value is arbitrary, so I set it to 3 for now.

Now in the update function, I add some more collision checks. First, I added a collision check between the enemies and the layer object. This will make the enemies sit on the ground and logs and stuff just like the frog does. Then I added an overlap check between the frog and the enemies. Why overlap instead of collision? If I checked for collision, the frog would push against the enemies and possibly even move them. I wanted encounters with enemies to only impact the frog. Checking for overlap allows me to call a function when two sprites are in collision but Phaser takes no action to remove the sprites from the collision state - I'll handle that manually. That's why I give the third parameter, hurtFrog, to the overlap function. The hurtFrog function will be called when overlap is detected. Here are the important parts of the update function:

function update() {

    game.physics.arcade.collide(frog, layer);
    game.physics.arcade.collide(enemies, layer);
    game.physics.arcade.overlap(frog, enemies, hurtFrog, null, this);

    if (!frog.immune) {
        frog.body.velocity.x = 0;
    }

    // ... other stuff ...

    if (!frog.immune) {
        if (cursors.left.isDown) {
            frog.body.velocity.x = -150;
        } else if (cursors.right.isDown) {
            frog.body.velocity.x = 150;
        }
    }

    // ... other stuff ...

}

The other changes to update are to only set the frog's velocity if the frog is not in that immune state. This is basically to override the player's control over the frog. I'm going to essentially toss the frog out of the way regardless of what key the player might be holding down.

...Which brings us to the the hurtFrog function. This is where the action is!

function hurtFrog(f, e) {
    
    if (!f.immune) {
        f.immune = true;
        f.alpha = 0.5;
        f.damage(0.1);
        if (f.body.position.x < e.body.position.x) {
            f.body.velocity.x = -300;
        } else {
            f.body.velocity.x = 300;
        }
        game.time.events.add(500, function() {
            f.immune = false;
            f.alpha = 1;
        }, this);
    }
    
}

Remember, this function will be called by Phaser when the frog overlaps with an enemy. The first parameter passed in will be the frog object and the second will be the enemy. This is the same order that they object are specified in when calling overlap. In this function, I first check to see if the frog is currently in that immune state. If he is, I do nothing. But if he isn't, I set the immune flag, set the alpha channel to 0.5, and do a little damage. Then comes the frog-tossing! To throw the frog out of the way of whatever enemy he just hit, I check to see where he is in relation to the enemy. If he's on the left side, I give him some velocity to toss him to the left. If he's on the right, I toss him to the right. Finally, I set that 500 millisecond timer. When time's up, the immune flag goes off and the alpha channel goes back to 100%. At this point, the frog can get hurt again.

You can give the game a try for yourself and let me know what you think. I am still playing around with the numbers for the toss velocity and such. The whole point of this sandbox level is to get the right feel for the game before I start making the real levels. The complete source code is on GitHub as well, if you want a closer look.

Also, don't forget to have a closer look at today's comic!

Amphibian.com comic for 20 November 2015