ruby.wasm + p5.js の組み合わせです。ほとんどの API は移植したので大体同じことができると思います。
- https://p5rb.ongaeshi.me/examples に色々サンプル。
- https://p5rb.ongaeshi.me/editor にオンラインエディタがあります。
使い方
p5.rb を HTML に読みこめばすぐに使えます。
<html> <head> <script src="https://cdn.jsdelivr.net/npm/ruby-3_2-wasm-wasi@next/dist/browser.script.iife.js"></script> <script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.js"></script> <script type="text/ruby" src="p5.rb"></script> <script type="text/ruby"> def setup createCanvas(720, 400) end def draw background(127) noStroke() 0.step(height, 20) do |i| fill(129, 206, 15) rect(0, i, width, 10) fill(255) rect(i, 0, 10, height) end end P5::init() </script> </head> <body> <main> </main> </body> </html>
オンラインエディタ
https://p5rb.ongaeshi.me/editor からオンラインエディタが使えます。
作ったものはURLで共有できます。モバイルからも使えるのでまずはこちらを触ってみるのがおすすめです。
できたものは #p5rb タグを付けて共有していただけるとよろこびます。
実装メモ
ruby.wasm の JS::Object
経由で p5.js のメソッドやプロパティをブリッジしています。Ruby の method_missing を使うことで 100 行程度で p5.js の全ての関数やプロパティを呼び出しています。method_missiong すごい。
# JS::Object can call property via function style class JS::Object def method_missing(sym, *args, &block) ret = self[sym] case ret.typeof when "undefined" str = sym.to_s if str[-1] == "=" self[str.chop.to_sym] = args.first return args.first end super when "function" self.call(sym, *args, &block).to_r else ret.to_r end end def respond_to_missing?(sym, include_private) return true if super self[sym].typeof != "undefined" end def to_r case self.typeof when "number" self.to_f when "string" self.to_s else self end end end # Call p5.js global functions $p5 = nil def method_missing(sym, *args, &block) return super unless $p5.respond_to?(:[]) ret = $p5[sym] case ret.typeof when "undefined" # str = sym.to_s # if str[-1] == "=" # $p5[str.chop.to_sym] = args.first # return args.first # end super when "function" $p5.call(sym, *args, &block).to_r else ret.to_r end end # Add new p5() to window.constructors.p5() JS.eval("window.constructors = { p5: (...args) => new p5(...args) };") module P5 Vector = JS.global[:p5][:Vector] module_function def init(query = "main", obj = self) unless query.is_a?(String) query, obj = "main", query end $p5&.remove() $p5 = nil sketch = ->(p5) { $p5 = p5 init_method(obj, :preload) init_method(obj, :setup) init_method(obj, :draw) init_event_method(obj, :mouseMoved) init_event_method(obj, :mouseDragged) init_event_method(obj, :mousePressed) init_event_method(obj, :mouseReleased) init_event_method(obj, :mouseClicked) init_event_method(obj, :doubleClicked) init_event_method(obj, :mouseWheel) init_event_method(obj, :keyPressed) init_event_method(obj, :keyReleased) init_event_method(obj, :keyTyped) } container = JS.global.document.querySelector(query) container.innerHTML = "" JS.global.window.constructors.p5(sketch, container) end def init_method(obj, sym) if obj.respond_to?(sym, true) m = obj.method(sym) $p5[sym] = ->() { m.call() } end end def init_event_method(obj, sym) if obj.respond_to?(sym, true) m = obj.method(sym) if m.parameters.count >= 1 $p5[sym] = ->(e) { m.call(e) } else $p5[sym] = ->(e) { m.call() } end end end end
おわりに
作ったものがあったら #p5rb タグを付けて放流してもらえたら嬉しいです。
全ての戻り値に to_r するのはやりすぎかもしれないけど、プロパティを Ruby の括弧無しメソッド呼び出しで呼べるのは本家でできてもいいのかもと思っています(後で PR 出してみよう)。