💨

p5.js 雑に入門

2024/02/11に公開

イベント用に作成したp5.jsを(割と雑に)始めるためのチュートリアルです。
とりあえずやってみたい人向け。必要なところだけつまみ食いしてください。
イベント毎に追記予定です。

p5.jsとは

p5.jsとは簡単にいうと「プログラミングで絵を描く」ためのツールです。
コンピュータの高速な動作や繰り返し動作、ランダム性を取り入れた動作を自由に実現することができ、それを使って視覚的なモノを作っていくことができます。

p5.jsを使って制作するためのツールは無料で提供されています。ここではオススメの2つを紹介します。

p5.js Web Editor

p5.js Web Editor」はp5.jsの開発元が提供している公式のツールです。シンプルな画面構成で保存機能も備わっているため最初はこれで良いでしょう。

https://editor.p5js.org/

Open Processing

Open Processing」は先の「p5.js Web Editor」と比べて作品の共有をすることに優れているツールです。自分が作ったものを作品のサムネイルとともに管理することができますし、他人の作品にも簡単にアクセスして鑑賞できるようになっています。エディタとしての使いやすさは「p5.js Web Editor」の方に軍配が上がりますので、「p5.js Web Editor」でコーディングした後に「Open Processing」で作品を公開する、というプロセスをとる方もいます。

https://openprocessing.org/discover/#/trending

p5.jsのキホン

では、早速コーディングしていきましょう。この記事ではツールとして「p5.js Web Editor」を用いで説明しますが、他のツールを使っていても、コードの内容が変わるようなことは特にないです。

最初にp5.js Web Editorを開くとこのような画面になっています。

画面左側がこれから編集していくコードで、右側がコードを実行した結果として描画される部分です。

左側のコードに注目してください。
コードの中には2つの関数setup(), draw()があります。これらの関数は名前の通り「準備」と「描画」をそれぞれ担当しています。
そしてsetup()はプログラムの実行時、最初に一回だけ実行されるのに対して、draw()setup()の実行後に何度もループして実行されます。

function setup() { //準備(最初に一回だけ実行)
  createCanvas(400, 400);
}

function draw() { //描画(何度もループ実行)
  background(220);
}

では、このまま何も編集していないコードを実行してみましょう。画面左上の三角マークを押して実行してください。そうすると、こうなります。

さて、これは何が起こっているのでしょうか。まずsetup()の中身を確認しましょう。
setup()の中にはcreateCanvas(400, 400)という関数があります。
これは、描画するためのキャンバスを作成すると同時に、そのサイズを任意に指定する関数です。
エディタの画面右側を確認すると灰色の四角形が描画されているかと思いますが、これのサイズが400×400となっているのです。

function setup() { //準備(最初に一回だけ実行)
  createCanvas(400, 400); //描画するためのキャンバスを作成&サイズを指定
}

次にdraw()の中身を確認しましょう。
draw()の中にはbackground(220)という関数があります。
これは、キャンバス全体の背景色を決め、それを描画する関数です。
引数(かっこの中の数字)の220は黒を0, 白を255としたときの、その間の色(つまり白寄りの灰色)を表しています。これを実行すると、先ほど確認したように画面右側に灰色の四角形が描画されます。

function draw() { //描画(何度もループ実行)
  background(220); //背景色を指定して塗りつぶし
}

例えばこれをbackground(50);へ変更するとこのようになります。

ほぼ黒に近い灰色になりました。

ちなみに、background()関数の引数に値を3つ渡すと、白〜黒の無彩色のみでなく有彩色を扱うことができます。さらに、あらかじめ用意されている基本的な色は引数にその色の名前を渡すことで使うこともできます。

background(60, 140, 200); //色をRGBで指定
background('white'); //色を名前で指定

図形を描画する

次は図形を描画してみます。p5.jsで扱える基本的な図形には「点」「線」「四角形」「円」があり、これらを組み合わせることでさまざまな表現をすることができます。
そして、それらをキャンバス上に描画するための関数がこちらです。

