【JavaScript】オブジェクトにバネ運動させる

オブジェクトにバネ運動をさせるスクリプト。Canvas上をクリックすると、左上から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._alpha = 1.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.globalAlpha = this._alpha;
		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;
	}

	get alpha() {
		return this._alpha;
	}
	set alpha(alpha) {
		this._alpha = alpha;
	}
}
スポンサーリンク

次に、画像にバネの動きをさせるSpring1クラス。

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

/**
 * バネ運動(spring1.js)
 */
export class Spring {
	constructor() {
		this._cvs = document.getElementById('canvas');
		this._ctx = this._cvs.getContext('2d');
		this._cat;
		this._spring = 0.03;	// ①バネの力
		this._friction = 0.95;	// ②摩擦
		this._targetX = this._cvs.width / 2;
		this._targetY = this._cvs.height / 2;
		this._vx = 0;
		this._vy = 0;
		this._cvs.addEventListener('mousedown', this._onMouseDown.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._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);

			// ③目標点から現在のX、Y座標を引き、縦横の移動する距離を計算
			let dx = this._targetX - this._cat.x;
			let dy = this._targetY - this._cat.y;
			// ④距離にバネの値をかけて加速度を計算
			let ax = dx * this._spring;
			let ay = dy * this._spring;
			// ⑤速度に加速度を加算
			this._vx += ax;
			this._vy += ay;
			// ⑥摩擦を適用
			this._vx *= this._friction;
			this._vy *= this._friction;
			this._cat.x += this._vx;
			this._cat.y += this._vy;
			this._cat.draw();

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

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

	_onMouseDown(e) {
		this._cat.x = this._cat.y = 0;
	}
}

①はバネの力の値、②は摩擦の値。摩擦を適用しないと、猫画像はCanvasの中央で揺れ続けて止まらない。

③〜⑥はコメントの通り。

目標点を移動させると、よりバネ運動の効果がはっきり分かる。以下はマウスカーソルを追いかける動きにバネ運動を適用した例。

コード(Spring2クラス)は以下。

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

/**
 * カーソルを追いかけるバネ運動(spring2.js)
 */
export class Spring {
	constructor() {
		this._cvs = document.getElementById('canvas');
		this._ctx = this._cvs.getContext('2d');
		this._cat;
		this._spring = 0.03;		// バネの力
		this._friction = 0.95;	// 摩擦
		this._vx = 0;
		this._vy = 0;
		this._mouseX = 0;
		this._mouseY = 0;
		this._cvs.addEventListener('mousemove', this._onMouseMove.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._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);

			// マウスカーソルの位置から現在のX、Y座標を引き移動する距離を計算
			let dx = this._mouseX - this._cat.x;
			let dy = this._mouseY - this._cat.y;
			let ax = dx * this._spring;
			let ay = dy * this._spring;
			this._vx += ax;
			this._vy += ay;
			this._vx *= this._friction;
			this._vy *= this._friction;
			this._cat.x += this._vx;
			this._cat.y += this._vy;
			this._cat.draw();

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

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

	_onMouseMove(e) {
		this._mouseX = e.clientX;
		this._mouseY = e.clientY;
	}
}

先程はCanvasの中央を(this._targetX、this._targetY)指定したが、代わりにthis._mouseX、this._mouseYを宣言。mousemoveイベントでカーソルが動く度に新たなX、Y座標を代入して、猫画像にカーソルを追いかけさせている。重力などを適用すると、夜店で売っている水ヨーヨーみたいな動きを再現できる。

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

コメント

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