はじめに
みなさんも一度は思ったことがあるでしょう
簡単にWebアプリが作れるElmと簡単にWeb-VRができるA-frameが一緒に使えたらなぁ…
わお、ちょうど良いところにelm-aframeなんていうピッタリなパッケージがあるじゃないですか!
でもよく見てみると最後のコミットのほとんどが3年前。作者のhalfzebraさんはcreate-elm-appを作るのに忙しそう
閑古鳥が鳴くissue
もうこうなったら自力でやるしかない
ということで、まだElmでまともなアプリを作ったことがない初心者がElmでVRを動かそうと試みてみました。
まずはGet started
まずはA-frameの公式ドキュメントにあるGet startedをElm上で実現してみましょう
A-Frame:Introduction
<html>
<head>
<script src="https://aframe.io/releases/1.0.1/aframe.min.js"></script>
</head>
<body>
<a-scene>
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
<a-sky color="#ECECEC"></a-sky>
</a-scene>
</body>
</html>
A-frameはこんな感じでタグを書いていくだけでお手軽にVR空間にオブジェクトを置けます。とっても便利!
これをElmで再現するために、elm-aframeにはタグに対応する関数が既に用意されているんですが、いかんせん3年前のElmで書かれたものだからまともに動かない。てことでこんな感じで自分で一個一個作っていきます。
sphere : List (Attribute msg) -> List (Html msg) -> Html msg
sphere =
node "a-sphere"
cylinder : List (Attribute msg) -> List (Html msg) -> Html msg
cylinder =
node "a-cylinder"
scene : List (Attribute msg) -> List (Html msg) -> Html msg
scene =
node "a-scene
あとはmainにベタ書きして
main =
scene [ ]
[ box [ position -1 0.5 -3, rotation 0.0 45.0 0.0, color "#4CC3D9" ] []
, sphere [ position 0.0 1.25 -5, radius 1.25, color "#EF2D5E" ] []
, cylinder [ position 1 0.75 -3, radius 0.5, height 1.5, color "#FFC65D" ] []
, plane [ position 0 0 -4, rotation -90 0 0, width 4, height 4, color "#7BC8A4" ] []
, sky [ color "#ECECEC" ] []
]
完成!ということでElmでA-frameのGet startedができました!お疲れさまでした!
いやElmいらなくない?
はい、これではHTMLに書いたのとあまり変わりないです。むしろElm挟んでるんで面倒くさくなってるだけです。てことで次はElmらしいTEAを使ったWebアプリ、Elmの門を開いた時に誰もが必ず通るカウンターアプリを作ってみましょう。
A-frameは普通にクリックイベントを定義できません。方法は様々あるのですが、Javascriptを使う場合はEventListenerを内包したコンポーネントを定義して、目的のオブジェクトと結び付けなければいけません。またJS側でイベントを発火させてるので、Elm側にそのことを伝えるためにportsを設定します。またVR上でクリックイベントを起こすためには<a-camera>
と<a-cursor>
も必要なので追加しています。
A-Frame:Interactions & Controllers
JS側
//クリックするとカウントアップするbox
AFRAME.registerComponent("plus-box", {
init: function() {
this.el.addEventListener("mousedown", function() {
app.ports.calculate.send("Increment");
});
}
});
//クリックするとカウントダウンするbox
AFRAME.registerComponent("minus-box", {
init: function() {
this.el.addEventListener("mousedown", function() {
app.ports.calculate.send("Decrement");
});
}
});
Elm側
-- VIEW
scene []
[
atext [ avalue (String.fromInt model) , position 0 2 -4, color "black", width 8] []
, box [color "#EF2D5E", position 1 0.5 -4, attribute "plus-box" "" ] []
, box [color "#EF4533", position -1 0.5 -4, attribute "minus-box" ""] []
, plane [ position 0 0 -4, rotation -90 0 0, width 4, height 4, color "#7BC8A4" ] []
, camera [] [ cursor [] [] ]
]
--SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model = calculate Calculate
port calculate : (String -> msg) -> Sub msg
--UPDATE
type Msg
= Calculate String
| Nothing
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Calculate string ->
case string of
"Increment" ->
(model + 1, Cmd.none)
"Decrement" ->
(model - 1, Cmd.none)
_ ->
(model, Cmd.none)
Nothing ->
(model, Cmd.none)
こんな感じで、クリックイベントを起こしたいオブジェクトにコンポーネントを作って結びつけて、クリックしたらportsを通してElmに教えるようにします。portsではJSからElmに送る時に何かしら値を送らなければいけないらしい(間違ってたらごめんなさい)ので、送られてきた文字列でパターンマッチしてます。
できました!(オブジェクトの形とか色とか適当だけど)なんとかElm + A-frameでカウンターアプリを作ることができました!やったね!!
感想
いやあElm自体初心者なのに、他のJSライブラリを併用したアプリを作るのはきつかったです。というか主にElmの基礎知識が足らなすぎて時間と労力かかりました。余裕があったら本当はOculusを使ったVRアプリまで作ってみたかったんですけどね…
結論を言うと、**多分A-FrameはElmに向きません。**やってる最中に「これ普通にHTMLでやればよくね…?」って何回も思いました。Elmの恩恵を受けた気は全然しないです。大体の操作はJS側でやるし、コンポーネントに状態持たせるし、そりゃ作者も更新止めますわ。
今回はこういうマイナスな感想になりましたが、Elmはちゃんと適所で使えばものすごい恩恵を受けられると思うので、JSライブラリを使う時はElmとの相性を吟味したほうが良いと思います。
ありがとうございました!