Monday, November 23, 2015

Attacking Enemies in my Phaser Platformer

Tossing Pencils
After adding some evil toads to my 8-bit style platformer last week, there were finally some things to avoid while jumping around in the game. Unfortunately, there was no way to defeat the toads. Typically, most enemies in these kinds of games can be killed in some way. This weekend I decided to add an attack capability to the game.

After debating an attack style for a little while, I decided that the basic attack capability should be for the frog to throw pencils. This is supposed to be Science Frog's game, but I haven't updated the frog sprite yet. As a basic attack, I think Science Frog would toss pencils.

Adding some attack pencils to the game turned out to be fairly simple. Phaser really does make game development easy!

The first step was to add a group of weapons to the game, along with a new key tracker to use for firing them off. I also added a variable called nextFire to limit how fast weapons can be thrown.

var weapons;

var FIRE_RATE = 250;
var nextFire;

function create() {

    // ... other stuff ...

    weapons = game.add.group();
    weapons.createMultiple(3, 'pencil', 0, false);

    // ... other stuff ...

    fire = game.input.keyboard.addKey(Phaser.Keyboard.CONTROL);
        
    nextFire = game.time.now + FIRE_RATE;

}

The createMultiple function on Phaser.Group objects takes 4 parameters. The first is the number of sprites to create. The second is the name of the sprite image to use. I added a "pencil" image to my assets for this purpose. The third parameter is the sprite frame to use, but my pencil only has one frame. The fourth parameter indicates if the sprite should exist. I set this one to false. Why would I create sprites that don't exist? How do I use them if they don't exist? Well, sprite "existence" really indicates if the sprite is currently interacting with the rest of the game. You can set a sprite's existence to false in order to temporarily disable it - and then re-use it later. That's exactly what I'll be doing.

The nextFire variable is set to a time in the future. That gets used later on, keep reading.

function update() {

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

    // see if weapons are hitting enemies
    game.physics.arcade.overlap(enemies, weapons, hurtEnemy, null, this);
    
    // ... other stuff ...

    if (fire.isDown) {
     throwSomething();
    }
    
    // ... other stuff ...

}

I added another overlap check, this time between the enemies group and the weapons group. If a weapon is overlapping an enemy, the hurtEnemy function will be called.

The other new piece of the update function is the check to see if the fire key is being held down. I used the CTRL key for this. If it is down, the throwSomething function is called.

First, the hurtEnemy function:

function hurtEnemy(e, w) {
    
    if (!e.immune) {
        e.immune = true;
        e.alpha = 0.5;
        e.damage(0.1);
        
        w.exists = false;
        
        game.time.events.add(200, function() {
            e.immune = false;
            e.alpha = 1;
        }, this);
    }
    
}

This function is very similar to the hurtFrog function I added last week. Here, the first parameter will be the enemy and the second will be the weapon that hit it. Just like I did for the frog, I use an immune flag to make sure the enemies can't get hurt too fast, and if they are hit there is a change to their alpha channel. After 200 milliseconds, the immune flag goes off and the enemy can get hurt again. The big difference between this and the hurtFrog function is the line that sets exists to false for the weapon. This will immediately make the weapon disappear so it can be recycled by the throwSomething function. And speaking of that function. here it is:

function throwSomething() {

    if (game.time.now > nextFire) {

        nextFire = game.time.now + FIRE_RATE;
        
        var weapon = weapons.getFirstExists(false);
        if (weapon){
            weapon.exists = true;
            weapon.anchor.setTo(0.5, 0.5);
            weapon.lifespan = 1500;
            weapon.reset(frog.body.position.x+20, frog.body.position.y-20);
            game.physics.arcade.enable(weapon);
            weapon.body.velocity.y = -400;
            weapon.body.angularVelocity = 50;
            if(frog.direction == 1){
                weapon.body.velocity.x = 500;
            }else{
                weapon.body.velocity.x = -500;
            }
            
        }

    }

}

It seems like there's a lot going on here, but it's really very simple. First, the whole thing is wrapped in a check to see if the current game time is greater than the next fire time. If not, nothing happens. This ensures that the fire rate is constrained. If firing is allowed, the nextFire time is reset to some time in the future and then the fun happens.

The call to weapons.getFirstExists(false) gets the first weapon from the group that doesn't currently exist. Remember, the weapons will get set to not exist after they hit an enemy. If we have a weapon in that state, it gets set to exist again. It also gets a lifespan set on it, so that it will automatically set itself to not exist even if it doesn't hit an enemy after a certain amount of time. Its position gets reset to around the middle of the frog (since he's supposed to be throwing it), and it gets some velocity given to it. That's what makes it fly through the air. I set both x and y velocity so that it moves in a kind of arc motion. I may give different types of weapons different motion attributes at some point.

With those simple additions to the game, you can now throw pencils at frogs. Give it a try for yourself at http://amphibian.com/eight-bit or browse the complete source code on GitHub.

Also remember to check out today's comic! Science Frog doesn't throw any pencils in it.

Amphibian.com comic for 23 November 2015