PixiJS で 描いてみた
タイトルの通りです。
JavaScriptを覚えるためになにか面白い素材がないかなぁと思っていて、前回Canvasを使って日本語で円を描いたりしましたが、、本格的なコンピュータグラフィックスのライブラリがいくつか見つかり、じゃぁコレを使ってみようとなりました。
最初は p5.js をさわっていたんですが、setup() と draw() という関数を使いコーディングするというスタイルは、分野は違うけど電子工作の Arduino言語の setup() と loop() と通じる分かりやすさがあって、すぐに記述することもできました。でもグラフィックスを扱うにはちょっと力不足じゃないのかなぁと思うようになり、PixiJSに乗り換えました。
とはいえ、どちらもJavaScriptが基本なので、描画するためのライブラリ関数の呼び出し方が違う以外はそんなに差異はありません。また私自身 クラス(オブジェクト指向)にも慣れていたので、そんなに苦労はありませんでした。
でもそれが逆につまづく要因になりました。
現在のJavaScriptはNode.jsとnpmの環境がほぼ必須なんですね。
それがわからなかったので、importする際の「パッケージ」という概念がどうしても理解できず、ものすごく遠回りしてしまいました。バンドルファイルを生成するとかそれらの話をするととんでもなく長くなるし、環境構築はこの記事の本来の目的ではないので、ばっさりと省略。
さて、今回描いてみたのは、いわゆる幾何学模様。この画像はなんて言うんでしょう。。
直線を座標をずらしながら回転させています。原理としては以下の画像です。わかりやすいようにそれぞれ色を変えていますが、単位時間ごとに座標を変えてプロットします。
実際に生成する画像は、一辺が300pxの四角形を10pxずつずらしながら、4辺を同時にプロットしながら直線を回転させるというのを描いてみました。4辺同時をわかるように4色であらわしています。ついでに回転スピードも可変できます😊
https://x.com/moegiiro_com/status/1812354587543957556
もともとp5.jsで作ったスプリプトをPixiJSに書き直したので、p5.jsのsetup()とdraw()はほとんど再利用しています。
あと、個人的なインデントの仕方やマルチステートメントを多用しているので、コーディングスタイルが気持ち悪いかもしれませんが、趣味のスクリプトなので気にしていません。
/lib/pixi.min.mjs ファイルはPixiJSのオフィシャルサイトからダウンロードしてください。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Practice 15</title>
<style>
html, body { margin: 0; padding: 0; }
div#canvasContainer { margin:0 auto; padding-top: 50px; background-color:black; text-align: center; color:white; height: 450px; }
div#sliderContainer { padding : 20px; background-color:black; text-align: center; color:white; }
</style>
</head>
<body>
<div id="canvasContainer"><h1>Pixi JS 8</h1></div>
<div id="sliderContainer">
<label for="speedSlider">Speed:</label>
<input id="speedSlider" type="range" min="1" max="5" step="1" value="3">
<span id="sliderValue">3</span>
</div>
</body>
</html>
<script type="module">
function setup() { // p5.jsのsetup()関数
const zero = 0;
let sttX = 0, sttY = 0, endX = 0, endY = 0;
let sideNum = 0, sideLoop = 0;
for (let countLoop = 0; countLoop < (Size * 4); countLoop++) {
switch (sideNum) {
case 0: sttX = zero + sideLoop ; sttY = zero ; endX = Size ; endY = zero + sideLoop ; break;
case 1: sttX = Size ; sttY = zero + sideLoop ; endX = Size - sideLoop ; endY = Size ; break;
case 2: sttX = Size - sideLoop ; sttY = Size ; endX = zero ; endY = Size - sideLoop ; break;
case 3: sttX = zero ; sttY = Size - sideLoop ; endX = zero + sideLoop ; endY = zero ; break;
}
if ((countLoop % Step) == 0) {
Plot.push(`${sttX},${sttY},${endX},${endY}`);
}
sideLoop++;
if (sideLoop >= Size) {
sideLoop = 0;
if (sideNum < 3) {
sideNum++;
}
else {
sideNum = 0;
}
}
}
}
function draw() { // p5.js の draw()関数
side0 = Plot.length / 4 * 0 + countLoop ; side0 = (side0 < Plot.length)? side0: side0 - Plot.length;
side1 = Plot.length / 4 * 1 + countLoop ; side1 = (side1 < Plot.length)? side1: side1 - Plot.length;
side2 = Plot.length / 4 * 2 + countLoop ; side2 = (side2 < Plot.length)? side2: side2 - Plot.length;
side3 = Plot.length / 4 * 3 + countLoop ; side3 = (side3 < Plot.length)? side3: side3 - Plot.length;
// console.log(Plot.length, countLoop, side0, side1, side2, side3);
[sttX, sttY, endX, endY] = Plot[side0].split(",") ; line.plot(sttX, sttY, endX, endY, 1, setColor[0]);
[sttX, sttY, endX, endY] = Plot[side1].split(",") ; line.plot(sttX, sttY, endX, endY, 1, setColor[1]);
[sttX, sttY, endX, endY] = Plot[side2].split(",") ; line.plot(sttX, sttY, endX, endY, 1, setColor[2]);
[sttX, sttY, endX, endY] = Plot[side3].split(",") ; line.plot(sttX, sttY, endX, endY, 1, setColor[3]);
countLoop++;
if (countLoop >= (Plot.length)) {
countLoop = 0;
}
}
// グローバル変数定義
const Size = 300; // 四角形の一辺の長さ px
const Step = 10; // 一辺のピッチ幅 px
let Plot = []; // 直線座標を格納する
// draw()用変数定義
const setColor = ['white', 'red', 'green', 'blue'];
let countLoop = 0;
let side0 = 0, side1 = 0, side2 = 0, side3 = 0;
let sttX = 0, sttY = 0, endX = 0, endY = 0;
// ループのスピードをコントロールする
let iid; // インターバルループのループ中のid
let delay = 20; // デフォルト値 100 milliseconds (0.1 seconds)
// スライダーの定義
const slider = document.getElementById('speedSlider');
const sliderValue = document.getElementById('sliderValue');
const delayValue = [70, 40, 20, 15, 10]; // 100 milliseconds (0.1 seconds)
// スライダーイベントを取得し、その値によるループタイミングの間隔を変える
slider.addEventListener('input', (event) => {
const speed = event.target.value;
sliderValue.textContent = speed;
delay = delayValue[speed - 1];
// イベントが発生しなければ setInterval()で定義された関数を永久ループする
clearInterval(iid); // 現在のintervalをクリア
iid = setInterval(draw, delay); // 新しいdelayでintervalを設定し draw関数を実行
});
// PixiJSライブラリの読み込み
import * as PIXI from '/lib/pixi.min.mjs';
const app = new PIXI.Application();
await app.init({ width: Size, height: Size, backgroundColor: 'black' });
document.getElementById('canvasContainer').appendChild(app.canvas);
// 線を引く lineクラスを定義
class PixiLine {
constructor() {
this.lineGraph = new PIXI.Graphics();
}
plot(x1, y1, x2, y2, width, color) {
// this.lineGraph.clear (); // 以前の描画をクリア
this.lineGraph.moveTo(x1, y1);
this.lineGraph.lineTo(x2, y2);
this.lineGraph.stroke({width: width, color: color});
}
}
// lineインスタンスを生成しキャンバスにステージする
const line = new PixiLine();
app.stage.addChild(line.lineGraph);
// スタート!
setup();
slider.dispatchEvent(new Event('input')); // スクリプトで強制的にイベントを発生させる
</script>
だいぶ慣れてきたので、次の目標にむかっていろいろ進めています。