Invader 03
Projectile クラスを作ろう
Projectile
とは?
Projectile
とはPlayer
が打つ弾のことです。
準備
- フォルダの中に
src
フォルダを作成し、その中にProjectile.js
ファイルを作成しましょう。 index.js
ファイルの一番上に、Projectile
のimport
を追加しましょう。- グローバル変数に
projectile
を入れておく配列(Array)を作成しましょう。
import { Player } from "./src/Player.js"
import { Projectile } from "./src/Projectile.js" // NEW!!
...
// -------------------------------------------
// VARIABLES
// -------------------------------------------
// ゲームで使われる変数をここで設定
globalThis.player = new Player()
// Projectileクラスを入れておく配列
globalThis.projectiles = [] // NEW!!
Projectile.js
のポイント
constructor
の理解
constructor
メソッドに引数として、オブジェクトを設定しましょう。
インスタンスを作成する例
const projectile = new Projectile({
position: {
x: 50,
y: 100,
},
velocity: {
x: 4,
y: 0,
},
color: "#38a3a5",
})
draw()
メソッドの円を描画するコードを確認
globalThis.ctx.beginPath()
で円を描き始めます。globalThis.ctx.arc()
で弧を描画し始めます。globalThis.ctx.closePath()
で弧を一周した後に円を閉じます。
update()
メソッドにProjectile
の位置を示す便利な値を設定
center
,top
,bottom
,left
,right
は後でProjectile
インスタンスに対してコードを書く時に便利になるから設定をしておきます。- 例えば円の右側を数値がほしいときに、毎回
this.position.x + this.radius
を書くよりはthis.right
と書くほうが楽です。
// -------------------------------------------
// PROJECTILE CLASS
// -------------------------------------------
// Playerが打つ弾
export class Projectile {
// コンストラクタ:Playerインスタンスをはじめて作成される時に呼び出される
// インスタンスを作成する時(new ??)、渡す引数
// Object形式で引数を渡していることに注意
// ex. {position: {x: 1, y: 2}}
constructor({ position, velocity, color = "#38a3a5" }) {
this.position = position
// velocityはフレームごとに動く距離
this.velocity = velocity
// 半径を設定
this.radius = 4
this.color = color
}
// DRAW METHOD -------------------------
// Classをどのように画面上に描かれるかを定義する
draw() {
// projectileの円を描くためのコード
globalThis.ctx.beginPath()
globalThis.ctx.arc(
this.position.x,
this.position.y,
this.radius,
0,
Math.PI * 2
)
globalThis.ctx.fillStyle = this.color
globalThis.ctx.fill()
globalThis.ctx.closePath()
}
// UPDATE METHOD -------------------------
// draw()を呼び、1フレームごとにどのように数値が変化するかを設定するメソッド
update() {
this.draw()
this.position.x += this.velocity.x
this.position.y += this.velocity.y
// positionの変化に合わせて数値を更新
// 真ん中の位置(x,y)
this.center = {
x: this.position.x,
y: this.position.y,
}
// top, bottom, left, rightの位置を設定・更新
this.top = this.position.y - this.radius
this.bottom = this.position.y + this.radius
this.left = this.position.x - this.radius
this.right = this.position.x + this.radius
}
}
index.js
のポイント
animate()
関数の中にProjectile
用のアニメーションコードを確認
for
ループがいつもと違うのに気がづきましたか?for
ループはいつもだったらfor (let i=0; i < globalThis.projectiles.length; i++)
と書きますが、今回は違います。ここでは配列の後ろにあるアイテムから前に向かってループをしています。なぜこれをやるかはアニメーションループの最中、配列の中のアイテム(例Projectile
)を消すことがあります。この時、前からループしていると、コンピューターが配列の順番を間違えてしまいます。これを防ぐために後ろからループします。こうすることで、ループ途中でアイテムを削除をしても、プログラムに影響がでません。- 衝突判定をしているコードを確認しましょう。
projectile
のbottom
が 0 以下になったら(つまり画面の上部から出た場合)projectile
を消します。 globalThis.projectiles.splice(i, 1)
はi
の位置にあるprojectile
を 1 つ消せというコードです。- 衝突判定がない場合は、それぞれの
projectile
インスタンスのupdate()
メソッドを実行します。
function animate() {
...
// NEW!!
// Projectile配列のアニメーション -------------------------
for (let i = globalThis.projectiles.length - 1; i >= 0; i--) {
const projectile = globalThis.projectiles[i]
// ProjectileとBombが衝突した場合
// TODO
// projectileがpowerUpに衝突した場合
// TODO
// Projectileが画面上部から消えたら
if (projectile.bottom <= 0) {
// Projectileを削除
globalThis.projectiles.splice(i, 1)
} else {
projectile.update()
}
}
...
}
addEventListener("keydown", ...)
のスペースキーの挙動を確認
- スペースキーを押した時に新しい
Projectile
インスタンスを作成しましょう。(new Projectile(...)
)
// 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
// NEW!!
globalThis.projectiles.push(
new Projectile({
position: {
x: globalThis.player.center.x,
y: globalThis.player.top,
},
velocity: {
x: 0,
y: -10,
},
})
)
break
}
}
})
確認
space
キーを打つと弾(Projectile
)が飛ぶことを確認しましょう。