//実際にはx, y, w, hに具体的な数値を入れて使ってください!
point(x, y); //(x, y)の位置に点を打つ
line(x1, y1, x2, y2); //(x1, y1)の点から[x2, y2]の点までの線を引く
rect(x, y, w, h); //(x, y)の点からwだけの幅(横方向), hだけの高さ(縦方向)を持つ四角形を描画する
ellipse(x, y, w, h) //(x, y)を中心にwだけの幅, hだけの高さを持つ円を描画する

これらを実際に使ってみるとこうなります。

図形の色を変更するためにはstroke()関数とfill()関数を使います。
stroke()は点や線、そして四角形や円の縁線の色を変更する時に使います。
fill()は四角形や円の内側の色を変更するときに使います。
これらの関数は大抵setup()の中に書きますが、draw()の中に書いてループ毎に色を変えることもできます(この方法については後述します)。

例えば、先ほどのコードに"stroke()"と"fill()"を追加するとこのようになります。

ちなみに、p5.jsではキャンバスの原点(x=0, y=0の点)はキャンバス左上となっていて、xが増えると図形の描画位置は右方向へ、yが増えると下方向へ動きます。一般的な数学のグラフとは違うので要注意です。

アニメーションを作る

draw()は何度もループ実行されると最初に書きました。そのループ性を利用して、ループしながら図形を描画する位置を変えていくことでアニメーションを作ることができます。例えば以下のコードを実行してみてください。

function setup() {
  createCanvas(400, 400);
}

let a = 0;

function draw() {
  background('white');
  ellipse(a, 200, 40, 70);
  a = a + 1;
}

このコードを実行すると楕円が右に動いていくようなアニメーションが描画されます。

このコードで重要なのは、ellipse()の引数の1つ目にaという変数を入れていることです。
このasetup(),draw()の外で宣言されているグローバル変数で(つまりdraw()が実行される度にリセットされない)、これをdraw()が実行される度にa = a + 1でカウントアップしています。これによって楕円が描画されるx座標が1ずつ増えていきます。

ついでに、background()setup()の中に移すとこうなります。

実はbackground()は実行される度にキャンバス全体を上から塗りつぶしています(「背景」と名付けられているのでややこしい)。そのため、draw()の中に書いている場合は毎度background()が実行された後にellipse()が実行されていた(つまり1実行前の描画が上から塗りつぶされていた)のですが、setup()へ移すと最初に一回だけ実行されそれ以降は実行されないため、ひたすら楕円が上から、移動しながら描画されていきます。そうすると画像のような見え方になります。

話を戻します。draw()の中で変数を変化させていく方法を使えば、あらゆるものをアニメーションさせながら変化させていくことができます。例えば、図形の大きさや色などもそうです。どんどん活用していきましょう。
これはstroke()で線の色を変化させてみた例です。

さまざまな変数

p5.jsでは(というよりはJavaScriptでは)用途に合わせて幾つかの型の変数を使い分けることができます。

let a = 0; //1つの変数
let b = []; //配列
let c = "Hello" //文字列

class My_class { //クラス
    constructor(){
        this.name = "hoge"
    }
}

//などなど…

条件分岐、繰り返し

条件分岐をしたいときはif()を使います。

if(a > 5){ //a > 5のときは実行
    //任意の処理
} else if(a > 10) //a > 5ではないがa > 10のとき実行
    //任意の処理
} else { //すべてに該当しないとき実行
    //任意の処理
}

また、通常時draw()はループ実行されますが、draw()の中でループを作りたいときは、繰り返し処理のための一般的な関数であるwhile()for()を使うことができます。

while(a < 10){ //a < 10の間は繰り返し
    //任意の処理
    a++;
}

for(let i=0; i<10; i++){ //i = 0からスタート, i < 10の間は繰り返し, iはカウントアップ
    //任意の処理
}

便利な値

キャンバスの広さをしたいときはwidth, heightを使うと良いでしょう。
以下は簡単な使用例です。

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background('white');
  rect(width/2, height/2, 100, 100);
  
}


キャンバスの幅の半分、そして高さの半分の位置(つまりキャンバスの真ん中)から幅100, 高さ100の四角形が描画されています。

乱数を使う

図形の位置をランダムにしたいときや、ランダムな色を使いたいときには乱数を使うと良いでしょう。
p5.jsではrandom()関数を使うことで実行毎に異なる乱数を生成することができます。

