JavaScript»ゲーム制作»あたり判定 戦闘機と隕石のあたり判定(すこしあたりにくくする)

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

下のソースコードをうつして、十字キーで戦闘機をうごかし、隕石とぶつかったときに爆発するプログラムを作ってください。

これは「戦闘機と隕石のあたり判定」にすこし手を加えて、あたりかどうかを判定する領域を狭くしたプログラムです。

画像がぞうは下のリンクからダウンロードできます。

画像をダウンロード

ソースコード

class GameEvent {
  constructor(data) {
    this.data = data;
  }
}

class Rect {
  constructor(x, y, w, h) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
  }
}

class Actor {

  constructor(x, y, w, h, hitArea) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.hitArea = hitArea;
    this.listeners = {};
  }

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

  dispatchEvent(type, e) {
    const listeners = this.listeners[type] || [];
    listeners.forEach(listener => listener(e));
  }

  isHit(other) {
    if (!this.hitArea || !other.hitArea) {
      return false;
    }
    return this.x + this.hitArea.x < other.x + other.hitArea.x + other.hitArea.w &&
      other.x + other.hitArea.x < this.x + this.hitArea.x + this.hitArea.w &&
      this.y + this.hitArea.y < other.y + other.hitArea.y + other.hitArea.h &&
      other.y + other.hitArea.y < this.y + this.hitArea.y + this.hitArea.h;
  }
}

class Fighter extends Actor {

  constructor(image, x, y) {
    super(x, y, 32, 32, new Rect(4, 4, 24, 24));
    this.image = image;
    this.destroyed = false;
    this.addEventListener("hit", e => {
      this.destroyed = true;
    });
  }

  update(input) {
    if (this.destroyed) {
      return;
    }
    if (input["ArrowUp"]) this.y -= 3;
    if (input["ArrowRight"]) this.x += 3;
    if (input["ArrowDown"]) this.y += 3;
    if (input["ArrowLeft"]) this.x -= 3;
  }

  render(context) {
    if (this.destroyed) {
      context.drawImage(this.image, 580, 661, 48, 46, this.x, this.y, this.w, this.h);
    } else {
      context.drawImage(this.image, 325, 0, 98, 75, this.x, this.y, this.w, this.h);
    }
  }
}

class Meteor extends Actor {

  constructor(image, x, y) {
    super(x, y, 32, 32, new Rect(4, 4, 24, 24));
    this.image = image;
  }

  update(input) {
  }

  render(context) {
    context.drawImage(this.image, 651, 447, 43, 43, this.x, this.y, this.w, this.h);
  }
}

const image = new Image();
image.src = "spaceshooter.png";
image.addEventListener("load", (e) => {

  const fighter = new Fighter(image, 144, 144);
  const meteors = [];
  for (let i = 0; i < 10; i++) {
    const meteorX = Math.floor(Math.random() * 320);
    const meteorY = Math.floor(Math.random() * 320);
    meteors.push(new Meteor(image, meteorX, meteorY));
  }

  const input = {};
  addEventListener("keydown", e => input[e.key] = true);
  addEventListener("keyup", e => input[e.key] = false);


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

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

  const update = (timestamp) => {

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

    prevTimestamp = timestamp;

    fighter.update(input);
    meteors.forEach(meteor => meteor.update(input));

    // あたり判定
    // プレイヤー機にあたっている隕石が見つかったら、hitイベント発生
    const meteor = meteors.find(meteor => fighter.isHit(meteor));
    if (meteor) {
      fighter.dispatchEvent("hit", new GameEvent(meteor));
    }

    context.fillStyle = "#000";
    context.fillRect(0, 0, 320, 320);
    fighter.render(context);
    meteors.forEach(meteor => meteor.render(context));

    requestAnimationFrame(update);
  };

  update();
});

解説

hitAreaを追加して、当たり判定を内側に4マス小さくしました。53行目と83行目のnew Rectのカッコの中の数字を変更すると、あたり判定をさらに小さくすることもできます。