logo

Invader 02

Player クラスを作ろう

準備

  • フォルダの中にsrcフォルダを作成し、その中にPlayer.jsファイルを作成しましょう。
  • フォルダの中にassetsフォルダを作成し、ships.pngを入れましょう。
  • index.jsファイルの一番上に下記のように書きましょう。
  • globalThis.player = new Player()ではグローバル変数にPlayerクラスのインスタンスを設定しています。
import { Player } from "./src/Player.js"

...

// -------------------------------------------
//  VARIABLES
// -------------------------------------------
// ゲームで使われる変数をここで設定
globalThis.player = new Player()

import, exportとは?

importとは、他のファイルからコードを読み込むことです。 exportとは、他のファイルにコードを読み込めるようにすることです。 import,exportとは新しいjavascriptの書き方です。 <script type="module">にし、サーバを立ち上げると使用できます。

globalThisとは?

globalThiswindowと同じようなグローバルオブジェクトです。グローバルオブジェクトというのは、プログラムのどこからでも呼び出せる変数のことです。この変数(オブジェクト)に様々な値を割り当てることができます。


index.jsのポイント

animate()関数の確認

  • animate()関数は 1 フレームごとに実行される関数です。
  • animate()関数の宣言の上にglobalThis.frames = 0でグローバル変数にframesが設定されていることを確認しましょう。これが現在のフレーム数を格納します。
  • 関数の中にglobalThis.requestAnimationFrame(animate)という行に注目をしましょう。これはanimate()関数の中に同じanimate関数を実行しています。つまり自分自身の中で自分を呼び出しています。これがゲームのループを表現しています。
  • animate()の中にplayerupdate()が呼び出され、1 フレームごとの変化を計算しています。
  • キーが押された時の挙動もここで管理をしています。
  • 関数の最後の行にglobalThis.frames++framesに 1 を足し、再び関数の上に戻って実行が始まります。

.addEventListener()の確認

  • addEventListenerで設定されているkeyup, keydownconsole.log()で表示されるのを確認しましょう。a, d, SPACEを押すとconsole.log()に表示されます。

index.js

//-------------------------------------------
// IMPORTS
//-------------------------------------------

import { Player } from "./src/Player.js"

// -------------------------------------------
//  SETUP CANVAS
// -------------------------------------------

// globalThisはどのファイルでも共有できるグローバル変数
// どのファイルでもその変数を使いたい時に追加する
// canvasとcは
globalThis.canvas = document.querySelector("canvas")
globalThis.ctx = globalThis.canvas.getContext("2d")

// ゲーム画面のサイズを固定
globalThis.canvas.width = 1024
globalThis.canvas.height = 576

// -------------------------------------------
//  VARIABLES
// -------------------------------------------
// ゲームで使われる変数をここで設定
globalThis.player = new Player()

// 各キーの押されている(pressed)状態を保管する
globalThis.keys = {
  a: {
    pressed: false,
  },
  d: {
    pressed: false,
  },
  space: {
    pressed: false,
  },
}

//  フレームを記録
globalThis.frames = 0

// -------------------------------------------
//  ANIMATE FUNCTION
// -------------------------------------------
// 1フレームごとに呼び出す関数・メソッドをここでまとめて呼ぶ
function animate() {
  // GAME LOOP -------------------------
  // ゲームをループする
  globalThis.requestAnimationFrame(animate)

  // 背景をblackに設定する(すべてを黒で塗りつぶす)
  globalThis.ctx.fillStyle = "black"
  globalThis.ctx.fillRect(
    0,
    0,
    globalThis.canvas.width,
    globalThis.canvas.height
  )

  // playerのupdateメソッドを呼び出す
  globalThis.player.update()

  // キーが押された場合の挙動 ------------------------------
  // Aが押された時
  if (globalThis.keys.a.pressed && globalThis.player.left >= 0) {
    globalThis.player.velocity.x = -7
    globalThis.player.rotation = -0.15
  } else if (
    // Dが押された時
    globalThis.keys.d.pressed &&
    globalThis.player.right <= globalThis.canvas.width
  ) {
    globalThis.player.rotation = 0.15
    globalThis.player.velocity.x = 7
  } else {
    // 何も押されていなければ各数値を0に戻す
    globalThis.player.rotation = 0
    globalThis.player.velocity.x = 0
  }

  // フレームを1増加
  globalThis.frames++
}

// アニメーションファンクションを走らせる
animate()

// -------------------------------------------
//  EVENT LISTENERS
// -------------------------------------------

//  KEYDOWN -------------------------
globalThis.addEventListener("keydown", ({ key }) => {
  switch (key) {
    case "a": {
      globalThis.keys.a.pressed = true
      break
    }
    case "d": {
      globalThis.keys.d.pressed = true
      break
    }
    case " ": {
      globalThis.keys.space.pressed = true
    }
  }
})

