JavaScript»ゲーム制作»EventDispatcher イベントを使って、倒した敵を画面から消す

ファイル名: js-game/quest_00927.html

下のソースコードをうつしてプログラムを作ってください。四角は1秒に1つずつふえていき、クリックしたら画面から消えてスコアがふえます。

ソースコード

// イベントを表すクラス
// 好きなものを入れておくことのできるdataというプロパティを用意する
class GameEvent {
  constructor(data) {
    this.data = data;
  }
}

class ScoreLabel {

  constructor() {
    this.score = 0;
  }

  update() {
  }

  render(context) {
    context.fillStyle = "rgb(0, 0, 0)";
    context.font = "24px monospace";
    context.fillText("SCORE: " + this.score, 0, 24);
  }

  addScore(score) {
    this.score += score;
  }
}

class Enemy {
  
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.listeners = {};
  }

  addEventListener(type, listener) {
    if (!this.listeners[type]) {
      this.listeners[type] = [];
    }
    this.listeners[type].push(listener);
  }

  // イベントを発生させるときに、イベントを表すEventオブジェクトをいっしょに渡せるようにする
  // Eventオブジェクトには好きなデータを入れることができるので、
  // イベントを発生させたのは誰(どのオブジェクトか)などの情報を
  // イベントリスナーに渡すために使うことができる
  dispatchEvent(type, event) {
    const listeners = this.listeners[type] || [];
    listeners.forEach(listener => listener(event));
  }

  destroyIfContains(x, y) {
    if (x > this.x && x < this.x + 32 && y > this.y && y < this.y + 32) {
      // destroyイベントが発生したことをイベントリスナーに伝える
      // その際、自分自身(つまり、クリックして倒されたEnemyオブジェクト)を
      // Eventオブジェクトに入れて、イベントリスナーに渡す
      this.dispatchEvent("destroy", new GameEvent(this));
    }
  }

  update(input) {
  }

  render(context) {
    context.fillStyle = "rgb(255, 127, 127)";
    context.fillRect(this.x, this.y, 32, 32);
  }
}

const label = new ScoreLabel();
let enemies = [];

const canvas = document.getElementById("canvas");
const context = canvas.getContext("2d");

canvas.addEventListener("click", (e) => {
  const rect = e.target.getBoundingClientRect();
  const x = e.clientX - rect.left - 1;
  const y = e.clientY - rect.top - 1;
  enemies.forEach(enemy => enemy.destroyIfContains(x, y));
});

const FPS = 60;
const frameTime = 1 / FPS;
let prevTimestamp = 0;
let tick = 0;

const update = (timestamp) => {

  const elapsed = (timestamp - prevTimestamp) / 1000;
  if (elapsed <= frameTime) {
    requestAnimationFrame(update);
    return;
  }
  prevTimestamp = timestamp;
  tick++;

  // 1秒に1回、あたらしいEnemyオブジェクトをふやしてenemies配列に入れる
  if (tick % FPS === 0) {

    const x = Math.floor(Math.random() * (320 - 32));
    const y = Math.floor(Math.random() * (320 - 32));
    const enemy = new Enemy(x, y);

    // 敵を破壊したらスコアを10増やし、
    // 破壊されたEnemyオブジェクトはenemiesから削除する
    // eはdispatchEventを呼び出したときに渡したEventオブジェクトで、
    // 中(e.data)には破壊されたEnemyオブジェクト自身が入っている
    enemy.addEventListener("destroy", (e) => {
      label.addScore(10);
      enemies = enemies.filter(enemy => enemy !== e.data);
    });

    enemies.push(enemy);
  }

  enemies.forEach(enemy => enemy.update());
  label.update();

  context.clearRect(0, 0, 320, 320);
  enemies.forEach(enemy => enemy.render(context));
  label.render(context);

  requestAnimationFrame(update);
};

update();

解説

配列enemiesを使って四角形(enemy)を管理します。112行目のfilterは、配列の要素のうちで条件に合う要素だけを新しい配列にします。
キャンバスがクリックされたとき、配列の四角形すべてに当たりか判定をします(77~82行目)。当たり判定のでた(クリックされた)四角形のデータはイベントリスナーに渡されます(53~60行目)。配列enemiesのうち、渡された(=クリックされた)四角形(enemy)以外が新しい配列enemiesになります(110~113行目)。