オブジェクトにバネ運動をさせるスクリプト。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でゲームループを作る」を参照。
リンク
リンク
コメント