🔗目次
🔗まえがき
皆さんどうもこんにちは、株式会社ECN所属のFuseです。
突然ですが皆さんはノードベースエディタ、お好きですか?
私は大好きです。UnrealEngine5やBlenderなど、いつもお世話になっています。
そんなノードベースエディタをReactで作れるライブラリを最近見つけました。
その名はFlume、
どうやらUIを作るだけでなくロジックも仕込めるらしく、これとOSCで何か面白いアプリを作れないか?と考えていたところいい案を思いついたので早速執筆に取り掛かることにしました。
🔗作りたいもの
突然ですが皆さんは2DサンドボックスゲームTerraria
に登場するアイテム群Hair Dyes
をご存じでしょうか?
髪の色がHPやMPといった自身の状態や時間帯や高度といった周辺の環境によって動的に変わるようになるアイテムです。
あれをVRChatでも再現したいと思ったのですが、VRChat内で読み取れる情報には限界があります。
そこでOSCを使うことで様々な条件に応じた髪色変更を可能にしてしまおう。
ついでに扱いやすいようにノードベースエディタもつけてしまおうという算段です。
ですが全部一気にやると記事の長さがものすごいことになりそうなので環境構築やキャッチアップを行う学習編と実装を行う実践編に分割します。
↑目次に戻る
🔗Lorcaって?
LorcaはモダンなHTML5デスクトップアプリを作るためのライブラリで、
Electronとは異なりアプリにChromeをバンドルするのではなく、すでにインストールされているものを再利用するためビルド後のサイズが非常に軽量、
重い動作やブラウザからは触れない部分をGo言語に任せることで高速に様々な操作が行えるなどの強みがあります。
🔗環境構築
まずは環境構築です。いつものようにリポジトリを作成して…
そしたら中に入って go mod init
、
go mod init 1_osc_app
その後 lorcaやginなどの必要なライブラリを go get
します。
go get github.com/zserge/lorca
go get github.com/gin-gonic/gin
go get github.com/gin-contrib/static
その後、リポジトリ内でviteアプリを作成し、ビルドします。
yarn create vite lorca_osc_frontend --template react-ts
cd ./lorca_osc_frontend
yarn install
yarn build
その後、 main.go
にビルドしたファイルをginで配信する処理とlorcaでそれを見る処理を書きます。
main.go
package main
import (
"log"
"github.com/gin-contrib/static"
"github.com/gin-gonic/gin"
"github.com/zserge/lorca"
)
var staticDir = "./lorca_osc_frontend/dist"
func main() {
go runGin()
err := runLorca()
if err != nil {
log.Fatal(err)
}
}
// フロントエンドを配信するサーバー
func runGin() {
router := gin.Default()
router.Use(static.Serve("/", static.LocalFile(staticDir, true)))
err := router.Run("127.0.0.1:9000")
if err != nil {
log.Fatal(err)
}
}
// lorcaを起動する
func runLorca() error {
ui, err := lorca.New("http://localhost:9000", "", 960, 720, "--remote-allow-origins=*") //--remote-allow-origins=*がないと動かないので注意
if err != nil {
return err
}
<-ui.Done()//ブラウザの終了を待つ
return nil
}
いつもの画面が表示されれば環境構築は成功です。
🔗Flumeを使ってみる
それでは試しにFlumeを使って簡単な計算機を作ってみましょう。
早速yarn add
し、触ってみましょう。
yarn add flume
今回作るのは足し算だけの簡単な計算機です。
まずはコンフィグを生成しポートで使う型とノードを定義していきます。
lorca_osc_frontend/src/const/config.ts
import { Colors, Controls, FlumeConfig } from "flume"
const config = new FlumeConfig()
config
.addPortType({
type: "number",
name: "number",
label: "数値",
color: Colors.red,
controls: [
Controls.number({
name: "number",
label: "数値"
})
]
})
.addNodeType({
type: "number",
label: "数値",
description: "数値を出力する。",
inputs: ports => [
ports.number()
],
outputs: ports => [
ports.number()
]
})
.addNodeType({
type: "add",
label: "加算",
description: "2つの入力を足した結果を出力する。",
inputs: ports => [
ports.number({ name: "a", label: "A" }),
ports.number({ name: "b", label: "B" }),
],
outputs: ports => [
ports.number({ name: "result", label: "結果" })
]
})
.addRootNodeType({
type: "result",
label: "結果",
initialWidth: 170,
inputs: ports => [
ports.number({
name: "result",
label: "結果"
}),
]
})
export default config
結構長いですが、順を追って説明していきます。
型の定義
.addPortType({
type: "number",
name: "number",
label: "数値",
color: Colors.red,
controls: [
Controls.number({
name: "number",
label: "数値"
})
]
})
FlumeConfig.addPortType
メソッドで型を定義できます。
type
はその型の識別子で、一意である必要があります。
name
はポートのデフォルトの名前で、typeと同じ文字列にすることが推奨されています。
label
はノード上に実際に表示される名前で、日本語が使えます。
また、color
を指定することでポートの丸い部分の色を型ごとに決めることができます。
また、controls
を追加すればユーザーに値を入力してもらうこともできます。
ノードの定義
型の定義が終わったら次はノードの定義です。
FlumeConfig.addNodeType
メソッドを使います。
.addNodeType({
type: "number",
label: "数値",
description: "数値を出力する。",
inputs: ports => [
ports.number()
],
outputs: ports => [
ports.number()
]
})
.addNodeType({
type: "add",
label: "加算",
description: "2つの入力を足した結果を出力する。",
inputs: ports => [
ports.number({ name: "a", label: "A" }),
ports.number({ name: "b", label: "B" }),
],
outputs: ports => [
ports.number({ name: "result", label: "結果" })
]
})
type
は型のときと同じく識別子。やはり一意である必要があります。
label
、description
はノードの名前と説明文で、日本語が使えます。
最後にinput
とoutput
で入力ポートと出力ポートを定義すれば完成です。
ポートのnameはロジックの作成時に使うので覚えておきましょう。
ルートノードの定義
最後に終点となるルートノードを定義します。ルートノードへの入力は最終的な出力結果になります。
FlumeConfig.addRootNodeType
メソッドを使います。
.addRootNodeType({
type: "result",
label: "結果",
initialWidth: 170,
inputs: ports => [
ports.number({
name: "result",
label: "結果"
}),
]
})
パラメータの解説は通常のノードと同じなので割愛します。
エンジンを作る
次にノードのロジック部分を担うエンジンを作っていきます。
lorca_osc_frontend/src/const/config.ts
//@ts-nocheck
import { RootEngine, InputData, FlumeNode } from 'flume'
import config from './config'
const resolvePorts = (portType: string, data: InputData) => {
switch (portType) {
case 'string':
return data.string
case 'boolean':
return data.boolean
case 'number':
return data.number
default:
return data
}
}
const resolveNodes = (node: FlumeNode, inputValues: InputData) => {
switch (node.type) {
case 'number':
return { number: inputValues.number }
case 'add':
return { result: inputValues.a + inputValues.b }
default:
return inputValues
}
}
const engine = new RootEngine(config, resolvePorts, resolveNodes)
export default engine
入力から出力を計算し返却する単純な構造となっております。
ノードエディタとUIをつなぐ
最後に、計算結果をUIに表示できるようにしていきます。
import { NodeEditor, NodeMap, useRootEngine } from "flume";
import { useCallback, useState } from "react";
import config from "./const/config";
import engine from "./const/engine";
const App = () => {
const [nodes, setNodes] = useState<NodeMap>({})
useCallback((nodes: NodeMap) => {
// 無限ループ対策として必ずメモ化する必要がある
setNodes(nodes)
}, [])
const e = useRootEngine(nodes, engine)//先ほど作ったエンジンを使う
console.log(e)
return (
<div style={{ width: 800, height: 600 }}>
<span>計算結果:{e?.result ?? <br />}</span>
<NodeEditor
{...config}
onChange={setNodes}
defaultNodes={[
{
type: "result",
x: 190,
y: -150
}
]}
/>
</div>
)
}
export default App
useRootEngine
フックにノードのリストと先ほど作ったエンジン、あとは任意で入力値(今回は省略)を渡すことで計算結果を受け取ることができます。
計算結果の型は[inputName: string]: any
になっており入力ポートの名前(今回はresult)を指定することで中身を取り出すことができます。
ビルドする
あとはビルド用のバッチを作成し、走らせれば…
build.bat
@echo off
cd ./lorca_osc_frontend
call yarn install
call yarn build
cd ../
go generate
go build -ldflags "-H windowsgui" -o lorca-example.exe
無事、デスクトップアプリ化に成功しました!
🔗あとがき
いかがでしたか?今回はFlumeを使ったノードベースエディタの構築とLorcaを使ったデスクトップアプリ化に挑戦しました。
次回は実践編としてOSCを使ったアバターの髪色制御に挑戦していこうと思います。それでは、また次回お会いしましょう。
株式会社ECNはPHP、JavaScriptを中心にお客様のご要望に合わせたwebサービス、システム開発を承っております。
ビジネスの最初から最後までをサポートを行い
お客様のイメージに合わせたWebサービス、システム開発、デザインを行います。
ライセンス
記事アイキャッチのReact.jsロゴはCC A 4.0 Internationalに基づいて利用しています。
参考: コモンズ証 - 表示 4.0 国際 - Creative Commons