6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ニフティグループAdvent Calendar 2021

Day 6

Vue3 の CompositionAPI で p5.js を動かす

Last updated at Posted at 2021-12-25

この記事は、ニフティグループ Advent Calendar 2021 6日目の記事です。遅くなりすみません!
最近はシミュレーションの描画にハマっており、p5.js 触ってみたいなということで今回のネタにしました。
Vue.js のコンテナ内で、CompositionAPI の記法で p5.js を動かしたので紹介します。

CompositionAPI についての説明は割愛します。以下の記事を参考にさせていただきました。

作ったもの

振動するボールを作って動かすことができます。
マウスをクリックするとその位置にボールが集まってきます。
丸い物体が絶妙に揺れながら近づいてくるの可愛すぎる。

brown.gif

p5.js の紹介

p5.js はクリエイティブコーディングのための JavaScript のライブラリです。
アートやシミュレーションの描画として利用できます。

ここでは基本的な使い方を紹介します。

  • setup(): 最初に呼び出され、canvas の大きさや背景色を指定
  • draw(): 1/60秒ごとに呼び出される
  • その他、円状の物体を表示する関数や、色を指定する関数、マウスの位置や状態を取得する関数が揃っている

例えば、 canvasを生成し、マウスがクリックされるとそのマウスの位置に円を描画するコードはこちら。

p5_sample.gif

sample.js
function setup() {
    createCanvas(300, 300);
    background(0);
}

function draw() {
    // 1/60秒に一回、canvas の中心に直径50の円を描画
    ellipse(width/2, height/2, 50, 50);

    // マウスがクリックされていれば、drawCircleAtMouse 関数を実行
    if (mouseIsPressed) {
        drawCircleAtMouse();
    }
}

function drawCircleAtMouse() {
    // マウスの位置に直径 5 の円を描画
    ellipse(mouseX, mouseY, 20, 20);
}

コンテナの作成

ディレクトリ構成

├── Dockerfile
├── docker-compose.yml

Dockerfile

  • 今回はvue-cli でプロジェクトを作成
Dockerfile
FROM node:16.3-alpine

WORKDIR /usr/src/app

RUN apk update && apk add curl && apk add vim
RUN npm install -g npm && npm install -g @vue/cli

docker-compose.yml

docker-compose.yml
version: '3.8'
services:
  p5_vue_container:
    build: .
    tty: true
    volumes:
      - ./:/usr/src/app
    ports:
      - "8080:8080"

コンテナを作成して中に入る

$ docker compose up -d
$ docker compose exec p5_vue_container sh
/usr/src/app #

プロジェクト作成

  • 今回は Vue.js で p5.js を使えれば良いので、preset は Default (Vue 3) を選択
/usr/src/app # vue create p5-test

Vue CLI v4.5.15
? Please pick a preset: Default (Vue 3) ([Vue 3] babel, eslint)
? Pick the package manager to use when installing dependencies: NPM

以下のファイルを追加

  • p5-test/js/brown.js
  • p5-test/js/class/ballClass.js

最終的な主なディレクトリ構造

├── Dockerfile
├── docker-compose.yml
├── p5-test
    ├── src
        ├── App.vue
        ├── js
            ├── SetupP5.js
            ├── class
                ├── ballClass.js

Vue.js コンテナ内で p5.js を使う

こちらを参考にさせていただきました。

コンテナ内で p5.js をインストール

/usr/src/app # cd p5-test
/usr/src/app/p5-test # npm install --save p5

利用したいファイル上で p5 をインポート

  • 今回はvue ファイル上でインポート
import p5 from 'p5';

vue ファイルの作成

App.vue を以下のコードに置き換える

App.vue
<template>
  <div>
    <h1>Create Canvas</h1>
    <!--ボール作成ボタン-->
    <button v-on:click="creatBalls">Create Balls</button>
    <!--全ボール削除ボタン-->
    <button v-on:click="clearCanvas">Clear Canvas</button>
  </div>
  <br>
  <!--canvas を配置する位置-->
  <div id="canvas"></div>
</template>
<script>
import { onMounted, ref } from 'vue';
import p5 from 'p5';
import { p5Setup, addBalls, clearBalls } from './js/SetupP5';

export default {
  setup() {
    const P5 = ref();
    
    // マウント時に canvas を生成
    onMounted(() => {
      P5.value = new p5(p5Setup);
    });

    // ボールを追加
    const creatBalls = () => {
      addBalls();
    };

    // ボールを全て削除
    const clearCanvas = () => {
      clearBalls();
    };
    
    return {
      P5,
      creatBalls,
      clearCanvas
    };
  }
};
</script>

p5.js のファイルを作成

p5.js でボールを描画するファイルと、ボールをオブジェクトとして管理するための Ball class の二つのファイルを作成

p5.js ファイル (SetupP5.js)

SetupP5.js
import { Ball } from './class/ballClass';

