フロントエンド初心者がp5.jsでビジュアルコーディングに挑戦

JavaScript自体あまりさわったことのないフロントエンド初心者ですが、p5.jsに挑戦してみました。

# p5.jsとは
[p5.js](https://p5js.org/) は、ProcessingをJavaScriptに移植したものらしいです。

[examples](https://p5js.org/examples/) を見てみると、いろいろできそうな雰囲気です。
例えば、こんな例があります[^1]
[^1]: [Penrose Tiles](https://p5js.org/examples/simulate-penrose-tiles.html) 

![image.png](https://qiita-image-store.s3.amazonaws.com/0/216010/b0a28ee9-a832-3a22-92a5-12c19618daad.png)
※実際には動きます。

# 何を作ってみるか...
12月なので雪を降らせてみようと思ったのですが、すでに [Snowflakes](https://p5js.org/examples/simulate-snowflakes.html) というサンプルがありました。。。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/216010/d7279b73-55c6-11bc-293c-c27229478a8a.png)
※実際には動きます。

それなら、この雪を積もらせてみようと思います!!!

# 追加したコード

Snowflakesのサンプルを元に、いくつか処理を追加していきます。

## 一番下に到達した雪を固定する

一番下に到達した雪を固定するため、変数と描画の処理を追加しました。

```javascript
let piledSnowflakes = [];
    :
function draw() {
    :
  // draw piledSnowflakes
  for (let piledFlake of piledSnowflakes) {
    piledFlake.display();
  }
    :
}

function snowflake() {
    :
     // delete snowflake if past end of screen
    if (this.posY > height) {
      // add piledSnowflake instead of deleting snowflake
      piledSnowflakes.push(this);

      let index = snowflakes.indexOf(this);
      snowflakes.splice(index, 1);
    }
    :
}
```

## 積もるラインをだんだん上にする

スクリーンの一番下に積もり続けるのではなく、積もる位置が徐々に上になっていくようにしました。

```javascript
let piledSpeed = 0.05;
let piledHeight = -300 * piledSpeed;
    :
function draw() {
    :
  piledHeight += piledSpeed;	
    :
}

// snowflake class
function snowflake() {
    :
    // delete snowflake if past end of piled snow line
    if (this.posY > height - piledHeight) {
      // add piledSnowflake instead of deleting snowflake
      piledSnowflakes.push(this);

      let index = snowflakes.indexOf(this);
      snowflakes.splice(index, 1);
    }
    :
}
```

## 四角形で積もるようにする

上記の積もらせ方では雪と雪の間に隙間ができてしまい、見た目が綺麗になりませんでした。
そこで、下から四角形が積もっていくようにしました。

```javascript
function draw() {
    :
  // draw piled snow using rectangle
  piledHeight += piledSpeed;	
  fill('white');
  rect(0, height - piledHeight, width, piledHeight);
    :
}
```
## 積もった雪に埋もれた雪片は削除する

雪片のオブジェクトが増えていくと処理が重くなっていくため、積もった雪に埋もれたものは削除する処理を入れました。

```javascript
function draw() {
    :
  // draw piledSnowflakes
  for (let piledFlake of piledSnowflakes) {
    piledFlake.deleteBuriedSnowflakes();
    piledFlake.display();
  }
}
    :
function snowflake() {
    :
  // delete piledSnowflakes under piled snow line
  this.deleteBuriedSnowflakes = function() {
    if (this.posY > height - piledHeight + 5) {
      let index = piledSnowflakes.indexOf(this);
      piledSnowflakes.splice(index, 1);
    }
  };
}
```

# 結果

こんな感じになりました。

![2s13v-l00jm-min.gif](https://qiita-image-store.s3.amazonaws.com/0/216010/963200c4-c3c4-17e9-249d-95ac144c1788.gif)

テーマがよくないのか、実装方針がよくないのか、、、
ちょっとしっくりきませんが、ひとまずこれでよしとします。

# 最終的なコード

今回の最終的なコードは以下の通りです。
[p5.js Web Editor](https://editor.p5js.org/) に貼り付けて動かすことができます。

```javascript
let snowflakes = []; // array to hold snowflake objects
let piledSnowflakes = [];

let piledSpeed = 0.05;
let piledHeight = -300 * piledSpeed;

function setup() {
  createCanvas(400, 600);
  fill(240);
  noStroke();
}

function draw() {
  background('brown');
  let t = frameCount / 60; // update time

  // create a random number of snowflakes each frame
  for (var i = 0; i < random(5); i++) {
    snowflakes.push(new snowflake()); // append snowflake object
  }

  // loop through snowflakes with a for..of loop
  for (let flake of snowflakes) {
    flake.update(t); // update snowflake position
    flake.display(); // draw snowflake
  }

  // draw piled snow using rectangle
  piledHeight += piledSpeed;	
  fill('white');
  rect(0, height - piledHeight, width, piledHeight);
  // draw piledSnowflakes
  for (let piledFlake of piledSnowflakes) {
    piledFlake.deleteBuriedSnowflakes();
    piledFlake.display();
  }
}

// snowflake class
function snowflake() {
  // initialize coordinates
  this.posX = 0;
  this.posY = random(-50, 0);
  this.initialangle = random(0, 2 * PI);
  this.size = random(2, 5);

  // radius of snowflake spiral
  // chosen so the snowflakes are uniformly spread out in area
  this.radius = sqrt(random(pow(width / 2, 2)));

  this.update = function(time) {
    // x position follows a circle
    let w = 0.6; // angular speed
    let angle = w * time + this.initialangle;
    this.posX = width / 2 + this.radius * sin(angle);

    // different size snowflakes fall at slightly different y speeds
    this.posY += pow(this.size, 0.5);

    // delete snowflake if past end of piled snow line
    if (this.posY > height - piledHeight) {
      // add piledSnowflake instead of deleting snowflake
      piledSnowflakes.push(this);

      let index = snowflakes.indexOf(this);
      snowflakes.splice(index, 1);
    }
  };

  this.display = function() {
    ellipse(this.posX, this.posY, this.size);
  };
	
  // delete piledSnowflakes under piled snow line
  this.deleteBuriedSnowflakes = function() {
    if (this.posY > height - piledHeight + 5) {
      let index = piledSnowflakes.indexOf(this);
      piledSnowflakes.splice(index, 1);
    }
  };
}
```

# 感想
p5.jsはプログラミングの経験が多少あれば見よう見まねでいじることができ、[p5.js Web Editor](https://editor.p5js.org/) を使えば環境構築も不要というハードルの低さがよかったです。

しかし、使いこなして面白いものを作れるようになるには、何をどう表現するかを考える力と適切にコーディングする力の両方が必要そうで、難しさも感じました。