初心者だけどElmでVRがしたい

## はじめに
みなさんも一度は思ったことがあるでしょう

**簡単にWebアプリが作れるElmと簡単にWeb-VRができるA-frameが一緒に使えたらなぁ…**

わお、ちょうど良いところに[elm-aframe](https://github.com/halfzebra/elm-aframe)なんていうピッタリなパッケージがあるじゃないですか！

でもよく見てみると最後のコミットのほとんどが3年前。作者のhalfzebraさんは[create-elm-app](https://github.com/halfzebra/create-elm-app)を作るのに忙しそう
![スクリーンショット 2019-12-22 7.46.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380386/624ba72f-3522-296d-0f3c-2dc54ffedc80.png)

閑古鳥が鳴くissue

![スクリーンショット 2019-12-22 7.47.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380386/a56ec290-22a6-2661-dac4-9d6c4010857c.png)

**もうこうなったら自力でやるしかない**

ということで、まだElmでまともなアプリを作ったことがない初心者がElmでVRを動かそうと試みてみました。


# まずはGet started

まずはA-frameの公式ドキュメントにあるGet startedをElm上で実現してみましょう
[A-Frame:Introduction](https://aframe.io/docs/1.0.0/introduction/)

``` html
<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>
```
![スクリーンショット 2019-12-22 5.08.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380386/71630a2d-c98b-0b64-e7da-3ff539ae8758.png)

A-frameはこんな感じでタグを書いていくだけでお手軽にVR空間にオブジェクトを置けます。とっても便利！

これをElmで再現するために、elm-aframeにはタグに対応する関数が既に用意されているんですが、いかんせん3年前のElmで書かれたものだからまともに動かない。てことでこんな感じで自分で一個一個作っていきます。

``` 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にベタ書きして

``` Elm
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" ] [] 
    ]
```
![スクリーンショット 2019-12-22 5.28.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380386/64d537de-e819-3ee7-c282-925335867bbd.png)

完成！ということで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](https://aframe.io/docs/1.0.0/introduction/interactions-and-controllers.html)


#### JS側

``` javascript
//クリックするとカウントアップする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側

``` 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に送る時に何かしら値を送らなければいけないらしい（間違ってたらごめんなさい)ので、送られてきた文字列でパターンマッチしてます。

さてどうなるでしょうか
![画面収録-2019-12-22-8.03.02.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380386/47a56986-6a72-e304-2a50-977bd8fe847c.gif)


できました！（オブジェクトの形とか色とか適当だけど）なんとかElm + A-frameでカウンターアプリを作ることができました！やったね！！

## 感想

いやあElm自体初心者なのに、他のJSライブラリを併用したアプリを作るのはきつかったです。というか主にElmの基礎知識が足らなすぎて時間と労力かかりました。余裕があったら本当はOculusを使ったVRアプリまで作ってみたかったんですけどね…

結論を言うと、**多分A-FrameはElmに向きません。**やってる最中に「これ普通にHTMLでやればよくね…？」って何回も思いました。Elmの恩恵を受けた気は全然しないです。大体の操作はJS側でやるし、コンポーネントに状態持たせるし、そりゃ作者も更新止めますわ。

今回はこういうマイナスな感想になりましたが、Elmはちゃんと適所で使えばものすごい恩恵を受けられると思うので、JSライブラリを使う時はElmとの相性を吟味したほうが良いと思います。

ありがとうございました！