// 生成されるボールの数
const numBalls = 2;
// canvas の背景色
const bgcol = 25;
// ボールクラスを保存する配列
let ballArr = [];
// ボール追加フラグ
let addBallsFlag = false;
// ボール削除フラグ
let clearBallsFlag = false;

// vue ファイルで p5 インスタンスに渡す関数
const p5Setup = function(p5) {
  // はじめに呼ばれる
  p5.setup = () => {
    // canvas 生成
    const canvas = p5.createCanvas(500, 500);
    // <div id="canvas"> に canvas を配置
    canvas.parent('canvas');
    // canvas の背景色
    p5.background(bgcol);
    // canvas 内の動きをなめらかにする
    p5.smooth();
    // draw()を 1/30秒ごとに実行
    p5.frameRate(30);
  };

  // 1/frameRate 秒ごとに呼ばれる
  p5.draw = () => {
    p5.background(bgcol);
    for (let i=0; i<ballArr.length; i++) {
      const thisBall = ballArr[i];

      // ボールの位置を計算
      thisBall.update(p5)

      // マウスが押されていれば、マウス方向へボールを移動
      if (p5.mouseIsPressed) {
        thisBall.agg(p5, p5.mouseX, p5.mouseY);
      }
    }

    // ボールを追加
    if (addBallsFlag) {
      for (let i=0; i<numBalls; i++) {
        const thisBall = new Ball(p5);
        thisBall.drawBall(p5);
        ballArr.push(thisBall);
      }
      addBallsFlag = false;
    }

    // ボールを全て削除
    if (clearBallsFlag) {
      ballArr = [];
      clearBallsFlag = false;
    }
  };
};

// ボール追加フラグの変更
const addBalls = () => {
  addBallsFlag = true;
}

// ボール削除フラグの変更
const clearBalls = () => {
  clearBallsFlag = true;
}

export { p5Setup, addBalls, clearBalls };
  • p5Setup 関数内に、p5 関連の処理を全て記述し、vue ファイルの p5 インスタンスに渡す
  • ボールの追加・削除は、vue 側からフラグを変更する関数を呼び、フラグをもとに p5Setup() 内でボールの追加・削除を実行

Ball class

ballClass.js
export class Ball {

  constructor (p5) {
    // ボールが描画される座標
    this.x = p5.random(p5.width);
    this.y = p5.random(p5.height);

    // 描画されるボールの半径、色、色の透明度
    this.radius = p5.random(10) + 10;
    this.fillcol = p5.color(p5.random(255), p5.random(255), p5.random(255));
    this.alph = 0;
  }
    
  // ボールを描画
  drawBall(p5) {
    // ボールの輪郭の線を消す
    p5.noStroke();
    // 色を設定
    p5.fill(this.fillcol, this.alph);
    // (x, y) に半径 radius の円を描画
    p5.ellipse(this.x, this.y, this.radius*2, this.radius*2);
  }
  
  // ボールの位置を更新
  update(p5) {
    this.x += p5.random(-3, 3);
    this.y += p5.random(-3, 3);

    // 境界条件
    if (this.x > (p5.width + this.radius)) {
        this.x = 0 - this.radius;
    }
    if (this.x < (0 - this.radius)) {
        this.x = p5.width + this.radius;
    }
    if (this.y > (p5.height + this.radius)) {
        this.y = 0 - this.radius;
    }
    if (this.y < (0 - this.radius)) {
        this.y = p5.height + this.radius;
    }

    // 更新した座標で再描画
    this.drawBall(p5);
  }

  // マウスのクリック位置に集まる
  agg(p5, mouseX, mouseY) {
    const v = 0.02;
    // ボールとマウスの位置を計算
    let d = p5.dist(this.x, this.y, mouseX, mouseY)

    // 距離が 2 より大きければ、マウスに近づくように座標を更新
    if (d>2) {
        this.x += v*(mouseX-this.x) + p5.random(-10, 10);
        this.y += v*(mouseY-this.y) + p5.random(-10, 10);
    }

    // 更新した座標で再描画
    this.drawBall(p5);
  }
}
  • ボール一つ一つをオブジェクトとして管理
  • ボールの位置はランダムに -3 から 3 の範囲で動く
  • マウスクリック時、マウス方向へ v の大きさ + -10 から 10 の範囲でランダムに移動
  • 境界条件は端に来たら逆の端に移動する
    • 某 RPG と同じ

動かす

プロジェクトのルートで実行

/usr/src/app/p5-test # npm run serve

localhost: にアクセス

image.png

まとめ

今回は Vue.js のコンテナ内で、CompositionAPI の記法で p5.js を動かしてみました。
ほとんど CompositionAPI の旨みは使っていないですが、Vue.js で p5.js を動かせたので良しとします。
ボール同士の衝突や複数の相互作用を入れたり、他のモデルを実装して遊んでみようと思います。

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?