最近、ジェネレーティブアートに興味があって
p5.jsとか使ってなにかCGアート作品作りたいなぁと考えています。
そのようなCG作品が沢山投稿されてるCodepenっていうサイトがあります
Codepenとか覗いていると、閲覧中によく右上にあるGUIでCGのパラメータを
変更できるパーツがあって、あれって何かなあとずっと疑問に思っていたので調べました。
dat.GUI とは
Googleのデータアート部門の人が作った、Javascript製の
パラメーター変更のためのグラフィカルで軽量なUIです。
簡単な書き方でササッと入力用GUIを追加できるので、CGアート界隈の人がよく使ってる感じらしいです。
React + dat.GUI
dat.GUIでJavascriptの変数を変更できるのはわかりました。
Reactは単方向データバインディングなので、
Reactとdat.GUIを組み合わせようと思ったら、dat.GUIのパラメータ変更時にsetState()を実行しないといけません。
そのあたりのノウハウを実践してみたので共有したいと思います。
プロジェクトを作る
create-react-appでさくっと作ります。
最近はまってるのでTypescriptでやります。
yarn create react-app react_dat --template typescript
必要なライブラリを追加
dat.GUIに加えて、
state管理にunstated-nextを使います
- dat.gui
- @types/dat.gui
- unstated-next
yarn add dat.gui @types/dat.gui unstated-next
unstated-next
unstated-next は React Context Hook を使いやすくしたライブラリです。
基本的な概念としては、
- stateを管理・変更通知するContainerオブジェクトを作る
-
<Container.Provider>
でコンポーネントをラップする - ラップされた内部では
Container.useContainer()
でstateが使える - stateに変更があったら、Providerがラップしているコンポーネントツリーを更新する
みたいな感じ?
偉大な先駆者様の解説記事があるのでそちらを見ると良いでしょう
https://qiita.com/kaba/items/b05f680f850dd46548f3
GUIContainerを作る
React Hooksをたくさん使います。
- useState
- useMemo
- dat.GUI オブジェクトをMemonizeする(再レンダリングしても同じメモリ位置のオブジェクトを使う)
- useEffect
- 最初の1回だけguiにプロパティを追加する
import dat from 'dat.gui'
import { createContainer } from 'unstated-next'
import { useState, useMemo, useEffect } from 'react'
const GUIContainer = createContainer(() => {
const [message, setMessage] = useState('dat.gui')
const [speed, setSpeed] = useState(0.8)
const [flag, setFlag] = useState(false)
const [fruit, setFruit] = useState('apple')
const [number, setNumber] = useState(1)
const gui = useMemo(() => new dat.GUI(), [])
useEffect(() => {
gui.add({ message }, 'message').onChange(value => setMessage(value))
gui.add({ speed }, 'speed', -5, 5).onChange(value => setSpeed(value))
gui.add({ flag }, 'flag').onChange(value => setFlag(value))
gui.add({ fruit }, 'fruit', [
'apple',
'orange',
'grape'
]).onChange(value => setFruit(value))
gui.add({ number }, 'number', {
one: 1,
two: 2,
three: 3
}).onChange(value => setNumber(value))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return { message, speed, flag, fruit, number }
})
export default GUIContainer
ちょっと長いファイルになりましたが、dat.GUIの紹介ということで
色々なフォームを詰め込んで見ました。
基本的には、以下のような書式でGUIフォームを追加できます。
import dat from 'dat.gui'
let gui = new dat.GUI()
gui.add(Object, "プロパティ名").onChange(value => /* code */)
.onChange() メソッドは、変更されたvalueを受け取ってなにか処理をするコールバックを登録できます。
このコールバックの中で**setState()**を実行すれば、ReactDOMを再描写できるというわけですね。
作ったGUIコンテナのstateを使う
import React from 'react'
import GUIContainer from './GUI'
const App: React.FC = () => {
const { message, speed, flag, fruit, number } = GUIContainer.useContainer()
return (
<>
<p>{message}</p>
<p>{speed}</p>
<p>{`${flag}`}</p>
<p>{fruit}</p>
<p>{number}</p>
</>
)
}
export default () => (
<GUIContainer.Provider>
<App />
</GUIContainer.Provider>
)
確認してみる
yarn start
localhost:3000
にブラウザでアクセス
まとめ
dat.GUI と React を組み合わせるノウハウを書いてみました。
dat.GUIはとても手軽に入力用のGUIを追加できるので、
デジタルアート以外でも、仕事でプロトタイピングのwebアプリの入力を試したい
なんてときにも役に立つと思います。
終わり。