今回の内容
p5.js でループするアニメーションを作る際に、frameCount と剰余演算を組み合わせたものをよく使うのですが、それに関連した内容です。
直近で、以下のようなアニメーション(イージングを適用したループアニメーション)を作ろうとした中で、試してみていた内容です。
プログラムの内容など
プログラム例を紹介してから、少し補足を書いてみます。
実装1
まずは、1つ目のプログラム全体を掲載します。
function setup() {
createCanvas(550, 400);
noStroke();
fill(100, 120, 210);
}
function draw() {
background(220);
let f = frameCount % 300;
f = (() => {
if (f < 45) {
return 0;
}
if (f > 200) return 1;
return map(f, 45, 200, 0, 1);
})();
// console.log(f);
circle(lerp(0, width, easing(f)), height / 2, 80);
}
function easing(t) {
// https://easings.net/ja#easeInExpo
return t === 0 ? 0 : pow(2, 10 * t - 10);
}
ループする時間間隔は frameCount % 300
の部分の、300フレーム分毎の時間になります。
そして、その 300フレームの中では、45フレーム目まではゼロ、200フレームより大きいところは 1、その間では 0 から 1 までを線形増加する値を作っています。この値をもとに、lerp() と easing() を使ったイージングの実装を行っています。
上記の 0 にした部分と 1 にした部分は、イージングが適用された動きの開始時と終了時の、それぞれの位置に静止した状態になります。ここで、動きが止まる空白の時間を作っています。
イージングの関数について
イージングの関数の部分は、有名どころの以下を見て実装しました。
●イージング関数チートシート
https://easings.net/ja
実装2
上に書いていたものとは、別の実装を試したものが以下になります。
function setup() {
createCanvas(550, 400);
noStroke();
fill(100, 120, 210);
}
function draw() {
background(220);
let f = frameCount % 200;
f = f < 120 ? 0 : map(f, 120, 200, 0, 2);
f = constrain(f, 0, 1);
// console.log(f);
circle(lerp(0, width, easing(f)), height / 2, 80);
}
function easing(t) {
// https://easings.net/ja#easeInExpo
return t === 0 ? 0 : pow(2, 10 * t - 10);
}
上記では、200 フレームのループが行われる中で、120フレームより小さい部分は 0 の値にするようにしました。そして、それより大きい値のところは、0 から 2 まで線形増加するようにしつつも、constrain() を使って上限の値を 1 に制限してみました。
これにより、上記の線形増加している部分で、1 から 2 の間の部分が全て 1 の値になります。
この事例でも 1つ目の実装と同じく、動きのある区間の前後それぞれおで、動きのない空白の時間を作っています。
そして、2つ目の実装例でも、1つ目の実装例と同じようにイージングを適用しています。
おわりに
自分が p5.js のアニメーション実装でよく使う frameCount と剰余演算を使ったループアニメーションの処理に関する話を書いてみました。
実装方法は他にもいろいろあると思いますので、他の実装も試していければと思っています。
実装1 の即時実行関数式の部分について
1つ目の実装例について、即時実行関数式を使った部分があります。
ここでの書き方は、最近見かけた以下の記事を参考にしました。
ちなみに以下の記事の概要は、多段の入れ子の三項演算子・条件文を避ける、という方向の内容でした。
●Stop nesting ternaries in JavaScript | Sonar
https://www.sonarsource.com/blog/stop-nesting-ternaries-javascript/
// 関数
function animalName(pet) {
if (pet.canBark() && pet.isScary()) { return "wolf"; }
if (pet.canBark()) return "dog";
if (pet.canMeow()) return "cat";
return "probably a bunny";
}
// 即時実行関数式
const animalName = (() => {
if (pet.canBark() && pet.isScary()) { return "wolf"; }
if (pet.canBark()) return "dog";
if (pet.canMeow()) return "cat";
return "probably a bunny";
})();