← На главную
Серия руководств

Шаг 6. Подготовка к стрельбе

Создание Tower Defense с PIXI • Шаг 6 из 12

В игре есть и враги, и башни. Можно переходить к реализации стрельбы по врагам.

6.1. Зона действия башни

Определим, как башня будет атаковать врага.

Сначала враг должен попасть в зону стрельбы башни. Тогда башня сможет обнаружить его и начать атаку. Дальность уже задана в конфигурации башни. Зона является кругом заданного радиуса, который можно нарисовать через PIXI.Graphics.

Создадим зону огня в классе Tower: нарисуем круг заданного радиуса в глобальных координатах башни через PIXI.Graphics.

export class Tower extends Tile {
    // ...
    createArea() {
        this.area = new PIXI.Graphics();
        this.area.beginFill(0xffffff, 1);
        this.area.drawCircle(this.sprite.getGlobalPosition().x, this.sprite.getGlobalPosition().y, this.config.range);
        this.area.endFill();
    }
}

Вызовем этот метод в Game при создании башни:

export class GameScene extends Scene {
    create() {
        this.createCollisionsContainer();
        // ...
    }

    createCollisionsContainer() {
        this.collisions = new PIXI.Container();
        this.container.addChild(this.collisions);
    }

    onTowerPlaceClick(towerPlace) {
        // ...
        tower.createArea();
        this.collisions.addChild(tower.area);
    }

В Game также нужен отдельный контейнер this.collisions. Создадим его в самом начале и добавим первым дочерним элементом контейнера Game. Все зоны башен добавим в него. Так изображения зон окажутся под слоем карты.

6.2. Обнаружение врага

Теперь можно проверить, находится ли враг в созданной зоне стрельбы.

Сначала реализуем Tower.detect:

detect(enemy) {
    if (enemy.sprite) {
        return this.area.containsPoint(new PIXI.Point(enemy.sprite.x, enemy.sprite.y));
    }
    return false;
}

Используем метод containsPoint класса PIXI.Graphics, передавая координаты врага и проверяя, принадлежат ли они текущей зоне.

Метод нужно вызывать постоянно на протяжении игры, чтобы проверять видимость врагов в реальном времени.

Для этого в классе Game используем возможности (PIXI.Ticker)[https://pixijs.download/v7.0.5/docs/PIXI.Ticker.html], чтобы вызывать update на каждом новом кадре анимации:

    setEvents() {
        App.on("tower-place-click", this.onTowerPlaceClick.bind(this));
        App.app.ticker.add(this.update, this);
    }

    update() {
    }

Запишем алгоритм проверки обнаружения врагов башнями:

  • для каждой башни, созданной на уровне
    • найти первого юнита в зоне её огня
      • если юнит найден, атаковать его

Реализуем этот алгоритм в processTowerAttackEnemies класса Game и вызовем метод в update:

    processTowerAttackEnemies() {
        this.map.towers.forEach(tower => {
            const enemy = this.enemies.units.find(unit => tower.detect(unit));

            if (enemy) {
                tower.attack(enemy);
            }
        });
    }

    update() {
        this.processTowerAttackEnemies();
    }

Саму атаку реализуем в следующих уроках. Пока оставим Tower.attack пустым:

// Tower.js
attack() {}

Сейчас созданные башни получаем из this.map.towers. Нужно реализовать этот геттер в LevelMap. Получить все башни просто:

  • получить все тайлы слоя towers — места для башен:
const towerPlaces = this.tiles["towers"];
  • отфильтровать места, где башни действительно построены:
const towerPlacesWithTowers = towerPlaces.filter(towerPlace => towerPlace.tower);
  • получить башни через Array.prototype.map:
towerPlacesWithTowers.map(towerPlace => towerPlace.tower);

Все действия можно записать одной строкой:

    get towers() {
        return this.tiles["towers"].filter(towerPlace => towerPlace.tower).map(towerPlace => towerPlace.tower);
    }

6.3. Поворот башни

Повернём башню к врагу, по которому нужно стрелять. Для этого определим угол поворота спрайта. Похожий механизм уже есть в Enemy: поворот к следующей точке пути. Перенесём этот код в Tower:

    getAngleByTarget(targetPosition) {
        const x = targetPosition.x - this.sprite.parent.x;
        const y = targetPosition.y - this.sprite.parent.y;
        return 180 * Math.atan2(y, x) / Math.PI + 90;
    }

    rotateToEnemy(enemy) {
        this.sprite.angle = this.getAngleByTarget({x: enemy.sprite.x, y: enemy.sprite.y});
    }

Поскольку спрайт башни является дочерним элементом тайла её места, нужно взять координаты тайла места. Поэтому исходные координаты получаем из родительского спрайта:

  • this.sprite.parent.x
  • this.sprite.parent.y

В атласе tilemap.png тайлы башен повёрнуты на 90 градусов относительно тайлов юнитов. Поэтому перед возвратом результата из getAngleByTarget добавляем 90 градусов.

Изменим attack в классе Tower:

// Tower.js
attack(enemy) {
    this.rotateToEnemy(enemy);
}