//  KEYUP -------------------------
globalThis.addEventListener("keyup", ({ key }) => {
  switch (key) {
    case "a": {
      globalThis.keys.a.pressed = false
      break
    }
    case "d": {
      globalThis.keys.d.pressed = false
      break
    }
    case " ": {
      globalThis.keys.space.pressed = false
      break
    }
  }
})

Player.jsのポイント

exportの理解

Player.jsファイルからは下記のPlayerクラスがexportされます。

export class Player {}

constructorの理解

  • classはあくまでも「青写真」です。クラスを実体化させるには new Player()とクラスのインスタンスを作成しなければなりません。インスタンスを作成するときに呼び出されるのがconstructorメソッドです。

new Image()

  • new Image()クラスのインスタンスを作成し、その中にassetsフォルダに入れた画像(ship.png)を読み込みます。
  • image.onloadで画像のロードが終わった時に実行するコードを書きます。

class Playerdraw()メソッド

  • draw()メソッドはcanvas上でどのように描画するかを表すコードです。

class Playerupdate()メソッド

  • update()メソッドは 1 フレームごとにどのように数値が変化するかを設定するメソッドです。
// -------------------------------------------
//  PLAYER CLASS
// -------------------------------------------
// Player(飛行機)
export class Player {
  // コンストラクタ:Playerインスタンスをはじめて作成される時に呼び出される
  constructor() {
    // velocityはフレームごとに動く距離
    this.velocity = {
      x: 0,
      y: 0,
    }

    // 飛行機の傾きの数値
    this.rotation = 0
    // 透明度を設定(0になると透明)
    this.opacity = 1

    // new Image()で画像を入れる「箱」を用意し、assetsフォルダにあるship.pngをImageインスタンスの.src属性に設定する
    const image = new Image()
    image.src = "./assets/ship.png"

    // Imageインスタンスに画像が読み込まれた(loadされた)後に呼ぶファンクション
    image.onload = () => {
      const scale = 1.15
      this.image = image

      // scale変数をもとにimageのサイズを変更する
      this.width = image.width * scale
      this.height = image.height * scale

      // canvasの横真ん中と画面の下の方に置く
      this.position = {
        x: globalThis.canvas.width / 2 - this.width / 2,
        y: globalThis.canvas.height - this.height - 20,
      }
    }
  }

  //  DRAW METHOD -------------------------
  // Classをどのように画面上に描かれるかを定義する
  draw() {
    // globalThis.ctx.fillStyle = "red"
    // globalThis.ctx.fillRect(this.position.x, this.position.y, this.width, this.height)

    // playerの傾き(rotate)を設定するためのコード。まずはもとの状態を保存する
    globalThis.ctx.save()

    // playerのopacity(透明度)を設定
    globalThis.ctx.globalAlpha = this.opacity

    // 回転の起点をplayerの真ん中の位置に設定する
    globalThis.ctx.translate(
      this.position.x + this.width / 2,
      this.position.y + this.height / 2
    )

    // this.rotationで設定した傾き分を回転させる
    globalThis.ctx.rotate(this.rotation)

    // 回転の起点をplayerからもとにあった場所に戻す
    globalThis.ctx.translate(
      -this.position.x - this.width / 2,
      -this.position.y - this.height / 2
    )

    // 画像を描く
    globalThis.ctx.drawImage(
      this.image,
      this.position.x,
      this.position.y,
      this.width,
      this.height
    )

    // 最初に保存したもとの状態に戻す
    globalThis.ctx.restore()
  }

  //  UPDATE METHOD -------------------------
  // draw()を呼び、1フレームごとにどのように数値が変化するかを設定するメソッド
  update() {
    // this.imageがある場合のみ描く(draw)する
    if (!this.image) return

    // x位置に常にvelocity.xを足す
    this.draw()
    this.position.x += this.velocity.x

    // positionの変化に合わせて数値を更新
    // 真ん中の位置(x,y)
    this.center = {
      x: this.position.x + this.width / 2,
      y: this.position.y + this.height / 2,
    }

    // top, bottom, left, rightの位置を設定・更新
    this.top = this.position.y
    this.bottom = this.position.y + this.height
    this.left = this.position.x
    this.right = this.position.x + this.width

    // 透明度が1出ない場合はここでreturnをする(return以下のコードを実行しない)
    if (this.opacity !== 1) return
  }
}

確認

  • aで飛行機が左に、dを押すと飛行機が右に移動することを確認しましょう。
  • 飛行機が移動方向に合わせて傾くこと(rotation)を確認しましょう。

pic1

Invader 02 | LET'S PROG