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