setTimeout() 関数を使って、p5.js でストップモーションを実現

2022年5月29日日曜日

p5.js アニメーション テクニック

t f B! P L

p5.js でストップモーションのアニメーション作品を作ってみました。短いコードで簡単に作れる手法になりましたので、紹介いたします。

👉 Read this article in English.

JavaScript の setTimeout() 関数

今回のコードのポイントは、JavaScript の setTimeout() 関数です。

 

setTimeout() 関数とは?

setTimeout() はタイマーを仕掛けられる関数です。セットした時間が過ぎたタイミングで指定した関数を実行するという動作をします。p5.js 固有の関数ではなく、JavaScript の関数です。

引数は主に 2つ。

setTimeout(関数, 時間);

「関数」はタイマーが切れた際に実行する関数、「時間」はタイマーにセットする時間(ミリ秒)です。

 

setTimeout() の動作

setTimeout() を使ったプログラムの動作には若干わかりづらい点があります。setTimeout()で 100ミリ秒のタイマーをセットしたとして、プログラムはその 100ミリ秒の間どうなるんでしょう?止まって時間が経つのを待ってるんでしょうか?

プログラムのコードは、通常順番に実行されていきます。下記のコードだと、x の計算、y の計算が行われ、その後に circle() が実行されます。


  let x = radius * cos(t);
  let y = radius * sin(t);
  circle(x, y, 10);

このコードの頭に setTimeout() を入れた場合、まずタイマーがセットされ、その後処理は止まることなく x の計算、y の計算と進んでいきます。


  setTimeout(関数A, 時間);
  let x = radius * cos(t);
  let y = radius * sin(t);
  circle(x, y, 10);

処理が進む間も時間はカウントされ続け、タイマーが切れたタイミングで「関数A」の実行が始まります。これが setTimeout() の動作です。

この動作を使って、p5.js の draw() を使わずにアニメーションさせることもできます。下記のコード、どんなアニメーションになると思います?


function setup() {
  createCanvas(640, 640);
  noLoop();

  setTimeout(drawCircle, 3000, 64);
  setTimeout(brightBg, 2000);
  setTimeout(drawCircle, 1000, 255);

  background(0);
  rect(0, 0, 200, 200);
}

function brightBg() {
  background(240, 128);
}

function drawCircle(c) {
  fill(c);
  circle(width * 0.5, height * 0.5, 200);
}


 

非同期関数

setTimeout() はいわゆる「非同期関数」というもので、これを使うと順番に一つづつ処理が進むのではなく、複数の処理が同時に進むことになります。

非同期関数は setTimeout() だけではなく、例えば p5.js の loadImage() も非同期関数の一つです。

使ったことのある方は経験あるかもしれませんが、setup() や draw() で loadImage() を使うと、画像の読み込みが終わる前に画像を使おうとして「オブジェクト無いよ!」と怒られることがあります。それは loadImage() が非同期関数で、画像ファイルを読み込んでいる最中も、コードの実行は並行してどんどん先に進んでしまっているからです。「loadImage() は preload() 中に書きましょう」というお約束があるのは、そういう事態を防ぐためなんですね。

setTimeout() についてより詳しく知りたい場合はこちらをご覧ください。

setTimeout() | MDN

 

p5.js でストップモーション

setTimeout() を使って、p5.js でストップモーションのアニメーション作品を作るアイディアを紹介します。

 

普通の p5.js アニメーション作品

まずはストップモーションを導入する前のアニメーション作品をご覧ください。

これは円が一定速度で回転しているだけのアニメーションです。バラバラに動いているように見える円が、ある瞬間に重なって整列するのが見どころで、この瞬間を setTimeout() を使って一時停止させてみたいと思います。

 

setTimeout() 、noLoop()、loop() でストップモーション

ストップモーションのエッセンスはこのコードです。


setTimeout(loop, 1000);
noLoop();

1000ミリ秒後に loop() を動作させるタイマーを仕掛けてから noLoop() することで、1000ミリ秒間のストップモーションを実現させます。

 

p5.js ストップモーションのサンプルコード

setTimeout()、noLoop()、loop() を使って、前述の普通の p5.js アニメーション作品をストップモーション入りのアニメーション作品にしたサンプルコードがこちらです。CC0 で公開します。



/**
 * p5.js stop-motion with setTimeout().
 *
 * @author @deconbatch
 * @version 0.1
 * @license CC0 https://creativecommons.org/publicdomain/zero/1.0/
 * p5.js 1.1.3
 * created 2022.05.29
 */

const w = 740;
const h = w;
const arms = 2;     // arm number
const nodes = 32;   // node number on arm
const armLen = 0.4; // arm length
const speed = 0.02;

let stopNum;
let timer;

function setup() {
  createCanvas(w, h);
  frameRate(30);
  noFill();
  stroke(32);
  strokeWeight(2);

  stopNum = 1;
}

function draw() {

  const time = frameCount * speed;
  
  background(224);
  translate(w * 0.5, h * 0.5);
  for (let node = 0; node < nodes; node++) {
    let nRatio = map(node, 0, nodes, 0.0, 1.0);
    let nSize = nRatio * w * 0.1;
    let radius = w * armLen * nRatio;
    for (let arm = 0; arm < arms; arm++) {
      let theta = TWO_PI * (arm + nRatio) / arms * time;
      let x = radius * cos(theta);
      let y = radius * sin(theta);
      circle(x, y, nSize);
    }
  }

  // stop-motion
  if (time >= stopNum) {
    clearTimeout(timer);
    timer = setTimeout(reLoop, 1000);
    noLoop();
  }

}

function reLoop() {
  stopNum++;
  loop();
}

 

よかったら、何か作ってみてくださいね。

JavaScript の setTimeout() 関数を使ってストップモーションのアニメーションを作ってみました。ストップさせる条件を工夫したり、動作再開時に変化を加えたり、いろんなアイディアを盛り込めると思います。応用してなにか作品を作ってみていただけると嬉しいです。

 

QooQ