← Back to home
Tutorial series

Step 8. Enemy damage

Creating Tower Defense game with PIXI Step 8 of 12 2 min read

8.1 Collision with a bullet

If the bullet sprite comes into contact with the enemy sprite, it is necessary to destroy the bullet and apply damage to the enemy unit. Let’s start by tracking sprite collisions.

In the new processEnemyBulletCollision method, we will loop through all the bullets of each tower and for each bullet we will check if there is at least one enemy this bullet collides with:

    processEnemyBulletCollision() {
        this.map.towers.forEach(tower => {
            tower.bullets.forEach(bullet => {
                const enemy = this.enemies.units.find(unit => bullet.collide(unit.sprite));

                if (enemy) {
                    bullet.remove();
                }
            });
        });
    }

    update() {
        // ...
        this.processEnemyBulletCollision();
    }

Let’s develop the collide method in the Bullet class:

collide(sprite) {
    if (!sprite) {
        return;
    }
    return sprite.containsPoint(this.sprite.getGlobalPosition());
}

8.2 Applying Damage

Let’s apply bullet damage to the enemy before destroying the bullet:

    processEnemyBulletCollision() {
        // ...
        if (bullet.collide(enemy.sprite)) {
            enemy.addDamage(bullet.damage);
            bullet.remove();
        }
        // ...
    }

Let’s set the damage field in the Bullet class with the value from the config:

export class Bullet extends EventEmitter {
    constructor(tower, enemy) {
        // ...
        this.damage = this.tower.config.bullet.damage;
    }
}
// ...

Let’s implement the addDamage and remove methods in the Enemy class:

export class Enemy extends Tile {

    constructor(config, path) {
        // ...
        this.hp = this.config.hp;
    }
    // ...

    addDamage(damage) {
        this.hp -= damage;

        if (this.hp <= 0) {
            this.remove();
        }
    }

    remove() {
        gsap.killTweensOf(this.sprite);
        this.sprite.destroy();
        this.sprite = null;
        this.emit("removed");
    }
}

Let’s set the health value from the enemy config. In the addDamage method we subtract the required amount of health and, if the value is less than or equal to zero, call the remove method.

In the remove method, first of all, we need to stop the animation of the gsap tweens. Then we destroy the sprite and fire the removed event:

Let’s subscribe to this event in the Enemies class immediately after creating the unit. Therefore, we will remove the unit from the active units pool when the event occurs:

createEnemy(i) {
    // ...
    enemy.once("removed", this.onEnemyRemoved.bind(this, enemy));
}

onEnemyRemoved(enemy) {
    this.units = this.units.filter(unit => unit !== enemy);

    if (!this.units.length) {
        window.setTimeout(this.create.bind(this), this.waveDelay);
    }
}

After deleting a unit, we additionally check the size of this.units field. If there are no units left in it, then it’s time to create a new enemies wave by calling the create method with a given delay. And let’s add the this.waveDelay field to the Enemies class constructor:

const WaveDelay = 3000;

export class Enemies extends EventEmitter {

    constructor(map) {
        // ...
        this.waveDelay = WaveDelay;
    }
    // ...
}