アリソンパリッシュによる
このチュートリアルでは、時間の経過とともに変化する、簡単なp5.jsスケッチの作成方法を見ていきます。しかしその前に、Processingにおける時間の働きについて少し説明しておく必要があります。
目次
setupとdraw
ここまで見てきたどのスケッチにも、プログラムの奇妙な箇所がありました。それはsetup() { … } と function draw() { … }の部分です。これが何をしているものなのか、正確にはまだ述べていないので、それらが意味するものとなぜそう見えるのかについて教えてもらわないことには、きちんと理解するのは難しいでしょう。
しかし次のハードルを越えるための分かりやすい説明をします。setup()内に置いたすべてのステートメントはプログラムのスタート時に1度だけ実行され、draw()内に置いたすべてのステートメントは、1秒間に何十回というスピードで、何度も繰り返し実行されます。
みなさんはおっしゃるでしょう。「何だって? どういう意味? わたしのスケッチはどう見ても何度も何度も描画しているようには見えない、ずっと同じ静止画のように見える」。その通り、これまでに見てきたスケッチは全部まったく同じことを行うものだったのです。したがってスケッチの実行時、外見に変化はなかったのです。
draw()内のコードは何度も繰り返し実行されます。これは、次のコードで確認できます。
function setup() {
createCanvas(400, 400);
}
function draw() {
console.log("mark");
}
何が起きているのでしょう? console.log()関数は、特定の値を出力することを目的とする関数です。draw()内のコードは全部、1秒間に何度も何度も実行されます。ここでは、同じconsole.log()関数への呼び出しが何度も何度も実行され、その結果おびただしい数の繰り返しになっているのです。
フレーム
画面に何かを描画する関数の1回の呼び出しの名前を、コンピュータアニメーションでは”フレーム”と言います。Processingでは、frameRate()関数とビルトイン変数のframeCountという、フレームを処理するための便利な方法を2つ提供しています。
frameRate()関数を使用すると、draw()内のコードの実行頻度が設定できます。これは”フレームレートの設定”と呼ばれます。次のスケッチで試してみましょう。
function setup() {
createCanvas(400, 400);
frameRate(1);
}
function draw() {
console.log("mark");
}
フレームレートの設定は1回行うだけででよいので、frameRate()への呼び出しはdraw()ではなくsetup()内で行うべきです。frameRate()のかっこの中に置いた数値は、draw()内のコードを毎秒何回実行するかを決めます。上記スケッチでも、前の例と同じようにmarkが出力されますが、かなり遅くなっています(1秒に1回の頻度)。
frameCount変数はProcessingに組み込まれているので、自分で定義する必要はありません。つまりみなさんに代わってProcessingが、スケッチの開始前に定義しているのです。frameCount変数は、フレームごとに変化するという点で特殊です。draw()内のコードが初めて実行されるとき、frameCountは0で、draw()内のコードが2回めに実行されるとき、frameCountは1になります。以降も同様です。次のスケッチは前のものとよく似ていますが、markを出力する代わりにframeCount変数の現在の値を出力します。
<pre><code>function setup() {
createCanvas(400, 400);
frameRate(1);
}
function draw() {
console.log(frameCount);
}</code></pre>
このスケッチをp5 Webエディタで実行すると、スケッチが1秒間に1ずつ、ゆっくりとカウントアップし(1ずつ大きくして数え)、コンソールにその数値を出力するのが分かります(もちろん、frameRate()で大きな数値を設定すると、もっと速くなります)。
frameCountを使ったアニメーション
frameCount変数は、式や関数呼び出しで使用できるという点で、ほかの変数と変わりません。frameCountはフレームごとに変化するので、その値を単純なアニメーションの作成に活かすことができます。次はその例です。
小さな楕円が画面左からスタートし、縦に長くなりながら、ゆっくりと右に移動するのが分かります。
演習
frameCount変数をほかの関数呼び出しで使用したり、frameCount変数を式の一部で使用して、何が起きるかを確認します。
前のフレームの内容を残す
次のスケッチは上のものとよく似ていますが、1つ重要な違いがあります。それを指摘し、なぜこういう結果になるのか、考えてみてください。
これは実に奇妙な結果です。各フレームが前の描画の上に描かれているように見えます。どうしたのでしょう?
最初に知っておくべきは、Processingは毎フレーム、自動的にキャンバスをクリアしないということです。デフォルトでは、前のフレームの最後の時点でキャンバスにあるものは全部、次のフレームのキャンバスに残ります。
2つのスケッチの違いは、background()関数を呼び出した場所にあります。上記の例では、background()をsetup()内で呼び出しています。これは1回だけ実行されるということです。draw()内ではbackground()を呼び出していないので、背景は毎フレーム、フラットなカラーに”リセット”されません。
毎フレーム、背景を再描画しないと、スケッチがそれ自体の上に何度も何度も描画されているように見える効果が生まれます。毎フレーム、background()を呼び出すと、アニメーションに見える効果が生まれます。この2つの方法論をうまく使うと、すぐれた効果を作り出すことができます。
単振動
アニメーションが作成できるのは素晴らしいことですが、frameCountには1つ問題があります。それは、線形に大きくなるので(一定の割合で大きくなる一方なので)、繰り返して見えるアニメーションにするための明確な方法がない、という問題です。作成できるのは、大きくなる一方のものや一方向に移動するものです。以降では、この線形のframeCount値だけを変化の元として使って、2つの状態の間を行ったり来たりさせる単振動の簡単な戦術を見ていきます。
剰余
数学には、”剰余”(余り)と呼ばれる演算があります。この演算では、ある整数を別の整数で割った結果の余りが得られます。たとえば、11 の 5に対する剰余演算の結果は、11 / 5の余りで1です。
JavaScriptでは、剰余演算の演算子として%が使用できます。たとえば、p5.jsのWebエディタで次のコードを試すと、コンソールに1が表示されます。
console.log(11 % 5)
今の目的にとって興味深いのは、剰余演算子を使うと、簡単な繰り返しカウンタが作成できるということです。これは、剰余演算子が次のパターンを生み出す、という事実から来ています。
0 % 5 => 0 (0を5で割ると0、余りは0)
1 % 5 => 1 (1を5で割ると0、余りは1)
2 % 5 => 2 (2を5で割ると0、余りは2)
3 % 5 => 3 (3を5で割ると0、余りは3)
4 % 5 => 4 (4を5で割ると0、余りは4)
5 % 5 => 0 (5を5で割ると1、余りは0)
6 % 5 => 1 (6を5で割ると1、余りは1)
7 % 5 => 2 (以降も同様...)
言い方を変えると、剰余演算子の左辺に増加しつづける値があり、右辺に一定の値があると、剰余演算子の評価結果から、0から演算子の右辺にある値まで(その値は含まない)を繰り返す一連の値が作成できる、ということです。、
剰余演算のこの性質を使用すると、任意のnフレーム繰り返す”ループ”が作成できます。たとえば、次のスケッチは、30フレームごとに大きくなり元のサイズにリセットされる楕円を作成します。
次の、左から右に移動する円を描画するスケッチは、円が画面右端に来ると、左端に”リセット”されるように見えます。また2つめの単振動として、ストロークの太さを加えています。
演習
frameCountの剰余演算を使って、異なる周期の単振動を複数持つスケッチを作成します。
サイン
剰余演算の利用はうまいテクニックですが、簡単に作成できるのは、線形に大きくなる、滑らかでないループだけです。滑らかに拡大縮小するように見えるアニメーションが作成できる簡単なテクニックとして、frameCount変数のサインの計算があります。
しかしここでは、”サイン”関数の説明や働きについて詳しく見ていくことはしません。ここでの目的は値のサインを計算して単振動を生み出す値を得ることです。サイン関数にはp5.jsのsin()が使用できます。これまで見てきた関数と異なり、sin()の目的は何かを描画することではなく、1つの値を取り、それに数学演算を適用した別の値を返すことです。
sin()関数はパラメータを1つ取り、-1から1までの数値に評価します。以下はsin()関数にさまざまな値を与えた評価の結果です。
sin(0) = 0
sin(0.39) = 0.38
sin(0.78) = 0.70
sin(1.17) = 0.92
sin(1.57) = 1
sin(1.96) = 0.92
sin(2.35) = 0.70
sin(2.74) = 0.38
sin(3.14) = 0
sin(3.53) = -0.38
sin(3.92) = -0.70
sin(4.31) = -0.92
sin(4.71) = -1
sin(5.10) = -0.92
sin(5.49) = -0.70
sin(5.89) = -0.38
sin(6.28) = 0
言い方を変えると、0のサインは0で、pi/2(約1.57)のサインは1、piのサインは0で、3/2pi(約4.72)のサインは-1、2*pi(約6.28)のサインも0です。sin()に渡す値を大きくしても、結果は-1と1の間に収まります。
ではsin()にframeCount変数を渡すとどうなるでしょう? 結果は次のようなものです。
さほど面白くはありません。円が震えているように見えますが、これは、sin()関数が返す値が常に-1から1までだからです。この変化をはっきり分かるようにするには、結果を掛け算して、正負両方で1ピクセルよりもっと大きな違いにする必要があります。
前より良くなりました。しかし少し動きが激しすぎます。それは、sin()関数によって6くらいまでカウントアップする値のサイクルが生まれ(正、正、正、負、負、負が1周期)、frameCountが6を過ぎたカウントを非常に速く進めるからです。この単振動をもっと遅くするには、frameCount値を別の値で割る必要があります。10で割って10倍遅くしてみましょう。
sin()関数は、ほとんどの場合で、振動の振幅(振動の大きさ)と振動の周波数(振動の速さ)に関する2つの値と組み合わせて使用すべきです。具体的には次のように表されます。
x + (sin(frameCount / y) * z)
このxとy、zは全部数値です。yを大きくすると振動が遅くなり(周波数)、zを大きくすると振動が大きくなります(振幅)。x値は振動の中心点で、つまり”動かない場所”の値です。
繰り返しとバリエーション
次のスケッチは、一連の円を、sin()を使ってその水平位置を制御して移動させます。
次のスケッチは洗練させたサンプルで、ループ変数のiを使って、各円に少し変化を持たせています。
このサンプルで難しいのは次の式です。
200 + (sin(frameCount / (i + 10)) * (i + 20))
これをよりよく理解するために、一度日本語に翻訳し、frameCountとiの特定の値に対して、式全体の値がどうなるか計算してみてください。
変数を経時的に変更する
これまでのサンプルで使用した変数は値を1つだけ持つものばかりで、setup()の前で最初に設定しました。しかし変数の値はプログラムの進行中に変えることができます。つまり、一度値を設定しても、その変数の現在の値をほかの値で上書きするようJavaScriptに命令することができるのです。JavaScriptのこの属性を使用すると、frameCount変数を使った場合よりももっと洗練された方法で、経時的に変化するスケッチが記述できます。
変数宣言は次のように記述しました。
let x = expr;
xは変数の名前で、exprはxに値を代入する式です。変数の値は次のシンタックスで変更できます。
x = expr;
ここでもxは変数の名前で、exprは結果がxに代入される式です。
次のスケッチでは、fooとbarという2つの変数を宣言し、それらをdraw()内のコードで変更しています。
このスケッチのコードでは、毎フレーム、fooに1を足し、barから1を引いています。
単に変数の現在の値を得て、その値に1を足すか引くかして代入し直すのはよく行われる作業なので、その値ためのショートカット(手っ取り早い方法)があります。次の式はその下の式に書き直すことができます。もちろん1以外の値も使用できます。
foo = foo + 1;
foo += 1;
次のスケッチは、毎フレーム、少量だけ値を大きくすることで(frameCountを割るのではなく)、sin()関数を使った上記サンプルで行う必要のあった多くの奇妙な計算の先を行く例です。
遊び半分でY位置を加えてみましょう。
演習
毎フレーム、xposとyposの調整に使用していた量(0.05と0.04)を、静的な値ではなく、経時的に変化させると、どうなるでしょう?
ランダムな変更
私見ではありますが、コンピュータが生み出すアートは、確定されていない外見を持つようになると、面白みが出始めるようになると思います。そこで次は、スケッチにランダム性を持ち込んでみましょう。
Processingには、0と1の間のランダムな数値に評価するrandom()という名前の関数があります。この関数の詳細や呼び出す別の方法については、リファレンスページを参照してください。random関数が生成する数値は、スケッチを実行するたびに、そして関数呼び出しごとに変わります。
次のスケッチでは、ランダムな位置にシェイプを描画するために、random()関数を使用しています。
random()関数は2つのパラメータを渡して呼び出すこともできます。その場合random()関数は、1つめと2つめのパラメータの間のランダムな数値を返します。次のスケッチでは、画面上を”さまよっている”ように見えるシェイプを作成しています。