A-FrameでJapanTownを体験してみる

## WebをよくわかっていないがA-Frameに手を出してみる
JavaScriptとかHTMLとかよくわかってないけど、A-Frameが気になりだしたのでちょこっと触ってみた。
ついでにVRChatなら360度画像取り放題ということを思い出し、VRCのワールドをwebで体験するというものをA-Frameを用いて作ってみた。
(360度カメラあれば現実でやりたかった。)

## 作ったもの
こんな感じ。ブラウザ上で動いています。
![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.amazonaws.com/0/332214/60f96f9a-ea72-9edf-5f13-73ce888573c3.gif)  

スマホでもOculusGOでもブラウザで動き、センサの値も取れている。
![IMG_0971.PNG](https://qiita-image-store.s3.amazonaws.com/0/332214/2e1f24ad-ed5b-6e94-20b2-19d206dffb38.png)

ハイエンドVRHMDでもたぶん動くはず…

## A-Frameとは
[A-Frame](https://aframe.io)とはWebでVRを体験するための機能を提供してくれるフレームワーク。
HTMLだけで、VR体験可能なWebページが作れる(JavaScriptは書けたほうが良いが…)。
名だたる企業が使ってる。元々の開発はFirefoxなどで有名なMozilla。頻繁にBLOGなど更新されている。
![used.jpg](https://qiita-image-store.s3.amazonaws.com/0/332214/616adc48-bbc4-24f3-a11e-6bfdd6ae78fb.jpeg)

VR用のプラットフォームは様々である。例えば、スマホ利用、スタンドアロン型(OculusGO)、PC接続型(HTC VIVE, Oculus Rift...)。また、それぞれでトラッキングも異なる(3DoFと6DoF)。そこら辺をうまいことやってくれてるフレームワークである。HTMLタグが使える人ならば、360度の画像を見るというのも割と楽に実装できる。
[A-FRAME で簡単 全天球画像 表示を試す](https://qiita.com/mamorunner/items/7354bb21da1e2b43cd5a)  

## VRChatとは
VR上でChatするゲーム。仮想世界の360度画像を撮影する機能がついている。
![_1.jpg](https://qiita-image-store.s3.amazonaws.com/0/332214/99ef85a9-23db-39fd-53dc-78cd4c5f101c.jpeg)

撮影した360度画像はこんな感じ。
![VRChat_2019-03-04_20-57-28-516.png](https://qiita-image-store.s3.amazonaws.com/0/332214/c08eaada-9105-f332-b02c-9aacb6fe5213.png)

両眼用のためか上下に2つ分の360度画像がある。今回は事前に、画像編集ソフトなどで上下どちらかの1つ分にしておく。

## 実装
VRChatにあるJapanTownの各地点で360度画像を撮影し、それらを行き来することができるhtmlを作る。
移動はspotと呼ばれるポイントを見つめることでその地点に移動することができるようにする(手で操作するコントローラーが必ずあるとは限らないためこの方式はよく見かける)。

こちらの方のソースコードが非常に参考になった。
[How to create a Virtual Tour using A-frame]
(https://medium.com/@kumar.ahir/how-to-create-a-virtual-tour-using-a-frame-164941fea573)
また、焦点カーソルを作るのはこちらの記事が大変参考になった。
[【A-Frame】a-linkでVRページ間を移動する](https://keeponrockin.hatenablog.com/entry/2018/12/26/102726)
(それぞれ参考になったというかほぼそのままだが…)。
バージョンによって大きく仕様が変わっているっぽいので、実装時にそこらへんは気を付けないといけない。

1.imgタグのsrcにそれぞれの360度画像を入れておく。今回はcentral、north、northweast、cafeの4枚。
a-skyタグは巨大な円形の360度画像を内側から見るイメージ。unityやってる人はすぐわかると思う。
デフォルトはcentralが読み込まれる。場合に合わせてa-skyのsrcを変えることで移動しているような効果を与える。


2.今回は3Dの球に360度画像を張り付けたものにしているをspotにする(コード例: a-sphere spot="linkto:#north;spotgroup:group-north" src="#north" position="-11 1 -3")
![53e9ab5a-c4fc-41a6-aa02-60e99de94745.jpg](https://qiita-image-store.s3.amazonaws.com/0/332214/9fd6d91e-76b3-013c-9b33-1f9f7ad39f02.jpeg)

spotに焦点を合わせると、 spotのclick要素が発火し、現在のa-skyが切り替わる。また、reloadspotsが発火し、現在のspotの親要素のa-entityの大きさ(scale)が0になり、子要素のspotの大きさも併せて0になる(=見えなくなる)。さらに、移行先のspotの親要素の大きさが1になり、移行先のspotが見えるようになる。
例として、初期地点(central)では、cafeとnorthのspotが見えるが、cafeに焦点を併せてa-skyが変わると(cafeに移動)、cafeのspotは見えなくなり、centralのspotのみが見えるようになる。




```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>WebVR-JapanTown</title>
    <script src="https://aframe.io/releases/0.8.2/aframe.min.js"></script>
    <script>
        AFRAME.registerComponent('hotspots', {
            init: function () {
                this.el.addEventListener('reloadspots', function (evt) {
                    //現在のスポットの親のentityの大きさを0にして、見えなくする
                    var currspotgroup = document.getElementById(evt.detail.currspots);
                    currspotgroup.setAttribute("scale", "0 0 0");

                    //移行先のスポットの親のentityの大きさを1にして見えるようにする
                    var newspotgroup = document.getElementById(evt.detail.newspots);
                    newspotgroup.setAttribute("scale", "1 1 1");
                });
            }
        });

        AFRAME.registerComponent('spot', { //spot属性
            schema: { //スキーマ：プロパティを記述。htmlに書いてある奴がそのままはいる
                linkto: {
                    type: "string",
                    default: ""
                }, //文字列型で記述し、デフォルトは空文字
                spotgroup: {
                    type: "string",
                    default: ""
                } //
            },
            init: function () {
                var data = this.data;
                this.el.addEventListener('click', function () { //スポットがクリックされたなら(焦点)
                    //スカイボックスの変更
                    var sky = document.getElementById("skybox");
                    sky.setAttribute("src", data.linkto);
                    var spotcomp = document.getElementById("spots");
                    var currspots = this.parentElement.getAttribute("id");
                    //reloadspotsに対してイベントを投げる newspotsに対してnewspot:data, currsportsについてcurrsprots
                    spotcomp.emit('reloadspots', {
                        newspots: data.spotgroup,
                        currspots: currspots
                    });
                });

            }
        });
    </script>
</head>

<body>
    <a-scene background="color: #ECECEC">
        <!-- skyboxと移動用sphereの画像 -->
        <a-assets>
            <img id="central" src="img/central2.png" />
            <img id="north" src="img/north.png" />
            <img id="northwest" src="img/northwest.png" />
            <img id="cafe" src="img/cafe.png" />
        </a-assets>

        <!-- 他のスカイボックスへのリンクスポット群 -->
        <!-- ある地点が持つスカイボックスから行ける場所、そのスポットの地点 -->
        <a-entity id="spots" hotspots>
            <a-entity id="group-central">
                <!-- sphereとtextでセット、テキストが場所名 -->
                <a-sphere spot="linkto:#north;spotgroup:group-north" src="#north" position="-11 1 -3"> </a-sphere>
                <a-text value="North" width="6" position="-11 2.4 -2.3" scale="2 2 2" rotation="0 65 0"></a-text>
                <a-sphere spot="linkto:#cafe;spotgroup:group-cafe" src="#cafe" position="5.3 1 6.4" rotation="0 50 0"></a-sphere>
                <a-text value="Cafe" width="6" position="6.0 2.4 6.4" scale="2 2 2" rotation="0 -154 0"></a-text>
            </a-entity>
            <a-entity id="group-north" scale="0 0 0">
                <a-sphere spot="linkto:#central;spotgroup:group-central" src="#central" position="11 1.2 1.1" rotation="0 50 0"></a-sphere>
                <a-text value="Central" width="6" position="11 2.5 0.3" scale="2 2 2" rotation="0 -109 0"></a-text>
                <a-sphere spot="linkto:#northwest;spotgroup:group-northwest" src="#northwest" position="1.7 1.1 10"></a-sphere>
                <a-text value="North West" width="6" position="2.9 2.9 10" scale="2 2 2" rotation="0 178 0"></a-text>
            </a-entity>
            <a-entity id="group-northwest" scale="0 0 0">
                    <a-sphere spot="linkto:#north;spotgroup:group-north" src="#north" position="16 1 1" rotation="0 50 0"></a-sphere>
                    <a-text value="North" width="6" position="16 2.9 0.4" scale="2 2 2" rotation="0 -90 0"></a-text>
            </a-entity>
            <a-entity id="group-cafe" scale="0 0 0">
                <a-sphere spot="linkto:#central;spotgroup:group-central" src="#central" position="-7.8 0.5 -10"></a-sphere>
                <a-text value="Central" width="6" position="-7.8 2.0 -8.7" scale="2 2 2" rotation="0 65 0"></a-text>
            </a-entity>
        </a-entity>

        <!-- 初期値central -->
        <a-sky id="skybox" src="#central"></a-sky>

        <!-- カーソル (焦点時アニメーション付き)-->
        <a-entity camera look-controls position="0 1.6 0">
            <a-entity cursor="fuse: true;"
                position="0 0 -1"
                geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03"
                material="color: blue; shader: flat">
                <!-- 焦点時アニメーション -->
                <a-animation begin="cursor-fusing" easing="ease-in" attribute="scale" dur="1500"
                    fill="backwards" from="1 1 1" to="0.1 0.1 0.1"></a-animation>
            </a-entity>
         </a-entity>
    </a-scene>
</body>

</html>
```
3.positionの設定は実際にhtmlをブラウザから開き Ctrl + Alt + i でGUIの操作環境が開けるのでそこから適切な値を確認。  
![無題.png](https://qiita-image-store.s3.amazonaws.com/0/332214/98ebe264-3d5a-b5b6-3f67-27d7cb07e3bf.png)



### 実行時に気を付けること
ファイルを直接ブラウザでみてもエラーが出る。webサーバーにファイルを置いておくこと。(xamppを利用してlocalhostでやるのが楽だと思う)

### 今後の改善点
a-skyの切り替え時にアニメーションとか使えたらよいなぁ。