p5.js 関数リファレンスと作例:frameRate() & frameCount

2023年1月31日火曜日

p5.js リファレンス 初心者向け

t f B! P L

p5.js の様々な関数の使い方をおさらいして、作例コードを書くことで、自身の知識の定着を図っています。

今回は frameRate() 関数と、それに関係の深い frameCount 変数を取り上げます。説明やコードに誤り、勘違いがあるかもしれません。何かあれば、ご指摘をいただけると嬉しいです。

p5.js は、バージョン 1.5.0 を使用しています。

 

frameRate() 関数の意味と使い方

frameRate() | p5.js 公式リファレンス

説明

主に、一秒間に何枚のフレームを描画するかの指定を行うのに使います。現在の fps値(frames per second:一秒間あたりのフレーム数)を得ることにも使えます。

CPU が遅くて描画が追いつかない等で、frameRate() で指定した値に実際の fps値が達しない場合もあります。よって、指定できるのは一秒間に何枚のフレームを描画するかの”上限”と考えたほうがよいでしょう。

コード中に frameRate(fps) の記載が無い場合、多くの環境ではデフォルト値 60fps が使用されます。

24fps以上あれば十分スムーズなアニメーションになるそうですが、24fps と 30fps では滑らかさに明らかな違いが出ることが多いと思います。

設定は setup() 中で行うのがよいでしょう。

 

書き方

パラメータ有りの場合

frameRate(fps)

パラメータ

  • fps:一秒間あたりの描画フレーム数、小数の指定も可能(0.2 なら 5秒に 1枚描画)です。

 

パラメータ無しの場合

frameRate()

返り値

  • 現在の一秒間あたりの描画フレーム数を返します。
  • これは、frameRate(fps) でセットした fps の値になるとは限りません。計算量の多い、いわゆる重い描画であるほど fps の値より小さい値になるでしょう。
  • setup() 中や、初回の draw() 中では値は 0 となるようです。

 

p5.js 公式リファレンスのコード例の補足

赤い四角形は 30fps で進み、青い四角形は 10fpsで進む。画面の端まで来たら赤と青が入れ替わる。というアニメーションです。

実際に動かしてみる場合、動きがまどろっこしいので、5倍速にしましょう。


  rectX += 5; // Move Rectangle

draw() の最後に下記を入れると、動作がより分かりやすくなると思います。


  text(frameRate(), 10, 10);

 

frameCount 変数の意味と使い方

frameCount | p5.js 公式リファレンス

説明

プログラム実行開始から何枚目のフレームを描画しているかを格納しているシステム変数です。

その値は setup() 内で 0、その後 draw() 内に入ると 1、その後 draw() が繰り返されるごとに 1カウントアップされます。途中で noLoop()、redraw() が入ってもこの関係は崩れません。

変数なので、値を代入することもできます。setup() 内で -1 を代入しておけば、draw() での frameCount を 0 から始めることができます。

 

どこまでカウントしてくれるのか?最大値は?
2^53 - 1 = 9007199254740991 では?
9,247,568までは実際にカウントしてみました。30fps で 3日半動かし続けても大丈夫! 代入もできるので、心配なら途中でリセットするようにコードを書くとよいでしょう。

 

p5.js 公式リファレンスのコード例の補足

変数 frameCount の値を画面に表示するコードです。

frameRate(30) を frameRate(1) にすると、frameCount が 1 から始まるのが見えるでしょう。

frameRate(0.25) とすると、4秒に一回 frameCount がカウントアップされることになります。このとき注目したいのが、最初のフレームが描画されるタイミングです。実行してみると、実行から 4秒経過後に最初のフレームが描画されることがわかります。

これからすると、frameRate(n) は一秒間に n枚のフレームを描画するというより、1枚のフレーム描画に 1/n秒かけるというのが正しいのかもしれません。

 

frameRate() と frameCount を使った作例

frameRate() と frameCount を使って、指定の秒数でループするアニメーションを作ります。

何かオブジェクトが動くアニメーションなら、オブジェクトの指定秒数後の位置を開始時と合わせればループになります。実現には、TWO_PI で値が一巡する三角関数を使って動きを制御すると簡単です。

ループアニメーション:回転

cNum 個の円がそれぞれ異なるスピードで回転するアニメーションです。cycleSec 秒でループします。


/** 
 * p5.js frameRate() 関数と frameCount 変数を用いた作例
 * 回転するオブジェクトのループアニメーション
 * 
 * @author @deconbatch
 * @version 0.1
 * @license CC0
 * p5.js 1.5.0
 * created 2023.01.31
 */

const w = 640;
const h = w;
const cNum = 6;
const frmRate = 24; // fps
const cycleSec = 5; // ループの秒数
const cycleFrm = frmRate * cycleSec;

function setup() {
  createCanvas(w, h);
  colorMode(HSB, 360, 100, 100, 100);
  noStroke();
  frameRate(frmRate);
}

function draw() {
  // ここで周期を生み出す
  const ratio = map(frameCount % cycleFrm, 0, cycleFrm, 0.0, TWO_PI);

  background(240);
  translate(w * 0.5, h * 0.5);
  for (let cCnt = 1; cCnt <= cNum; cCnt++) {
    let cR = map(cCnt, 1, cNum, 0.1, 0.4) * min(w, h);
    let cX = cR * cos(ratio * cCnt);
    let cY = cR * sin(ratio * cCnt);
    fill(360 * cCnt / cNum, 40, 60, 100);
    circle(cX, cY, 30);
  }
}

円の座標計算時にランダム要素(円ごとの noise())を加えると、滅茶苦茶な動きをしてるように見えてちゃんとループするアニメーションにもできます。


    let phaseX = noise(10, cCnt) * TWO_PI;
    let phaseY = noise(20, cCnt) * TWO_PI;
    let cX = cR * cos(ratio * cCnt + phaseX);
    let cY = cR * sin(ratio * cCnt + phaseY);

 

ループアニメーション:サインカーブ

cNum 本のサインカーブがそれぞれ異なるスピードで揺れるアニメーションです。回転のコードの x 軸の計算を変えたものです。

cNum や xDiv の値を変えてみるといろんな表情が見られると思います。


/** 
 * p5.js frameRate() 関数と frameCount 変数を用いた作例
 * サインカーブのループアニメーション
 * 
 * @author @deconbatch
 * @version 0.1
 * @license CC0
 * p5.js 1.5.0
 * created 2023.01.31
 */
const w = 640;
const h = 320;
const cNum = 9;
const xDiv = 20;
const frmRate = 24; // fps
const cycleSec = 6; // ループの秒数
const cycleFrm = frmRate * cycleSec;

function setup() {
  createCanvas(w, h);
  frameRate(frmRate);
}

function draw() {
  const ratio = map(frameCount % cycleFrm, 0, cycleFrm, 0.0, TWO_PI);

  background(255);
  translate(0, h * 0.5);
  for (let cCnt = 1; cCnt <= cNum; cCnt++) {
    let cR = map(cCnt, 1, cNum, 0.05, 0.2) * min(w, h);
    for (let cX = 0; cX < w; cX += xDiv) {
      let phaseY = TWO_PI * cX / w;
      let cY = cR * sin(ratio * cCnt + phaseY);
      circle(cX, cY, cCnt);
    }
  }
}

 

QooQ