ブラウザで閲覧できるVRポートフォリオを作った

Oculus Questを購入後に色々見ていると、ブラウザでVRができるWebVRというものを知りました。しかもA-Frameというフレームワークがあり、HTMLとJavaScriptで簡単にVR空間が作れるということでしばらく遊んでみてとても楽しいことがわかりました。

丁度自分のポートフォリオをどうしようか悩んでいたときだったこともあったため、せっかくなのでVRポートフォリオを作ってみることにしました。

で、実際に出来たものが下記です。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">VRポートフォリオ内をOculus Questで遊んでる様子。 <a href="https://t.co/SfnT5ZX2wl">https://t.co/SfnT5ZX2wl</a> <a href="https://t.co/doRQVYOFjD">pic.twitter.com/doRQVYOFjD</a></p>&mdash; だら🍔技術系投稿サービスや100の質問メーカー運営中 (@dala00) <a href="https://twitter.com/dala00/status/1143659217460449280?ref_src=twsrc%5Etfw">June 25, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

A-FrameはWebVRに対応しているものですが、普通にブラウザで3Dの描画もできるため、せっかくなのでVRに対応していないPCやスマホでもある程度操作できるようにしてみました。

記事の最後にURLもありますので是非実際に見てみてください。

## 入れたもの

A-Frameには組み込むだけで簡単に色々と拡張できるコンポーネントが作られてGitHub上に公開されています。今回実際に何を入れているのかを紹介していきます。

### Super Hands

[wmurphyrd/aframe-super-hands-component: 👐All-in-one natural hand controller, pointer, and gaze interaction library for A-Frame](https://github.com/wmurphyrd/aframe-super-hands-component)

Oculus Questのコントローラではグリップボタンという握りしめるような操作のボタンがあり、普通に手で物を持つようにVR空間のオブジェクトを持ち上げたり動かしたりすることが出来ます。それを簡単に実現してくれるためのA-Frame用コンポーネントです。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136506/b52d6916-a4c8-b198-2ceb-72e7b8e32680.jpeg)

上記のようにオブジェクトを掴むだけであれば下記のようなちょっとしたHTMLだけで実現できます。JavaScriptも不要です。

```html
  <a-scene>
    <a-assets></a-assets>
    <a-entity>
      <a-camera></a-camera>
      <a-entity sphere-collider="objects: a-box" super-hands hand-controls="left"></a-entity>
      <a-entity sphere-collider="objects: a-box" super-hands hand-controls="right"></a-entity>
    </a-entity>
    <a-box hoverable grabbable stretchable draggable dropppable color="blue" position="0 0 -1"></a-box>
  </a-scene>
```

ポートフォリオにははっきり言って全く関係ないのですが、とりあえず箱を置いて操作できるようにしておきました。

### 物理エンジン

簡単に物理エンジンを導入できるコンポーネントもあります。

[donmccurdy/aframe-physics-system: Physics system for A-Frame VR, built on CANNON.js.](https://github.com/donmccurdy/aframe-physics-system)
[wmurphyrd/aframe-physics-extras: 🔧Cannon API interface components the A-Frame Physics System](https://github.com/wmurphyrd/aframe-physics-extras)

extrasの方はSuper Handsの作者の方が簡単に物理空間上でオブジェクトを操作できるようにしてくれているコンポーネントです。

これをSuper Handsと合わせて入れることで箱を投げたりすることが出来ます。（ただしこれも全然ポートフォリオ的には意味はありません。なんとなく入れているだけです）

### パネル & raycaster

適当に配置している制作物毎のパネルです。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136506/ec937144-1248-71ef-bfba-b9df0eb852cc.png)

これ自体はa-planeやa-imageを適当に組み合わせて並べているだけです。ただ、一応実際にサービスのURLにアクセスできるようにしてあります。VRでない場合はクリックでアクセスできますし、VRの場合はraycasterというレーザーポインターのようなものでクリックできます。Super Handsは最初からクリックイベントを使ってくれるようになっているため、普通にonclickを入れておけば両方で動作します。

```html
        <a-plane
          mylink
          onclick="location.href='https://crieit.net'"
```

クリックできる対象はraycaster側で設定できます。

```html
        raycaster="showLine: true; objects: [mylink], .checkpoint, a-link"
```

### ラジコン

スティックで操作できるラジコンも無意味に置いておきました。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136506/cc3d44ca-7d60-ac31-733c-a2d18120d970.png)

