【JavaScript】ボールを壁で跳ね返させる【ゲーム制作】

ボール(といっても猫だが)をCanvas内で跳ね返らせる。矢印キーで操作し、壁に衝突すると反射する。

別ページで表示

まず、猫画像を表示するCatクラス。

/**
 * 猫画像を表示(cat.js)
 */
export class Cat {
	constructor(parent = undefined, x = 0, y = 0) {
		if (parent !== undefined) {
			this._parent = parent;
		}
		this._x = x;
		this._y = y;
		this._rotation = 0;
		this._img;
		
		this._init();
	}

	//////////////////////////////////
	// Private and protected
	//////////////////////////////////

	_init() {
		if (this._parent !== undefined) {
			this._ctx = this._parent;
		}
		this._img = new Image();
		this._img.src = "./cat.png";
		this.draw();
	}

	//////////////////////////////////
	// Public
	//////////////////////////////////

	draw() {
		// 現在の描画状態を保存
		this._ctx.save();
		// コンテキストの座標を変更し、キャンバス中央に移動
		this._ctx.translate(this._x, this._y);
		// コンテキストの角度をラジアン値で指定
		this._ctx.rotate(this._rotation);
		this._ctx.drawImage(this._img, -(this._img.width / 2), -(this._img.height / 2));
		// save()で保存した描画状態を復元
		this._ctx.restore();
	}

	//////////////////////////////////
	// Getters/Setters
	//////////////////////////////////

	get x() {
		return this._x;
	}
	set x(x) {
		this._x = x;
	}

	get y() {
		return this._y;
	}
	set y(y) {
		this._y = y;
	}

	get width() {
		return this._img.width;
	}

	get height() {
		return this._img.height;
	}

	get rotation() {
		return this._rotation;
	}
	set rotation(rotation) {
		this._rotation = rotation;
	}
}

次に、移動と跳ね返りを処理するBoundクラス。

import { Cat } from "./cat.js";

/**
 * 跳ね返りをシミュレートする(bound.js)
 */
export class Bound {
	constructor() {
		this._cvs = document.getElementById('canvas');
		this._ctx = this._cvs.getContext('2d');
                this._leftEnd = 0;                  // キャンバス左端
                this._rightEnd = this._cvs.width;   // キャンバス右端
                this._topEnd = 0;                   // キャンバス上端
                this._bottomEnd = this._cvs.height; // キャンバス下端
		this._cat;
		this._vx = 0;
		this._vy = 0;
		this._ax = 0;
		this._ay = 0;
		this._cvs.setAttribute('tabindex', 0);
		this._cvs.addEventListener('keydown', this._onKeydown.bind(this));
		this._cvs.addEventListener('keyup', this._onKeyUp.bind(this));
		// タイマー関連
		this._animID;
		this._isAnim = 0;
		this._FPS = 60;
		this._frame = 0;
		this._startTime;
		this._nowTime;
		
		this._init();
	}

	//////////////////////////////////
	// Private and protected
	//////////////////////////////////

	_init() {
		this._cvs.style.backgroundColor = "#eeeeee";
		this._cat = new Cat(this._ctx, this._cvs.width / 2, this._cvs.height / 2);

		this._startTime = performance.now();
		this._mainLoop();
	}

	_mainLoop() {
		this._nowTime = performance.now();
		let elapsedTime = this._nowTime - this._startTime;
		let idealTime = this._frame * (1000 / this._FPS);
		if (idealTime < elapsedTime) {
                        this._ctx.clearRect(0, 0, this._cvs.width, this._cvs.height);
                        this._vx += this._ax;
                        this._vy += this._ay;
                        this._cat.x += this._vx;
                        this._cat.y += this._vy;

                        // ①画面の境界に触れたら跳ね返す処理
                        if (this._cat.x + this._cat.width / 2 > this._rightEnd) {
				// ②画像を壁ピッタリの位置に移動
				this._cat.x = this._rightEnd - this._cat.width / 2;
				// ③速度の値を反転させる
				this._vx *= -1;
			} else if (this._cat.x - this._cat.width / 2 < this._leftEnd) {
                                this._cat.x = this._leftEnd + this._cat.width / 2;
                                this._vx *= -1;
                        }
                        if (this._cat.y + this._cat.height / 2 > this._bottomEnd) {
				this._cat.y = this._bottomEnd - this._cat.height / 2;
				this._vy *= -1;
			} else if (this._cat.y - this._cat.height / 2 < this._topEnd) {
                                this._cat.y = this._topEnd + this._cat.height / 2;
                                this._vy *= -1;
                        }

                        this._cat.draw();

                        this._frame++;

                        if (elapsedTime >= 1000) {
				this._startTime = this._nowTime;
				this._frame = 0;
			}
		}
	
		this._animID = requestAnimationFrame(this._mainLoop.bind(this));
	}

	//////////////////////////////////
	// Handlers
	//////////////////////////////////

	_onKeydown(e) {
		switch (e.keyCode) {
			case 37: // 左矢印キー
				this._ax = -0.2;
				break;
			case 39: // 右矢印キー
				this._ax = 0.2;
				break;
			case 38: // 上矢印キー
				this._ay = -0.2;
				break;
			case 40: // 下矢印キー
				this._ay = 0.2;
				break;
		}
	}

	_onKeyUp(e) {
		this._ax = this._ay = 0;
	}
}

Boundクラスは「【JavaScript】加速度を使った移動」のスクリプトを元に跳ね返り処理を追加しただけ。「【JavaScript】端から出たオブジェクトを反対側から出現させる」では画面端から反対端へ移動させる処理を追加したが、今回はその処理の部分を跳ね返りに書き換えただけとも言える。

①猫画像が画面端、つまり壁に当たった(超えた)か判定している。

②壁(境界)を超えた猫画像を壁ピッタリの位置に移動させている。これは単に壁を正しく跳ね返ったという視覚効果を狙っただけではない。壁に当たった(超えた)ということは、僅かながら画像がめり込んでいる状態だ。場合によっては次のフレームでもまだめり込んでいる状態のままかもしれず、再度、速度の反転が行われ壁に向かうのを防いでいる。

③跳ね返らせるには、速度を反転させればよい。左右の壁に当たったらX座標の速度(this._vx)を、上下の壁に当たったらY座標の速度(this._vy)を反転させる。反転は速度に-1をかければよいだけ。

requestAnimationFrameを使ったゲームループの実装は、「【JavaScript】requestAnimationFrameでゲームループを作る」を参照。

コメント

タイトルとURLをコピーしました