let a = random() //0~1までの乱数をaに代入
let b = radom(100) //0~100までの乱数をbに代入
let c = random(10, 200) //10~200までの乱数をcに代入

以下は四角形の描画位置を直接"random()"を用いて指定している例です。

function setup() {
  createCanvas(400, 400);
  background('white');
}

function draw() {
  rect(random(width), random(height), 100, 100);
  
}

詳細は公式リファレンスをご参照ください。
https://p5js.org/reference/#/p5/random

パーリンノイズを使う

パーリンノイズを使うと、滑らかで豊かな表現をすることができます。

パーリンノイズは簡単に言うと「ランダム性を持つ波」です。乱数を使うための関数であるrandom()は実行毎に異なる値を返すため、連続で実行しても1つ1つの値には関連性がありません。
このrandom()を、例えばアニメーションの中で使うとメチャクチャなアニメーションになります。
メチャクチャな表現をしたい時には有用ですが、もっと「滑らで連続的」な表現をしたいときにはパーリンノイズを使います。

p5.jsでパーリンノイズを使うためにはnoise()関数を使います。noise()はプログラムが実行されると生成されるノイズ上で、引数によって指定される「位置」における値を0~1で返します。例えばnoise(5)はノイズ上で5の位置に当たる値(0~1)を返します。

let a = noise(5); //ノイズ上で5の位置に当たる値を返す

挙動を理解するために、まずは以下のコードを実行してみてください。

function setup() {
  createCanvas(400, 400);
  background('white');
}

let a = 0;
let ns = 0.2; //ノイズスケール

function draw() {
  point(a, width/2 - noise(a*ns)*50);
  a = a + 1;
}

何やら、波のようなものが描画されます
点を打つx方向の間隔が広すぎるので、a = a + 0.2に変更します。

少し見やすくなりました。

コードの解説をします。
noise(a*ns)*50では、コードの実行時にあらかじめ生成されているノイズの上でa×nsの位置にある値(0~1)を50倍にしています。aの値を変えていくと、a×nsも変わっていくので、指定されるノイズ上の位置が代わり、noise()が返す値が少しずつ変わります。

ここでnsはノイズスケールの役割を果たしています。nsが大きければ大きいほど一回のループでノイズ上の位置が大きく変わり、返り値も大きく変わります。小さいと位置の変化は小さく、返り値の変化も小さくなります。

ここで、ノイズスケールが小さい場合と大きい場合について、random()を使った場合と比較しながら確認しましょう。コードはこちらです。

function setup() {
  createCanvas(400, 400);
  background('white');
}

let a = 0;
let ns1 = 0.2; //小さなノイズスケール
let ns2 = 5; //大きなノイズスケール

function draw() {
  point(a, width/2-100 - noise(a*ns1)*50); //ノイズスケールが小さい場合
  point(a, width/2 - noise(a*ns2)*50); //ノイズスケールが大きい場合
  point(a, width/2+100 - random()*50); //ランダムの場合
  a = a + 0.2;
}

上から順番に小さなノイズスケール、大きなノイズスケール、ランダムの場合それぞれについて描画されています。下に行くほどランダム性が高くなっているのがわかります。つまり、nsが大きくなればなるほど波が荒くなり、連続性のないランダムに近づいていきます。
逆にnsを小さくするほど、連続的に滑らかでありながらランダム性を持つ波を作ることができます。

nsの引数は3つまで指定することができます。p5.jsでは基本的に2次元のキャンバス上で表現をしてくので、必要に応じて引数を2つにすることでキャンバス上の位置に応じた値を返すことができます。
以下はその簡単な例です。

function setup() {
  createCanvas(400, 400);
  background('white');
}

let a = 0;
let ns = 0.1;

function draw() {
  for(let i=0; i<width; i++){
    for(let j=0; j<height; j++){
      stroke(255*noise(i*ns, j*ns));
      point(i, j);
    }
  }
  noLoop();
}


キャンバス上の全ての点に、その座標情報を引数にしたnoise()の返り値をstroke()の引数として色を変更した点を打つ頃で、場所と色を対応づけています。

Discussion