A-Frameチュートリアル【その1】

## 前置き
<details><summary>[A-Frame](https://aframe.io/)とはHTMLを利用した... 【クリックして展開】</summary>
[A-Frame](https://aframe.io/)とはHTMLを利用した、Web上でインタラクティブに編集が可能なWebVR空間を生成するウェブフレームワークである。[Oculus Quest2](https://www.oculus.com/quest-2/) の発売や[VIVE Focus 3](https://www.vive.com/jp/product/vive-focus3/overview/) など様々なVR機器が世に売り出され、VR市場が活発になっている。そのため、VRに興味を持つ人が増えるのではないだろうか。その際にVRに関する開発として手をつきやすいのがA-Frameだと思う（高度な物を作製する場合はUnity等の方が適切だと思う）。私がVRに関する研究するに当たってA-Frame学ぶ際に、様々なサイトを歩き回り勉強した。そのためこの先A-Frameに関して学びたい人が現れたときやVRに興味が持った方に簡単に始められる様にチュートリアルを記載する。学習の際にはぜひ参考にしていただきたい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640108/a99a7b83-447e-4572-e3a3-b46fe9a593ca.png)

</details>
## 解説について
　本解説は[A-Frameのドキュメント](https://aframe.io/docs/1.2.0/introduction/)や[GitHubのドキュメント](https://github.com/aframevr/aframe/tree/master/docs)を参考に作製している。どちらも英語だがより詳細な情報が欲しい場合はそちらを参照していただきたい。また本解説においてA-Frameのバージョンは1.2.0を利用している。
　また本解説においてHTMLやJavaScriptを扱う。なるべく分かりやすく記述するが初見ではつかみにくい内容が多いと思う。そのためプログラミングに関する知識とHTMLに関する知識が多少は持っていることを前提として進めることが多いかも知れない。そのためプログラミングに触れたことがないけどVRに関する開発をしたくてこのページに訪れた場合は多少理解出来ない点があるのは覚悟して進めていただきたい。
　また解説に当たって要所要所でデモサイトへのリンクを記述しておく。正しい内容が書けているか確認する際に利用して欲しい。
## [筆者](https://plum-chloride.jp/index.html)について

<details><summary>学習する上では関係ないが、宣伝も込めて記述する。 【クリックして展開】</summary>
　執筆当時は武蔵野大学データサイエンス学部2年生であり1年の後期から2年の前期にかけてA-Frameを用いたVR空間を作製し研究を行った。制作物に関しては[デモページ](https://plum-chloride.jp/output/VR-CoPractice/index.html)にて公開している。プログラムの知識と技術があれば1年でこれくらいのVR空間は作れると思う。
　また上記研究は**マルチメディア、分散、協調とモバイル DICOMO2021シンポジウム**にて**最優秀プレゼンテーション賞**を獲得した。本気で取り組みプロダクトとして何かしらのアプリケーションを作れば自分の実績にもつながるため、これから学習する人はぜひ頑張って欲しい。</details>

## その1

- HTMLとA-Frameを用いてVR空間の作製
- オブジェクト及びテキストの配置
- JavaScriptを用いてオブジェクト、テキストの移動や色等をいじる。

[チュートリアルその1完成デモページ](https://plumchloride.github.io/A-frame-demo-Qiita/No1/hazimeni/6.html)(リロードで状態が変わる)

## VR空間の作製
　まず初めに何も設置物のないVR空間の作製を行う。 
　お好きなフォルダー（ディレクトリ）を作製してそこにテキストエディター等でindex.htmlを作製する。そのindex.htmlには下記を記述する。

```html:index.html
<html>
  <head>
      <script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
    </a-scene>
  </body>
</html>
```

　これだけでA-FrameのWebVR空間の土台は完成している。参考までに開いてみると真っ白い空間が表示され、右下にVRモードに入るボタンが存在するページが出来ているはずだ。[デモサイト](https://plumchloride.github.io/A-frame-demo-Qiita/No1/hazimeni/1.html)


しかしこのままでは味気ない。そのため地面と空を配置しよう。


　空に関しては**a-sky**タグ、地面に関しては**a-plane**タグを利用する。skyタグは空として利用するが、planeは平らな物体を作製するものだが、それを倒して地面として擬似的に利用している。

```html:hoge.html
<a-sky color="#ECECEC"></a-sky>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
```

　このコードを先ほど記述したindex.htmlの**a-scene**の子オブジェクトとして記述する。

　**a-sky**タグはskyとついているが基本的には背景の色を定めるものである。特に何もオブジェクトを置かなかった場合は画面全部が指定した色に染まる。引数のcolorに希望の色を挿入する。なお引数として色を指定しなかった場合#FFFFFF(白)として認識している（**a-sky**タグを記述しなかった場合と同様の出力結果）。また応用として画像を背景にしたりと引数を利用すること様々なことができるが、ここでは割愛する([A-Frame ドキュメント a-plane](https://aframe.io/docs/1.2.0/primitives/a-plane.html))。
　色の指定には上記の**`color="#ECECEC"`**以外にも **`color=#ECECEC`** や **`color=rgb(236,236,236)`** , **`color="color: red"`** 等様々な記述法が可能である。
　基本的にと書いたのはあくまで**a-sky**タグは半径500のドームを作製しているものでそれ以上遠い距離のオブジェクトを配置すると正常に描写されない（そのため球体画像を用いてVR空間を作製出来る）。上記制約が不便に感じた場合は**a-scene**の開始タグを `<a-scene background="#ECECEC">` と記載することで真の意味で背景を一色に染めることが出来る。

　**a-plane**タグは薄い（引数にもよるが）板を定めるものである。このコードでは自分よりやや前にx軸方向-90度回転させた4*4の板を#7BC8A4色で配置するという意味意になっている。positionやrotationの三次元空間に関しては`"x y z"` で記載する。また間にカンマを入れて`"x, y, z"`と記載しても問題ない([A-Frame ドキュメント a-sky](https://aframe.io/docs/1.2.0/primitives/a-sky.html))。
　A-Frameのポジションに関しては以下の通りである（特にカメラのrotationをいじっていない場合）。またrotationに関しては何軸を中心として回すかという意味である。

| 1 | 2 | 3 |
|:-:|:-:|:-:|
| 座標 | プラス | マイナス |
| x | 画面右側 | 画面左側 |
| y | 画面上側 | 画面下側 |
| z | 画面手前側 | 画面奥側 |

---

[デモページ](https://plumchloride.github.io/A-frame-demo-Qiita/No1/hazimeni/2.html)


<details><summary>index.htmlコード 【クリックして展開】</summary><div>

```html:index.html
<html>
  <head>
      <script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
      <a-sky color="#ECECEC"></a-sky>
      <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
    </a-scene>
  </body>
</html>
```

</div></details>

<details><summary>完成図　【クリックして展開】</summary>
![FireShot Capture 012 -  - plumchloride.github.io.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640108/871a0e92-f2ea-00c4-fa71-c73ee5371292.png)
</details>


## オブジェクトの配置
　作製したVR空間にオブジェクトを配置する。今回は新たに四角形、球、円柱を配置する。

```html:hoge.html
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" height="1" depth="1" width="1" ></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-text position="0 1.7 -2" value="Hello, World!" align="center" color="#000" scale="1 1 1"></a-text>
```
　**a-box**タグは四角形を作製するものである。`height="1" depth="1" width="1"`のようにサイズを指定する([A-Frame ドキュメント a-box](https://aframe.io/docs/1.2.0/primitives/a-box.html))。

　**a-sphere**タグは球体を作製するものである。`radius="1.25"`のようにサイズを指定する([A-Frame ドキュメント a-sphere](https://aframe.io/docs/1.2.0/primitives/a-sphere.html))。

　**a-cylinder**タグは円柱を作製するものである。`radius="0.5" height="1.5"`のようにサイズを指定する。球ではなく六角形にしたり、円ではなく途中で閉じたり（パックマンみたいに）、中を空洞にしたり工夫する場合は右を参照([A-Frame ドキュメント a-cylinder](https://aframe.io/docs/1.2.0/primitives/a-cylinder.html))。

　**a-text**タグは文字を表示するものである。`value="Hello, World!"`のようにテキストを指定する。改行したい場合は`\n`をvalue内の改行する箇所に記述することで改行できる。また`scale="1 1 1"`でサイズを指定している。現在日本語を入力することはできない。またこのテキストを裏側から見ると何も表示されない（反対向きに表示される訳ではない）ため`rotation="90 0 0"`等で回転させる必要がある。([A-Frame ドキュメント a-text](https://aframe.io/docs/1.2.0/primitives/a-text.html))。
　利用したことはないが下記手法で日本語を表示できる？

https://qiita.com/emuyuu/items/df90a011d73643b8adcb

　なお作製したページはpcの場合wasdで移動できる。


---

[デモページ](https://plumchloride.github.io/A-frame-demo-Qiita/No1/hazimeni/3.html)


<details><summary>index.htmlコード 【クリックして展開】</summary><div>

```html:index.html
<html>
  <head>
      <script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
      <a-sky color=#ECECEC></a-sky>
      <a-plane position="0, 0, -4" width="4" height="4" rotation="-90 0 0"  color=#7BC8A4></a-plane>
      <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" height="1" depth="1" width="1" ></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-text position="0 1.7 -2" value="Hello, World!" align="center" color="#000" scale="1 1 1"></a-text>
    </a-scene>
  </body>
</html>
```

</div></details>

<details><summary>完成図　【クリックして展開】</summary>
![FireShot Capture 014 -  - 127.0.0.1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640108/6f40462d-c079-aa3b-13e0-43e45fbf9da1.png)


</details>


# オブジェクトの移動
　DOMと呼ばれるHTMLオブジェクトのデータを取得する際に利用するAPIが存在する。そのシステムとA-Frameの土台であるthree.jsの機能を用いてJavaScriptを用いて移動させたり、内部のデータを変更させることができる。([A-Frame ドキュメント position update](https://aframe.io/docs/1.2.0/components/position.html#updating-position))。

　まず初めに移動させたいオブジェクトにidをつける。

```html:hoge.html
<a-sphere id="moving_sphere" position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
```

　その後JavaScriptを記述する。その際記述法が2種類あるため両方紹介する。

<details><summary>JavaScript記述法 【クリックして展開】</summary><div>

> 1 インライン

　HTMLの`</body>`の後ろに

```html:index.html
  </body>
  <script>
    // This is JavaScript comment
  </script>
</html>
```

と追記する。

> 2 外部ファイル

　外部ファイルとしてテキストエディター等でscript.jsを作製し、

```js:script.js
// This is JavaScript comment
```
としてHTMLで

```html:index.html
<html>
  <head>
      <script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
      <script src="./script.js" defer></script>
      <!--- もしくは<script src="./js/script.js" defer></script> --->
  </head>
</html>
```
として記述する。

　どちらもDOMを読み込んだ後JavaScriptファイルを読み込む必要があるため、bodyの後ろにscriptを記載したり、deferを入れる必要がある。どちらが良いかに関しては[CSSやJavaScriptを外部ファイル化するメリットとデメリット](https://www.1-firststep.com/archives/2076)等に記載してあるが、本解説ではファイルが一つですむ利便性からインライン表記を利用する。

</div></details>

　その後、DOMを用いてこのエレメントを取得する

```js:hoge.js
const $moving_sphere = document.getElementById("moving_sphere");
```

<details><summary>JavaScriptに関する解説 【クリックして展開】</summary><div>

<dl>
  <dt>const</dt>
  <dd>定数定義。 let を利用すると変数を定義できる</dd>
  <dt>$moving_sphere</dt>
  <dd>定数名。その定数がDOMのオブジェクトの場合 \$ をつけて何の定数か分かりやすくしている（jQueryを用いた際に\$("DOM名")と宣言するから？）</dd>
  <dt>document</dt>
  <dd>HTMLのドキュメント全部から</dd>
  <dt>getElementById</dt>
  <dd>idを利用してelementを取得する</dd>
  <dt>"moving_sphere"</dt>
  <dd>moving_sphereというidのついたエレメントを取得する</dd>
  <dt>文末の「; 」</dt>
  <dd>あってもなくても動くが、基本入れるべき。コードの切れ目を示している</dd>
</dl>

</div></details>

　このエレメントのポジションを取得して変更させるようにする。上記A-Frameのドキュメントに記載してあるとおり`element.setAttribute()`をもちいる方法は推奨されていないため、three.jsの機能を利用して変更させる。

```js:hoge.js
const random = Math.random();
$moving_sphere.object3D.position.set(0, (random)*2 + 1, -5);
// 個別に $moving_sphere.object3D.position.y += random; と記載する方法もある
```

<details><summary>JavaScriptに関する解説 【クリックして展開】</summary><div>

<dl>
  <dt>Math.random</dt>
  <dd>0~1のランダムな乱数生成</dd>
  <dt>.object3D</dt>
  <dd>取得したelementのobject3Dに関して扱う</dd>
  <dt>.position</dt>
  <dd>取得したelementのobject3Dのpositionに関して扱う</dd>
  <dt>.set()</dt>
  <dd>値を()に定める</dd>
</dl>

</div></details>

　このコードにより、Webページをリロードするたびにランダムで球の高さが変わるようになった。

　まとめてインライン表記する際には下記の通りである。

```html:index.html
  </body>
  <script>
    const $moving_sphere = document.getElementById("moving_sphere");
    const random = Math.random();
    $moving_sphere.object3D.position.set(0, (random)*2 + 1, -5);
  </script>
</html>
```

---

[デモページ](https://plumchloride.github.io/A-frame-demo-Qiita/No1/hazimeni/4.html)


<details><summary>index.htmlコード 【クリックして展開】</summary><div>

```html:index.html
<html>
  <head>
      <script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
      <a-sky color=#ECECEC></a-sky>
      <a-plane position="0, 0, -4" width="4" height="4" rotation="-90 0 0"  color=#7BC8A4></a-plane>
      <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" height="1" depth="1" width="1" ></a-box>
      <a-sphere id="moving_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-text position="0 1.7 -2" value="Hello, World!" align="center" color="#000" scale="1 1 1"></a-text>
    </a-scene>
  </body>
  <script>
    const $moving_sphere = document.getElementById("moving_sphere");
    const random = Math.random();
    $moving_sphere.object3D.position.set(0, (random)*2 + 1, -5);
  </script>
</html>
```

</div></details>

<details><summary>完成図　【クリックして展開】</summary>
![FireShot Capture 015 -  - 127.0.0.1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640108/a228f953-d4d9-be9d-fe00-28ced5cec8cd.png)
</details>

# オブジェクトの回転
　別のオブジェクトに対して回転をさせてみる。基本的にやり方は移動の時と同じである。

　まず初めに回転させたいオブジェクトにidをつける。

```html:hoge.html
<a-box id="rotation_box" position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" height="1" depth="1" width="1" ></a-box>
```
　
　回転は0～360度なので、その範囲に収まるようにランダムな数字を作製し適用させる。

```html:index.html
  </body>
  <script>
    const $moving_sphere = document.getElementById("moving_sphere");
    const random = Math.random();
    $moving_sphere.object3D.position.set(0, (random)*2 + 1, -5);
    //ここから下が追加
    const $rotation_box = document.getElementById("rotation_box");
    $rotation_box.object3D.rotation.set(0, random*360, 0);
  </script>
</html>
```

　このコードによりランダムで球の高さが変わりつつboxが回転しているはずだ。

---

[デモページ](https://plumchloride.github.io/A-frame-demo-Qiita/No1/hazimeni/5.html)


<details><summary>index.htmlコード 【クリックして展開】</summary><div>

```html:index.html
<html>
  <head>
    <script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
      <a-sky color=#ECECEC></a-sky>
      <a-plane position="0, 0, -4" width="4" height="4" rotation="-90 0 0"  color=#7BC8A4></a-plane>
      <a-box  id="rotation_box" position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" height="1" depth="1" width="1" ></a-box>
      <a-sphere id="moving_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-text position="0 1.7 -2" value="Hello, World!" align="center" color="#000" scale="1 1 1"></a-text>
    </a-scene>
  </body>
  <script>
    //可読性をあげるために並び替えている
    const $moving_sphere = document.getElementById("moving_sphere");
    const $rotation_box = document.getElementById("rotation_box");
    const random = Math.random();
    $moving_sphere.object3D.position.set(0, (random)*2 + 1, -5);
    $rotation_box.object3D.rotation.set(0, random*360, 0);
  </script>
</html>
```

</div></details>

<details><summary>完成図　【クリックして展開】</summary>
![FireShot Capture 016 - A-Frame　チュートリアル - plumchloride.github.io.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640108/7195e998-87da-dbe1-e15f-26acf0b7ea37.png)
</details>

# 値・色変更
　別のオブジェクトの色を変えたり、テキストを変えたりする。ランダムで空の色を変えつつ、球とboxの回転に利用したランダムな値をtextとして表示させる様にする。

　同様にidを振る

```html:hoge.html
<a-text id="front_text" position="0 1.7 -2" value="Hello, World!" align="center" color="#000" scale="1 1 1"></a-text>
<a-sky id="sky" color=#ECECEC></a-sky>
```

　今度は3Dオブジェクトに関して扱うわけではないので、普通？に`element.setAttribute()`を用いる。A-Frameのプログラム箇所はそれくらい。あとはだらだらとJavaScriptを記述。

```html:index.html
  </body>
  <script>
    //可読性をあげるために並び替えている
    const $moving_sphere = document.getElementById("moving_sphere");
    const $rotation_box = document.getElementById("rotation_box");
    const random = Math.random();
    $moving_sphere.object3D.position.set(0, (random)*2 + 1, -5);
    $rotation_box.object3D.rotation.set(0, random*360, 0);
    //ここから下が追加
    const $front_text = document.getElementById("front_text");
    const $sky = document.getElementById("sky");
    $front_text.setAttribute("value","Hello, World! \n random num:"+String(random.toFixed(2)));
    $sky.setAttribute("color","rgb("+String(parseInt(255-Math.random()*100))+","+String(parseInt(255-Math.random()*100))+","+String(parseInt(255-Math.random()*100))+")");
  </script>
</html>
```

<details><summary>JavaScriptに関する解説 【クリックして展開】</summary><div>

<dl>
  <dt>.setAttribute( 属性名 , 値 )</dt>
  <dd>Attributeつまり属性の値を定める。属性名にはcolorやvalue, scaleなど色々存在している。</dd>
  <dt>\n</dt>
  <dd>改行コード。文字列を改行させてる</dd>
  <dt>String()</dt>
  <dd>括弧内の値のtypeを文字列にする。Pythonでいうstr()</dd>
  <dt>.toFixed(x)</dt>
  <dd>.の前の数値をx桁の小数に変換する</dd>
  <dt>parseInt()</dt>
  <dd>括弧内の値を整数にする</dd>
  <dt>String(parseInt(255-Math.random()*100))</dt>
  <dd>155~255までのランダムな整数を計算している。色を混ぜたときにどぎつい色にならないようその範囲に定めた</dd>
</dl>

</div></details>

---

[デモページ](https://plumchloride.github.io/A-frame-demo-Qiita/No1/hazimeni/6.html)


<details><summary>index.htmlコード 【クリックして展開】</summary><div>

```html:index.html
<html>
  <head>
    <script src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
      <a-sky id="sky" color=#ECECEC></a-sky>
      <a-plane position="0, 0, -4" width="4" height="4" rotation="-90 0 0"  color=#7BC8A4></a-plane>
      <a-box  id="rotation_box" position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" height="1" depth="1" width="1" ></a-box>
      <a-sphere id="moving_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-text id="front_text" position="0 1.7 -2" value="Hello, World!" align="center" color="#000" scale="1 1 1"></a-text>
    </a-scene>
  </body>
  <script>
    //可読性をあげるために並び替えている
    const $moving_sphere = document.getElementById("moving_sphere");
    const $rotation_box = document.getElementById("rotation_box");
    const $front_text = document.getElementById("front_text");
    const $sky = document.getElementById("sky");
    const random = Math.random();

    $moving_sphere.object3D.position.set(0, (random)*2 + 1, -5);
    $rotation_box.object3D.rotation.set(0, random*360, 0);

    $front_text.setAttribute("value","Hello, World! \n random num:"+String(random.toFixed(2)));
    $sky.setAttribute("color","rgb("+String(parseInt(255-Math.random()*100))+","+String(parseInt(255-Math.random()*100))+","+String(parseInt(255-Math.random()*100))+")");
  </script>
</html>
```

</div></details>

<details><summary>完成図　【クリックして展開】</summary>
![FireShot Capture 017 -  - 127.0.0.1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/640108/b68b2d3b-7155-5674-8342-31b29019af0e.png)

</details>

# まとめ
　A-Frameのオブジェクトの操作に関して軽く記述した。解説を添えつつ完成したコードも折りたたみの中に記述しているため、比較的容易に進められたと信じたい。
　今回の技術に加えてJavaScriptの`new Date()`を学べば、時刻を取得して擬似的な太陽を空に浮かべたり、時刻に応じてbackgroundのcolorを変更したりと様々な応用が利くと思う。
　
　その2に関しては未定（7月中に出せたら良いな……）
# 参考文献

- [A-Frameのドキュメント](https://aframe.io/docs/1.2.0/introduction/)
- [GitHubのドキュメント](https://github.com/aframevr/aframe/tree/master/docs)