これは自作で、作り方は下記で紹介しています。なんかここまでやるとゲームとか作りたくなってしまいますね。

[A-FrameでVRラジコンを作る - Crieit](https://crieit.net/posts/A-Frame-VR)

ちなみにポートフォリオは当然一般公開したかったため仕方なくモデルは適当に作りました。フリーの素材と比べてかなりの低クオリティで悲しいです。

### 墓地

閉鎖したサービスの墓地です……。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136506/853b1d50-9db1-7172-c185-a630868a39c5.png)

## PC、スマホに対応

本当にVRだけだとOculus Questを持っている人しか体験できないため、PC、スマホでも操作できるように、とりあえず最低限ではありますが対応しておきました。

### 歩く＆ラジコン操作

3D空間上をWASDキーで歩くことが出来ます。ただ、これはA-Frameはデフォルトで歩けるようになっています。しかし今回ラジコンもWASDキーで操作できるようにしたかったため、下記の様なUIを用意してクリックで操作を切り替えることができるようにしておきました。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136506/60537439-6f3e-3de1-c4a3-f57d80808d2e.png)

下記のような感じで普通にクリックイベントでコンポーネントを取得してコンポーネントのメソッドを呼び出しています。

```typescript
  selectCamera() {
    this.getCar().setKeyboardEnabled(false)
    this.getCamera().setKeyboardEnabled(true)
  }

  selectCar() {
    this.getCar().setKeyboardEnabled(true)
    this.getCamera().setKeyboardEnabled(false)
  }

  private getCamera(): MobileCameraControl {
    return document.querySelector('a-camera').components[
      'mobile-camera-control'
    ]
  }

  private getCar(): Car {
    return document.querySelector('[car]').components.car
  }
```

### Vue.jsと共存

A-Frameは`a-scene`という要素があれば3D空間を作ってくれますが、その外に普通のHTMLも書くことが出来ます。前述のUIもそのようにして作成しています。（VRモードでは見えないのであくまでもVR非対応端末用）

ただ、`a-scene`がbody直下にないと今度は単なる3D描画だけになりVRボタンが消えてしまいますので、下記のようにしてシーンとHTMLのUIを分けて描画しています。

```typescript
new Vue({
  render: h => h(Scene)
}).$mount('#scene')

new Vue({
  render: h => h(App)
}).$mount('#app')
```

```html:index.html
  <body>
    <div id="scene"></div>
    <div id="app"></div>
  </body>
```

### スマホ用移動ボタン

スマホ表示の場合は下記のようなボタンを表示し、タップでVR空間内を歩けるようにしています。そのうちラジコンも操作できるように改良したいです。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136506/d9790fcc-a9eb-ad88-82f8-bc2c00110bbd.png)

これも先程のようにカメラコンポーネントを取得してメソッドを呼び出して動かしているだけです。状態を変更して常時呼ばれ続けるtick内で動かしているだけです。

```typescript
  tick() {
    if (this.up) {
      this.goUp()
    }
    if (this.down) {
      this.goDown()
    }
    if (this.left) {
      this.goLeft()
    }
    if (this.right) {
      this.goRight()
    }
  },
  goUp() {
    const angle = (Math.PI * this.el.getAttribute('rotation').y) / 180
    const position = this.el.getAttribute('position')
    position.x -= speed * Math.sin(angle)
    position.z -= speed * Math.cos(angle)
    this.el.setAttribute('position', position)
  },
```

## デプロイ

基本的にHTML & JavaScriptだけでサーバーサイドの機能も不要なペライチページのため、Netlifyにデプロイしています。Gitでpushするだけです。

## まとめ

こんな感じでVRポートフォリオを作ってみました。A-Frameはほんとにとりあえずプログラム無しのHTML1枚からVRコンテンツ作成を初められるので非常におすすめです。特にOculusストアにアプリを出すのは今のところ簡単ではなさそうですので、作ったアプリを一般公開するのは現在ある意味WebVR以外無いような状況でもあると思いますので非常に便利です。是非みなさんも試してみてください。

下記が今回実際に作ったVRポートフォリオです。よろしければ遊んでみてください。

面白いなと思う部分があったらぜひ「いいね」をお願いします！

[VRポートフォリオ](https://dala00.alphabrend.net)