なんやこの記事
私は、p5.js を使った作品をTwitterに投稿しています。
で、どんなコードの断片...つまり、"素材" を組み合わせて作品を作っているか...を書きます。
つまるところ、コードの素材AとBを合わせると作品Cが出来た... という例を集めた記事です。アイデア集ともとることができますね。
この記事は、コードの素材の部と、そこからできた作品の2部に分かれます。まずは素材の部から行ってみましょう。
重要: 私はframeCountに依存するようなコードしか書きたくない体質になっているため、配列やクラスを使った書き方についてはここでは紹介しません。 もっと高度な事がしたいなら配列を使った方がいいです。1
素材集め (アートの"もと"となるコード)
ある処理をx座標ごとにコピーする。
let step = 50; // コピーしていく幅
for(let x=0;x<=width;x+=step){
// [ある処理, xを使用可能]
}
これ以降の素材でも同じことが言える考え方として、[ある処理] の中では、数値の部分を x
に置き換えることができます。
例えば:
function setup() {
createCanvas(500, 500);
background(0)
circle(200, 200, 99)
}
のcircle
の、x座標の部分をxに置き換えると...
function setup() {
createCanvas(500, 500);
background(0)
let step = 50;
for(let x=0;x<=width;x+=step){
circle(x, 200, 99) // 一つ目の座標を200からxに置き換える。
}
}
のように変化します。x座標ごとにコピーされていることが分かりますか?
同じ手順で x だけでなく y 方向にもコピーすることができます。circleの2変数目を置き換えればできますね。
時間をずらしてコピー
for(let n=0;n<=99;n+=3){
let newFrameCount = frameCount + n;
// または、let newFrameCount = frameCount - n;
// [ある処理, frameCountの代わりにnewFrameCountを使う]
}
図形の描画がframeCount
によって決まっている場合、そのframeCount
を少しずつずらしていくことで、図形の軌跡が残っていくような表現を作ることができます。"x 座標ごとにコピーする" の章に似ていますね。
n
はframeCount
をどれだけずらすか、という数値ですが、正直やってみないといい塩梅が分からないと思うので、動かしながらfor文に渡す値を変えてみたほうがいいです。
例えば、後の章の"端から端まで動かす"と組み合わせてみると... :
function setup() {
createCanvas(500, 500);
}
function draw(){
background(0)
let speedPerFrame = 3
// 元のコード... (赤円)
fill(255, 0, 0)
let x = frameCount * speedPerFrame
circle(x, 150, 50)
// 時間をずらしてコピーし、残像を残していく (緑円)
fill(0, 255, 0)
for(let n=0;n<=99;n+=3){
let newFrameCount = frameCount - n;
let x = newFrameCount * speedPerFrame
circle(x, 350, 50)
}
}
緑の円を、それぞれn
だけフレームが遅れたように沢山書いているため、円が連続しているように見えます。...言い換えると、後ろの方の緑円は前の方の緑円が通って行ったところを追いかけている感じに描画されます。
応用として、"x座標ごとにコピーする" と組み合わせて、n
の代わりにx
を使うと、x
座標ごとにframeCount
をずらすことができます。
放射状に座標を得る
let n = 9; // 一周中、何個分の座標を得るか
let centerX = 200, centerY = 200; // 放射状の中心とする点
for(let angle=0;angle<=TAU;angle+=TAU/n){
let r = 200; // 点を取る半径
let x = centerX + r * cos(angle), y = centerY + r * sin(angle);
// [ある処理, x, y (と、r)を使用可能]
}
点 $(cx, cy)$ から 角度 $i$ の方向に 距離 $r$ だけ離れた点は、 $(cx + r × cos(i), cy + r × sin(i))$ で得ることができるんですよ。これの説明は本題からそれるので省きますが、簡単に放射状の座標を得ることができます。
あー、TAU
というのは2 × PI
の事ですよ。 (リファレンス:https://p5js.org/reference/#/p5/TAU)
ラジアンという角度の表し方だと一周の角度(360°)は 2 × PI
と表されるので、それを使います。 sin()
やcos()
は引数にラジアンを受け取るためです。
for文では、0 から一周する角度まで回すことで一定間隔ごとに角度を得ていっています。
例:
function setup() {
createCanvas(500, 500);
background(0)
// 元のコード... (赤い円)
fill(255, 0, 0)
circle(200, 200, 50)
// 放射状の座標を使ってみる (緑の円)
fill(0, 255, 0)
n = 9;
centerX = 200, centerY = 200;
for(let angle=0;angle<=TAU;angle+=TAU/n){
let r = 150;
let x = centerX + r * cos(angle), y = centerY + r * sin(angle);
circle(x, y, 50) // (200, 200) を (x, y) に置き換え
}
}
端から端まで動かす
let speedPerFrame = 3;
let x = (frameCount * speedPerFrame) % width; // 時間ごとにxがspeedずつ増えていき、端まで着いたらxは0に戻る。
let x2 = (frameCount * speedPerFrame) % (width + 200) - 100; // 両端に"100"だけ余裕を持たせたバージョン
// [ある処理。xやx2を使うことができる。]
このx
やx2
の式を使うと、"図形が移動したまま画面の外に出ていくこと"を防ぐことができます。
もちろんx座標だけじゃなくてy座標にも使えますからね。
詳しい動きは以下の例を見てください。
例:
function setup() {
createCanvas(500, 500);
}
function draw(){
background(0)
let speedPerFrame = 3
// 元のコード... (赤円)
fill(255, 0, 0)
let x = frameCount * speedPerFrame
circle(x, 100, 50)
// 端まで着いたときに元の座標に戻る (緑円)
fill(0, 255, 0)
x = (frameCount * speedPerFrame) % width
circle(x, 200, 50)
// 端にちょっと猶予を持たせる (青円)
fill(0, 0, 255)
x = (frameCount * speedPerFrame) % (width + 200) - 100
circle(x, 350, 50)
}
赤は、右に移動し続けて外に出て行ってしまうパターン。
緑は、右端に着いたらすぐに左端に移動するパターン。
青は、図形が x = width + 100
に移動するまで待ってから、x = -100
の位置に移動するパターン、になります。
青がいちばん不自然じゃなくておしゃれに見えませんか?
ある変数について滑らか(連続)な三角波を得る
let variable = 50; // なにかの変数
let period = 200; // 三角波の周期
let amplitude = 20; // 三角波の振幅
// 短く書きたいなら
let triangle = amplitude / PI * acos(cos(variable / period * 2 * PI));
// 真面目に書きたいなら
let fraqVariable = variable % period
let halfPeriod = period / 2
let triangle2 = fraqVariable < halfPeriod ? (fraqVariable / halfPeriod * amplitude) : (amplitude - (fraqVariable-halfPeriod) / halfPeriod * amplitude)
// ちょっとテクい書き方をしたいなら
let triangle3 = amplitude - abs(fraqVariable - halfPeriod) / halfPeriod * amplitude
// [ある処理, triangle や triangle2 や triangle3 を使用可能]
私は このサイト を通してこれを知り、以降愛用しています。
この三角波は、端にあたったら同じ速度で跳ね返るという動きを表すのに有用です。私はたいていvariable
としてframeCount
を使います。
例:
function setup() {
createCanvas(500, 500);
}
function draw() {
background(0);
let variable = frameCount; // なにかの変数
let period = 99; // 三角波の周期
let amplitude = height; // 三角波の振幅
let y = amplitude / PI * acos(cos(variable / period * 2 * PI));
circle(250, y, 50)
}
...frameCount
に応じて y
座標が三角波的に変わっているのが分かりますか?
ある変数についてランダムな値を得る (同じ入力なら同じ結果を得るし、違う入力なら違う結果を得る)
let variable = 50; // なにかの変数
// パターンA
let random_float_A = noise(variable); // variableに応じて、0-1の間でランダムな小数を得る。
// パターンB
randomSeed(variable);
let random_float_B = random(); // variableに応じて、0-1の間でランダムな小数を得る。
// [ある処理, random_float_A や random_float_B を使用可能]
variable
に応じて、とは、一回のスケッチ実行中であれば、同じvariableなら同じ結果を返す 乱数であるという意味です。
パターンAでもパターンBでも同じようにvariable
に応じて0-1の乱数を生み出しますが、使い心地は微妙に違います。以下にその違いを示します。
比較項目 | noise | randomSeed + random |
---|---|---|
コードの長さ | 短い | 長い |
スケッチの実行ごとに値が変わる? | 変わる | 変わらない |
値のばらつき具合 | 0.5周りに集中 | 0-1までバランスよく出る |
元とする変数の数 | 最大3つ | 1つ |
variableの差と結果の値(*) | 関係がある | 関係ない |
性質が違うので、ぴったり合う方を使ってみてください。
(*)もともとnoiseは、x座標やy座標から地形の高さを滑らかに決定する...などの用途のために生み出されたため、近いvariable
の 値だと近い結果しか返しません。noise(0.55)
とnoise(0.56)
ぐらいの差だとほぼ同じ値が返ってくるでしょう。
...あー、noiseSeed
はここでは取り上げません。 (リファレンス:https://p5js.org/reference/#/p5/noiseSeed)
例えば、「x座標ごとにコピー」と合わせて... :
function setup() {
createCanvas(500, 500);
}
function draw(){
background(0)
for(x=0;x<500;x+=50){
circle(x, 500*noise(x), 50); //500*noise(x) -> xごとにランダムなy座標を得る。何回実行しても同じy座標になる。
}
}
draw内の処理は毎フレームごとに行われるはずですが、円の位置は変化しません。
つまり、何回実行しても 500 * noise(x)
の値は変化しないという事です。
しかも、xごとにy座標がばらついています。乱数的性質が分かりますか?
パターンを敷き詰めてスクロール (難)
"処理のx座標ごとのコピー"の亜種です。
let step = 50;
for(let x=-frameCount%step;x<=width;x+=step){
let noiseArg = x+f;
// [ある処理, xを使用可能。"ある変数についてランダムな値を得る"のvariableの代わりにnoiseArgを使用。]
}
解説は、自分でもこれで動くのを不思議に思っているため、スキップします。
実際の例を見てどう動くのかをとってください。
もう一度言いますが、拡張性を高くするなら明らかに配列を使った方がいいです。
例:
function setup() {
createCanvas(500, 500);
}
function draw() {
background(0);
let step = 50;
//円が突然現れるのを防ぐため、右端にももう少し円を書き続ける
let mergin = 100;
for(let x=-frameCount%step; x<=width+mergin; x+=step){
let noiseArg = x+frameCount;
let y = height * noise(noiseArg)
circle(x,y,50)
}
}
上の例では、circle(?, [時間がたっても変化しないランダムな高さ], 50)
というパターンを、x座標ごとにコピーして、しかもそのパターンを左に繰っていっています。一度にいろいろなパターンをスケッチに詰めたい、というときに有用です。というか私はこれをめちゃくちゃ使います。
料理 (どの素材を組み合わせるか)
この章では、実際にどれを組み合わせてどんな作品が出来たか、というのを見せていきます。
私の作品作りはコードゴルフも兼ねているため、コードを読もうとせずに感覚的に理解するのがいいと思います。
では、行ってみましょう。
処理のx座標ごとのコピー + ある変数についてランダムな値を得る + 端から端まで動かす + 放射状
処理のx座標ごとのコピー + 時間をずらしてコピー (+ 時間をずらしてコピーの応用)
パターンを敷き詰めてスクロール + 時間をずらす + ある変数についてランダムな値を得る + 極座標
パターンを敷き詰めてスクロール(x) + パターンを敷き詰めてスクロール(y) + 三角波 + 極座標
処理のコピー(単純に増やすだけ) + 極座標 + ある変数についてランダムな値を得る
おわりに
この記事は、Processing Advent Calendar 2022, 5日目の記事です。
全部読んだ人、お疲れ様です。
全部読まなかった人、全部読むのはとてもしんどいんで気になったところだけ読めば良いですよ。
もしかしたら、これとこれを組み合わせて新しいスケッチを...っていうこともできるかもですね。
以上です。
-
配列を使わないということ、つまりframeCount以外のグローバル変数を用いないという事は、図形の座標がframeCountによって、微分や積分を使わない形で表せるという状況です。積分が難しい速度や加速度を持つ図形は配列やグローバル変数を使ってコンピュータ上で疑似的に積分をしてやる必要があります。...って説明であっているかしら。 ↩