サイトロゴ

JavaScriptでお絵描き p5.js

著者画像
Toshihiko Arai
JavaScriptでお絵描き
p5.js

はじめに

久しぶりにプログラミングでお絵描きをしたいと思った。 以前にProcessingやPython、OpenCVなどでお絵描きというかアニメーションのテストをしたことがあるが、今回はJavaScriptで実現してみたいと思った。


JavaScriptでお絵描きができる「p5.js」

そこで方法を探していたところ「p5.js」が良さそう。 「p5.js」はProcessingの概念をJavaScriptに移植する形で誕生。HTML5のCanvasを使って描画される。

「p5.js」をはじめる

「p5.js」を手っ取り早くはじめるには、CDNで提供されている「p5.js」ライブラリをHTMLに読み込むだけ。

<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.js"></script>

あとは <script></script> 内に、Processingのようなプログラミング形式でスクリプトを記述していく。 setupは最初に一度だけ呼ばれる関数なので、キャンバスの作成や初期設定を記述する。drawは毎フレーム呼ばれるループ関数である。 Arduinoをやったことのある人なら、同じ感覚でプログラミングできるため理解も簡単だろう。

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

function draw() {
    background(220);
    ellipse(200, 200, 50, 50);
}

キャンバスを画像で保存

描画したキャンバスをjpgファイルとして保存したい場合があるだろう。その場合はmousePressed関数にsaveCanvasを実装すれば良い。

function mousePressed() {
    saveCanvas('test1', 'jpg');
}

キャンバスをクリックすると画像として保存できる。とても便利だ。

アニメーション、ボールを動かす

次に、ボールを動かしてみよう。さらに壁に当たったら、反射するようになっている。

let x = 250, y = 200;
let xSpeed = 10, ySpeed = 7;

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

function draw() {
    background(220);
    ellipse(x, y, 50, 50);
    x += xSpeed;
    y += ySpeed;

    if(x > width - 25 || x < 25) xSpeed *= -1;
    if(y > height - 25 || y < 25) ySpeed *= -1;
}

アニメーション、ボールを動かす応用

さらに発展させて、クリックするたびにボールが追加してランダムな色・スピード・方向へ動かしてみよう。 オブジェクト指向なプログラミングアプローチとして、Ballクラスを定義すると扱いやすくなる。

let balls = [];

function setup() {
    createCanvas(400, 400);
    // 初期状態で5個のボールを生成
    for (let i = 0; i < 5; i++) {
        balls.push(new Ball(random(25, width - 25), random(25, height - 25), random(-5, 5), random(-5, 5)));
    }
}

function draw() {
    // 半透明の背景でトレイル効果を出す(alpha値を50に設定)
    background(30, 30, 30, 80);

    // 各ボールの動きと描画
    for (let ball of balls) {
        ball.move();
        ball.display();
    }
}

// クリックで新しいボールを追加
function mousePressed() {
    balls.push(new Ball(mouseX, mouseY, random(-5, 5), random(-5, 5)));
}

// ボールのクラス定義
class Ball {
    constructor(x, y, xSpeed, ySpeed) {
        this.x = x;
        this.y = y;
        this.xSpeed = xSpeed;
        this.ySpeed = ySpeed;
        this.size = random(20, 40);
        this.r = random(100, 255);
        this.g = random(100, 255);
        this.b = random(100, 255);
    }

    move() {
        this.x += this.xSpeed;
        this.y += this.ySpeed;

        // 壁に当たったら跳ね返る処理
        if (this.x > width - this.size/2 || this.x < this.size/2) {
            this.xSpeed *= -1;
        }
        if (this.y > height - this.size/2 || this.y < this.size/2) {
            this.ySpeed *= -1;
        }
    }

    display() {
        noStroke();
        fill(this.r, this.g, this.b);
        ellipse(this.x, this.y, this.size);
    }
}

点と線を結ぶジェネラティブアート風アニメーション

▼ キャンバスをクリックで開始/一時停止
let points = [];
let velocities = [];
const num = 64;
const radius = 2;
const maxSpeed = 3;
const connectionDistSq = 6000;

function setup() {
    createCanvas(600, 400);
    stroke(222);
    fill(222);
    for (let i = 0; i < num; i++) {
        points.push(createVector(random(width), random(height)));
        velocities.push(p5.Vector.random2D().mult(random(maxSpeed)));
    }
    frameRate(30)
}

function draw() {
    background(0);

    // 点の移動と描画
    for (let i = 0; i < num; i++) {
        let pt = points[i];
        let v = velocities[i];
        pt.add(v);

        // 反射
        if (pt.x < 0 || pt.x > width) v.x *= -1;
        if (pt.y < 0 || pt.y > height) v.y *= -1;

        circle(pt.x, pt.y, radius * 2);
    }

    // 線の描画
    for (let i = 0; i < num; i++) {
        for (let j = i + 1; j < num; j++) {
            let d = distSq(points[i], points[j]);
            if (d < connectionDistSq) {
                line(points[i].x, points[i].y, points[j].x, points[j].y);
            }
        }
    }
}

function distSq(p1, p2) {
    let dx = p1.x - p2.x;
    let dy = p1.y - p2.y;
    return dx * dx + dy * dy;
}