イージング(比例運動)とは、出発点から目標点に近づくにつれ、徐々に遅くなって止まるような動き。出発から到着まで同じ速度の等速運動と違い自然な動き見える。
下のスクリプトは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; } }
次に、画像をイージングをかけて移動させるEasingクラス。
import { Cat } from "./cat.js"; /** * オブジェクトをイージングさせる(easing.js) */ export class Easing { constructor() { this._cvs = document.getElementById('canvas'); this._ctx = this._cvs.getContext('2d'); this._cat; this._easing = 0.2; // ①イージングに使う値 this._targetX = this._cvs.width / 2; // ②画像を向かわせるX、Y座標 this._targetY = this._cvs.height / 2; this._isMouseDown = 0; // クリック中か判断するフラグ変数 this._cvs.addEventListener('mousedown', this._onMouseDown.bind(this)); this._onMouseUp = this._onMouseUp.bind(this); this._onMouseMove = 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); // ③イージング処理部分 if (!this._isMouseDown) { let dx = this._targetX - this._cat.x; let dy = this._targetY - this._cat.y; // ④1ピクセル以内まで近づいたら到着と判断し停止させる if (Math.sqrt(dx * dx + dy * dy) < 1) { this._cat.x = this._targetX; this._cat.y = this._targetY; } else { this._cat.x += dx * this._easing; this._cat.y += dy * this._easing; } } 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._isMouseDown = 1 - this._isMouseDown; this._cvs.addEventListener('mouseup', this._onMouseUp); this._cvs.addEventListener('mousemove', this._onMouseMove); } _onMouseUp(e) { this._isMouseDown = 1 - this._isMouseDown; this._cvs.removeEventListener('mouseup', this._onMouseUp); this._cvs.removeEventListener('mousemove', this._onMouseMove); } _onMouseMove(e) { this._cat.x = e.clientX; this._cat.y = e.clientY; } }
①イージングの値を設定。
②目標点を設定。今回はCanvasの中央。
③画像にイージング処理を施した速度で移動させている。処理の流れは以下。
- 画像から目標点までの距離を計算
- 距離にイージングの値をかけて速度を求める
- 速度の値を現在の位置に加算
- 画像が到着するまで1〜3を繰り返す
④では、画像と目標点の距離が分かった時点で、1ピクセル以内に近づいていたら、画像のX,Y座標に目標点のX、Y座標を代入して移動を停止。
今回の目標点はCanvas(400×400)の中央の200ピクセル。イージングの値は小数だから、かけ続けても決して200に達することがない。最も近づいても199.99999999999994まで。これだと移動処理が果てしなく続くため、停止処理を追加している。
Math.sqrt(dx * dx + dy * dy)は三平方の定理で距離を計算している。
イージングは移動だけではなく、透明度の値の変更にイージングをかけることでフェードイン・フェードアウトの効果をつけたり、回転で使用したりと応用の幅が広い。
requestAnimationFrameを使ったゲームループの実装は、「【JavaScript】requestAnimationFrameでゲームループを作る」を参照。
リンク
リンク
コメント