編み図エディタ開発withPixiJS振り返り
概要
編み物で使う編み図を作成するサービスを作れないかなと思って 編み図エディタ Amimo を作ってみました。その開発の途中であれこれやってみたことの振り返りです。いつも通りさらっと読めるように書くつもりなのでよかったら数分のお茶のお供にでもどうぞ 🍵 開発中に学んだこととか気づいたこととかを少し時間をおいて振り返ることで忘れにくくなったらいいなあというやつです。
編み図とは
知らなくても読むのに支障ないですが簡単に説明すると、編み物のレシピみたいなものでどういう編み方をどのくらいどこにしたら作品が作れるかを示します。例えばこちらのあゆむーずさんのレシピだとバツマークのようなものや開閉に使うアイコンみたいなマークのついた図形がそれです。
Input? Drag&Drop?
最初に悩んだのは編集方法でした。例えばパターンの繰り返しになることが予想できる円形だとしたら、1. 円を何周重ねて 2. 何編みを何種類するパターンを 3. 何回繰り返すをinputで入力して一気に図形で出力する形を考えていました。
ただこれが意外とどう自動で配置したものか難しく、また円形以外では同じ方法が取りにくいことから実装を考える段階で少し行き詰まります。そこでたまたま世間話的に先輩兼友人の方々にこの話をしたところ「ドラッグアンドドロップじゃないんだね〜」とコメントをもらって、ああそういうアプローチもあるな!と何かが開けました。なんで頭に浮かばなかったのか今思うと不思議なくらいですが一人でやっていると発想が偏ってしまうのかもしれません。やっぱり自分以外の人と開発した方が何倍も効率的だし楽しいですね。
編み図の目数はそこそこあるので1つ1つドラッグアンドドロップは煩雑すぎるかと言うことで、中間をとって(?)お絵描きソフトのようになぞったところに編み目記号を配置していく今の形に落ち着きました。
Canvas? SVG? HTML?
次に悩んだのは図形の描画ベースを何にするかです。編み図の記号は頑張れば手書きSVGでどうにかできそうなシンボルが多いし、SVGは拡大などもスムーズにできると思ったので一旦SVGで実装を始めました。
グリッド等の描画自体はSVGだととても簡単でしたが、色々な種類の要素から複数種のイベントを取得しないといけなかったり、mousemove
などのイベントに応じて新たにたくさんの図形を描写したりと結果的に処理はJSに寄せた方が良さそうなことがわかったのでSVGはやめて、Canvasベースのライブラリを使ってみることにしました。
一方で、編み目記号を選択するボタンは描画内容が固定で内容もそこまで複雑ではないので SVG で用意しました。
PixiJS を初めて使った所感
名前は結構見かけるイメージだったのですでに相当使われているのかなと思ったのですが、思ったよりは日本語での情報数が少ないんだなと感じました。公式リファレンスもそこまで言葉豊かに説明している印象ではなかったので公式サンプル集も合わせてみると良いかも。
ただやはりグラフィックに特化しているのであのままSVGを貫くより圧倒的に良い選択肢だったと思います。例えば多角形を描くとか楕円を描くとか、はたまたイベントの判定エリアの指定だとかローカル/グローバル座標だとか図形を描くときにハンディなインターフェイスがたくさん準備されているので、ビジュアルの操作が多いものを作るときはこういうライブラリがあるとだいぶ開発体験が変わりそうです。
PixiJS のイベントにハマる
ちょくちょくイベントの引数のオブジェクトの参照が使いまわされています。InteractionEvent
などのオブジェクトごと値を一時保存したりしていると、例えば図形の移動適用前と適当後で値の変化が0になってしまって何も起こらないとか、デバッグ用にconsole.log
しても target が null なのになぜか画面上では処理が起きているとかで軽く沼にはまったりしました。必ず値そのものを扱うようにしたほうが良さそうです。
PixiJS の pivot にハマる
pivotは画像を回転する時などの基準点を指定するものです。デフォルトは左上ですが、今回は中心をピン留めした形で回転させたかったので、pivot を中心に変更する必要がありました。コンセプト自体は難しくないのでここで手間取るとは思っていなかったのですが、なんと図形が変な位置に移動します。
色々調べた結果と手元で試した結果、pivot を変更すると元々ローカル座標で (0,0) が存在していた左上の部分に図形の中心部が来るように移動してから回転してしまうみたいでした。なので、解決策としては最初からローカル座標 (0,0) が中心に来るように図形を描画するか、最初から pivot を中心にしておいて配置もそれを考慮した位置にするか、基準点を考慮した回転後の左上の座標を自分で計算して設定するになるかと思います。私は後者を選択しました。(次で詳しく話します)
行列を学び直す
編み図は結構奥が深く、単にグリッドに編み目記号が置けるだけではほぼ使い物になりません。2目を1目にまとめる編み目は中間にないといけないし、ベースは四角の編み図でもふちにレースのような飾りをつけるだけで直線以外の配置が必要になります。
そのため、移動・回転・自由変形などいわゆるペインティングツールのバウンディングボックス相当の機能が必要です。移動までは簡単ですが、回転などが入り始めたあたりから三角関数・行列が徐々に必要になることがわかっていました。あまり数学好きではないのでどこまでやろうか…という感じだったのですが、ある日 Twitter のタイムラインで CodingOcean さんの行列の動画 に出会います。
これが奇跡的にやりたいことにどんぴしゃで、説明もわかりやすくてなんとなくクセになるトーンも良く、まんまと自分でもやりたくなったので先ほど書いたように自分で回転後の座標を計算して編み図を配置する方式を採用するに至りました。
Canvas のドローイングモデル
これはキャンバスあるあるみたいなのですが PixiJS の getImage
でキャンバスの内容を画像として出力するときはレンダリングと同じイベント内で実行する必要があります。
getImage(){
this.app.render(); // これがないと真っ黒になるよ
const image = this.app.renderer.plugins.extract.base64(null,'image/jpeg');
return image;
}
この stackoverflow のアンサーと Canvas 描画実行の流れに詳しい説明がありますが、キャンバスはデフォルトで2つのバッファを持っていて、これらをスワップすることで高速な描画を実現しています。そのため毎コンポジット後に描画内容はクリアされて真っ黒になっており、(自分が想定している)描画内容を取得するにはレンダリングと同じスコープで呼ぶ必要があります。
PixiJS のコンテナにハマる
初期の実装はログが残っていなくて確かな説明ができないのですが、最初の頃はキャンバスの大元の参照になる PIXI.Application
(もしくはその下の stage
) の下に PIXI.Container
を作成してグリッドを描画してイベントを取得していました。この時やたら Mac がウィンウィン言うし熱くなるので開発者ツールでパフォーマンスをチェックしてみたらなんとページを開いているだけで CPU 50% くらいいっていました。
色々調べたりつけたり外したりしてコンテナを外しても挙動が変わらないし CPU は 10% くらいまで落ち着くことがわかったので一旦 PIXI.Container
なしで進めることにしました。このポストが関係ありそうなのですが一旦実装を優先したかったので原因はきちんと追いかけてられていません。
まとめ
ゲーム座標だったり、ベースにグラフィックを作成して配置していく感覚だったりアニメーションライブラリのベースの考え方みたいなものに親しむことができたので PixiJS を使ってみてよかったです。 Canvas(WebGL) は過去にあまり良い思い出がなく敬遠しがちでしたが今回の開発で印象がフラットに近づいたので今後は必要に応じて使ってみようと思える予感がします。これまであまり数学的な要素を使ってビジュアルを作るみたいなことをしてこなかったのでこれも良い経験になりました。
最後に、実は私自身はもっぱら素晴らしい編み図にしたがって編むばかりで自ら編み図を作成するようなスキルは全くないのですが、もしどなたかの役に立ちそうだったら嬉しいです。正直やりきれていないことはたくさんありまだ全然ベータ版なので時期を見てまた続きを作れたらいいなと思います。
Discussion