p5.jsでイージングを使ってみる


2018年05月20日
With
p5.jsでイージングを使ってみる はコメントを受け付けていません。

最近スケッチの動きにバリエーションを出したくて、先日はp5.jsでのベクトルの使い方をちょっと勉強してみました。

p5.jsのベクトルで加速度や引力を扱ってみる
p5.jsでミニゲーム「メテオロイド」を作ってみた

ベクトルのほかに使ってみたかった手法に「イージング」というのがあります。Web系やゲーム系のフロントエンド開発などではおなじみですよね。オブジェクトを動かすときに、動きはじめと終わりの間で移動速度を変化させて、スムーズな動きを演出する方法です。

イージングの基本 | Web | Google Developers

実は太古の昔に趣味でFlash(現Adobe Animate CC)を使っていたことがあるのですが、その時はとても簡単にイージングが使えていました。Flashのタイムライン上でアニメーションの開始と終了のフレーム、オブジェクト位置を指定して、その間に「トゥイーン」(恐らくFlash用語。betweenに由来?)を作成。さらにその「トゥイーン」に「イージング」を適用するという操作が、全部GUIからできたんですよね。いやー、Flashっていいツールだったよなー。

FlashオルタナティブなJSライブラリ群として登場した「CreateJS」にも、「TweenJS」というライブラリがあり、その中に含まれる「Ease」というクラスでFlashライクなイージングの手段を提供しているようです。

TweenJS v1.0.0 API Documentation : TweenJS

イージングを実現するのであればp5.jsとTweenJSを両方使うというのもありなのですが、もうちょっとシンプルにやれる方法はないかとググっていたところ、Github Gistにこんなファイルを公開されている方がいらっしゃいました。

Simple Easing Functions in Javascript

コードを見てみると、連想配列のキーになっている「linear」「easeInQuad」などがイージングのタイプ。バリューはそれぞれ「t」(time)を引数にとる無名関数となっています。引数として「0~1」の数値(動き始めが0で目的地到達時が1)を与えてやると、その時点での位置を「0~1」(動き始めの位置が0で目的地が1)を返してくれるようです。つまり、動かしたいオブジェクトについて

・動き始めの座標
・目的地の座標
・動き始めた時間
・動き始めてから目的地に到達するまでの目標時間
・動き始めてから現在までの時間(到達目標時間との比率を0~1で)

といった値を持っておけば、ここにある関数を使って、動きのイージングができそうです。

…で、いろいろゴチャゴチャやりながら作ってみたのが次のスケッチです。

●ニセ星座ジェネレーター - easeOutQuint版(クリックすると別タブが開きます)

スタートすると、65個のドットが中央から飛び出して2500ミリ秒でそれぞれの目的地に「easeOutQuint」のイージングで移動します。初速速めで、目的地に近づくにつれて「スッ」とスローダウンする感じの動きになっているのが分かっていただけるかと。

移動完了後は同時間の休憩を挟んで、次の目的地へ移動…というのを延々と繰り返します。また、カンバス上のどこかをマウスクリックすると、すべてのドットの目的地がマウス位置に設定され、一斉に集合してくるというおまけを入れてみました。

で、まぁ、それだけだとちょっと面白くなかったので、各ドットの位置がある程度近づいたときに(別のドットの中心が、自分の中心から直径の4倍以内の位置にあるとき)自分と相手の中心を線で結ぶという処理も入れてみました。休憩時の線の結ばれ方が、星座盤のようにも見えるので「ニセ星座ジェネレーター」と名付けてみました。コードはこんな感じになっています。

const easeOutQuint = function (t) { return 1 + (--t) * t * t * t * t };

const moveLimit = 2500;
const dotCount = 65;
const dots = [];

function setup() {
	createCanvas(600, 600);
	colorMode(HSB);
	for (let i = 0; i < dotCount; i++) {
		dots[i] = new Point();
	}
}

function draw() {
	blendMode(BLEND);
	background(240, 80, 8, .3);
	blendMode(SCREEN);
	dots.forEach(dot => {
		dot.updateMe();
		dot.drawMe();
	});
	drawLine();
}

class Point {
	constructor() {
		this.x = width / 2;
		this.y = height / 2;
		this.distX;
		this.distY;
		this.distSet();
		this.preX = this.x;
		this.preY = this.y;
		this.startTime = Date.now();
		this.moveTime = 0;
		this.stopTime = 0;
		this.isMove = false;
		this.rad = random(5, 22);
		this.col = random(15, 195);
	}

	updateMe() {
		if (this.isMove) {
			this.moveTime = (Date.now() - this.startTime) / moveLimit;
			this.x = this.preX + (this.distX - this.preX) * easeOutQuint(this.moveTime);
			this.y = this.preY + (this.distY - this.preY) * easeOutQuint(this.moveTime);
			if (Date.now() - this.startTime >= moveLimit) {
				this.isMove = false;
				this.stopTime = Date.now();
				this.preX = this.x;
				this.preY = this.y;
			}
		} else {
			if (Date.now() - this.stopTime > moveLimit) {
				this.isMove = true;
				this.startTime = Date.now();
				this.distSet();
			}
		}
	}

	drawMe() {
		fill(this.col, 60, 80, 1);
		noStroke();
		ellipse(this.x, this.y, this.rad);
	}

	distSet() {
		this.distX = random(0, width);
		this.distY = random(0, height);
	}
}

function drawLine() {
	strokeWeight(1);
	stroke(240, 20, 80, .5);
	for (let i = 0; i < dots.length; i++) {
		for (let j = i + 1; j < dots.length - 1; j++) {
			if (dist(dots[i].x, dots[i].y, dots[j].x, dots[j].y) < dots[i].rad * 4) {
				line(dots[i].x, dots[i].y, dots[j].x, dots[j].y);
			}
		}
	}
}

function mouseClicked() {
	dots.forEach(dot => {
		dot.preX = dot.x;
		dot.preY = dot.y;
		dot.isMove = true;
		dot.distX = mouseX;
		dot.distY = mouseY;
		dot.startTime = Date.now();
	});
}

1行目で「easeOutQuint」という定数に、先ほどのGistにあった式を割り当てており、x座標値とy座標値のアップデート時に関数として呼び出しています。この部分を変えてやることで他のイージングスタイルへの変更もカンタンです。

例えば、先ほどのGistでは、他の方がコメントとして「ElasticEasing」の式をアップされていました。1行目とアップデート部分の記述をちょっと変えてやると、こんな動きになります。

●ニセ星座ジェネレーター - easeInElastic版(クリックすると別タブが開きます)

ドットが目標地点にゴムで引っ張られて「ぼよよーん」と震えながら止まるような動きになっています。これはこれで面白いですね。

とりあえず、これで作りたい表現に合わせてイージングが使えそうです。なんとなく「時間までに決まった場所へ移動する」ことが重要な時には「イージング」が、「移動物体自体の持つスピードや加速度、他物体とのインタラクション」が重要なときには「ベクトル」が使いやすそうな気がします。あと、イージングの場合は移動だけでなく、サイズの変更などにも応用が利きそう。積極的に使っていきたいと思います。