7.1. Создание снаряда
Башни стреляют по врагам снарядами, представленными спрайтом fire.png. Создадим класс Bullet, создающий снаряд для заданной башни:
import { EventEmitter } from "events";
import { App } from '../system/App';
export class Bullet extends EventEmitter {
constructor(tower, enemy) {
super();
this.tower = tower;
this.enemy = enemy;
this.sprite = App.sprite("fire");
this.sprite.anchor.set(0.5, 1);
this.sprite.x = this.tower.sprite.x;
this.sprite.y = this.tower.sprite.y;
this.sprite.angle = this.tower.sprite.angle;
}
}
Унаследуем его от EventEmitter, чтобы отправлять события снаряда. Например, позже нам понадобится знать момент его уничтожения — при попадании во врага или выходе за экран — по соответствующему событию.
Передадим в конструктор Bullet объект стреляющей башни и врага, по которому выпущен снаряд.
Создадим спрайт снаряда и поместим его в координаты башни с тем же углом поворота.
За выстрел отвечает shoot класса Tower. В нём нужно создать экземпляр Bullet:
export class Tower extends Tile {
constructor(config) {
// ...
this.bullets = [];
this.active = true;
}
// ...
shoot(enemy) {
const bullet = new Bullet(this, enemy);
this.bullets.push(bullet);
this.sprite.parent.addChild(bullet.sprite);
}
}
Метод также принимает объект врага, по которому стреляет башня. Все созданные снаряды будем сохранять в this.bullets, чтобы затем отслеживать их и проверять столкновения с врагами.
Изменим attack в Tower, добавив вызов shoot при активной башне:
attack(enemy) {
this.rotateToEnemy(enemy);
if (this.active) {
this.active = false;
this.shoot(enemy);
window.setTimeout(() => this.active = true, this.config.cooldown);
}
}
Флаг active показывает, может ли башня стрелять сейчас. Tower.attack вызывается в Game.update, то есть постоянно, на каждом кадре. Но мы хотим выпускать снаряды только с частотой, заданной для башни.
Поэтому для перезарядки нужно отключать активность башни сразу после выстрела и включать после тайм-аута. Запустим тайм-аут с временем перезарядки из конфигурации, затем снова активируем башню флагом this.active = true;
7.2. Полёт снаряда
Чтобы снаряд двигался, на каждом кадре нужно менять координаты его спрайта на заданное смещение. Определим смещение по x и y с учётом угла поворота спрайта. Сделаем это в Bullet.init:
export class Bullet extends EventEmitter {
constructor(tower, enemy) {
// ...
this.init();
}
init() {
const speed = this.tower.config.bullet.speed;
const azimuth = (this.sprite.angle) * (Math.PI / 180) - Math.PI / 2;
this.velocity = {x: Math.cos(azimuth) * speed, y: Math.sin(azimuth) * speed};
App.app.ticker.add(this.update, this);
}
update() {
this.sprite.x += this.velocity.x;
this.sprite.y += this.velocity.y;
}
}
Берём скорость снаряда из заданной ранее конфигурации башни. Затем определяем азимут по углу поворота спрайта после создания. Зная азимут и скорость, вычисляем смещение по x и y и записываем его в this.velocity.
Создаём callback update, в котором прибавляем полученное смещение к координатам спрайта снаряда.
Добавим функцию в PIXI.Ticker, чтобы она выполнялась на каждом кадре игры.
7.3. Уничтожение снаряда
Вышедший за экран снаряд нужно уничтожить по двум причинам:
- неиспользуемый объект не должен занимать память
- ненужные проверки не должны выполняться, а
updateснаряда не должен продолжать бесполезные вычисления
Сделаем это сразу, чтобы избежать возможных проблем с производительностью игры.
Проверим координаты снаряда. Если он вышел за экран, уничтожим спрайт и удалим update из ticker:
update() {
this.sprite.x += this.velocity.x;
this.sprite.y += this.velocity.y;
let position = this.sprite.getGlobalPosition()
if (position.x < 0 || position.x > App.app.view.width) {
this.remove();
}
}
remove() {
App.app.ticker.remove(this.update, this);
this.sprite.destroy();
this.sprite = null;
this.emit("removed");
}
Достаточно проверить координату x спрайта. Для получения глобальной позиции используем метод getGlobalPosition класса PIXI.Sprite. Левая граница экрана имеет x = 0. Координату правой границы получим из ширины canvas через App.app.view.width. Если x снаряда меньше 0 или больше правой границы, уничтожим его спрайт, удалим callback из ticker и отправим соответствующее событие.
Теперь можно отслеживать событие уничтожения снаряда. Изменим Tower.shoot, удаляя уничтоженный снаряд из пула башни:
shoot(enemy) {
// ...
bullet.once("removed", () => this.bullets = this.bullets.filter(item => item !== bullet));
}