Oculus Questのブラウザで遊べるWebVR音ゲーを作った

Oculus Questで遊べるVRの音ゲーを公開しました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">VR音ゲーを作ってみました。Oculus QuestのブラウザでもVRが遊べるんです。是非ハイスコアにチャレンジしてみてください～。まだ試しで１曲だけのEASY &amp; NORMALだけになっています。<a href="https://t.co/LAVpCI5r3d">https://t.co/LAVpCI5r3d</a> <a href="https://twitter.com/hashtag/OculusQuest?src=hash&amp;ref_src=twsrc%5Etfw">#OculusQuest</a> <a href="https://twitter.com/hashtag/aframevr?src=hash&amp;ref_src=twsrc%5Etfw">#aframevr</a> <a href="https://twitter.com/hashtag/WebVR?src=hash&amp;ref_src=twsrc%5Etfw">#WebVR</a> <a href="https://t.co/QrIjjpPnIZ">pic.twitter.com/QrIjjpPnIZ</a></p>&mdash; だら🍔技術系投稿サービス運営中 (@dala00) <a href="https://twitter.com/dala00/status/1171041311073300481?ref_src=twsrc%5Etfw">September 9, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## WebVRとは

じつはこのゲームはOculusストアに登録したものではありません。Oculusストアは審査が厳しすぎて個人で適当に作ったようなクオリティのゲームは登録できません。

しかし現在はWebVRという技術があり、なんとOculus Quest等のブラウザでもVRコンテンツを楽しむことができるようになっています。これであれば普通にWeb上に公開できるため、URLさえあればリリースして他の皆に遊んでもらう事ができます。調整すれば普通にPC等のブラウザでも遊べるようにできるため、VR機器を持っている人も持っていない人も楽しめるゲームを作ったりすることも出来ます。

## A-Frame

今回はA-Frameという、Mozillaが開発しているWebVR用のライブラリを使っています。

[A-Frame – Make WebVR](https://aframe.io/)

A-FrameはHTMLとJavaScriptで簡単にVRコンテンツを作ることができるフレームワークです。以前も下記のようなVRポートフォリオの記事を書かせていただきました。

[ブラウザで閲覧できるVRポートフォリオを作った](https://qiita.com/dala00/items/5ea1ebe97bed55963ebf)

公式のサンプルになりますが、こんなコードでもうVR空間が作れてしまいます。

```html
<html>
  <head>
    <script src="https://aframe.io/releases/0.9.2/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>
```

[DEMO](https://glitch.com/~aframe)

内部的にはthree.jsが使われているので、本格的な3Dプログラミングを加えることも出来ます。

## 開発

開発はVue CLIで作ったプロジェクトで作っています。ビルドの設定をしなくても最初からビルドできる環境が整っているためすぐ開発が進められますし、HTMLを利用するということで割と親和性は良いかなと思っています。（とはいえゲームとなるとわりとプログラムを書く方が多いためそこまでではないですが）

## デプロイ

Netlifyを使っています。HTMLとJavaScriptがメインのため、相性は良いと思います。ハイスコアの登録や音符データ管理のためにHerokuにサーバーサイドのアプリケーションも配置しています。

元々作り始めた時は音符データも全部ファイルに配列でベタ書きしており、完全にサーバー側も無しでやっていました。曲や難易度を切り替えたりハイスコアを登録したりしたかったのでサーバー側も作りましたが、もっとシンプルであれば無しでも十分に楽しいコンテンツが作れるのではないかと思います。

## 実際にどんなふうにゲームを作ったか

実際にどういうふうにやるとゲームができるのか、いくつか解説してみたいと思います。

### 銃

銃のソースはこんな感じです。（VueのためちょっとVueの書き方が混ざっています）

```html
  <a-entity
    :laser-controls="{ hand, model: false }"
    raycaster="objects: .click, [block]; direction: 0 -0.8660254037844387 -0.4999999999999998; origin: 0 0 -0.04"
    gun
  >
    <a-entity
      obj-model="obj: #gun-obj; mtl: #gun-mtl"
      position="0 -0.03 -0.07"
      rotation="60 180 0"
      scale="0.3 0.3 0.3"
    ></a-entity>
  </a-entity>
```

最初のa-entityというところは、今回でいうとOculus Questのコントローラになっています。laser-controlsという属性をつけることでコントローラと結びつきます。そのままだとコントローラのモデルが表示されてしまうので、modelをfalseにして消しています。その代わりにコントローラの下にもう一つ銃のモデルを表示するためのa-entityを追加しています。子要素なのでこれだけでもう手を動かすだけで銃を持った感じに操作できるようになります。

#### 銃を撃つ

前述のコントローラに、`gun`という属性があるのがわかると思います。A-Frameではこのように属性として`コンポーネント`を割り当てることが出来ます。コンポーネントはプリセットのものもありますし、このgunのように自分で作ることもでき、様々な機能をJavaScriptで実装できることが出来ます。

具体的にはこんな感じです。

```typescript
import { registerComponent } from 'aframe'

const bulletDistance = 20.0

registerComponent('gun', {
  init() {
    this.el.addEventListener('triggerdown', (e: TriggerDownEvent) => {
      this.generateBullet()
    })
  },
  generateBullet() {
    // レーザー生成
  }
})
```

triggerdownというのは、コントローラに銃を撃つようなボタンがあるのでそれのことです。普通にaddEventListenerでイベントを追加し、処理を書くことが出来ます。

#### 弾の生成

弾というか今回はレーザーですが、生成は下記のような感じです。

```typescript
    const bullet = document.createElement('a-cylinder')
    bullet.setAttribute('bullet', {})
    bullet.setAttribute('position', {
      x: 0,
      y: -0.2 - bulletDistance,
      z: -0.04
    })
    bullet.setAttribute('scale', { x: 1, y: bulletDistance * 2, z: 1 })
    bullet.setAttribute('rotation', { x: 0, y: 0, z: 0 })
    bullet.setAttribute('radius', 0.03)
    bullet.setAttribute('material', 'color: white; opacity: 0.5')
    group.appendChild(bullet)

    document.querySelector('a-scene').appendChild(group)
```

JavaScriptを使ったことがある人なら割と見慣れたコードなのではないかと思います。大枠のa-sceneという要素があるので、そこにcreateElementで作った要素に諸々設定したりコンポーネントを紐付けてappendChildするだけです。

#### レーザーの角度

レーザーはコントローラの角度に合わせて生成します。しかし、属性で設定できるようなx, y, z座標のrotationでは正しい角度が取得、設定できません。3D空間で正確に角度を扱う時はquaternionという複素数を使ったものを利用します。計算は全部three.jsがやってくれるので特に深く考える必要はありません。

こんな感じでコントローラ（gun）のquaternionを取得し、レーザーに割り当てるための`q`に掛け合わせてその他色々傾けたりしているだけです。

```typescript
    const q = new Quaternion()

    const gunQuaternion = new Quaternion()
    this.el.object3D.getWorldQuaternion(gunQuaternion)
    q.multiply(gunQuaternion)

    // 色々

    group.object3D.applyQuaternion(q)
```

#### 消えるレーザー

レーザー自体もbulletというコンポーネントを作っています。これは単にだんだん細くなって消えるだけです。

```typescript
registerComponent('bullet', {
  baseScale: 1.0,
  baseLengthScale: 1.0,
  time: 0,

  init() {
    this.time = new Date().getTime()
    this.baseScale = this.el.getAttribute('scale').x
    this.baseLengthScale = this.el.getAttribute('scale').y
  },

  tick() {
    const time = new Date().getTime() - this.time
    if (time > bulletLifeTime) {
      this.el.remove()
      return
    }

    const power = (bulletLifeTime - time) / bulletLifeTime
    const scale = this.baseScale * power * power
    this.el.setAttribute('scale', {
      x: scale,
      y: this.baseLengthScale,
      z: scale
    })
  }
})
```

tickというメソッドが常時呼ばれるループ処理になるため、ここでサイズを変えたり、時間が過ぎたら要素を削除したりしています。まあこれはわざわざ処理を書いてしまいましたが、A-Frameにはanimationというコンポーネントも用意されており、それだけでアニメーションも可能です。

例えばこんな感じです。アニメーション時間や変えたい値、ループするかやイージングなども属性で指定するだけで可能です。プログラムを書かなくてもいいので楽ですね。（イージングというのは例えばこんなふうに変化の仕方を変えたりすることです [https://easings.net/](https://easings.net/)）

```typescript
    image.setAttribute('animation__position', {
      property: 'position',
      to: `${position.x} ${position.y + 1} ${position.z}`,
      dur: 1000,
      easing: 'easeOutCirc'
    })
```

### 音符とヒット

音符（というか箱ですが…）とレーザーがヒットする処理も作る必要があります。ただ、最初に書いたようにコントローラではraycasterというコンポーネントを利用しています。そのため、この機能で普通にレーザーポイントみたいに直線上でヒットしているオブジェクトを取得することが出来てしまいます。レーザーとの当たり判定を行っているわけではありません。レーザーはほんとに単なる飾りです。

ということで、ヒットしている状態でトリガーを引くと普通にボックス側にclickイベントが発生します。それであとは破壊したりポイントを追加したりしているだけなのでめちゃくちゃ簡単です。（raycasterのobjectsに当たり判定を可能とするクエリセレクタを書いておきます）

```typescript:箱のコンポーネント
  init() {
    this.startTime = new Date().getTime()

    this.el.addEventListener('click', (e: Event) => {
      this.destroyByHit()
    })
  },
```

### デザイン

画像はフリー素材を使ったりしていますが、モデルは全部プリセットのためしょぼいです。なんとかしなければ…。

とはいえ平面であれば三角や四角、モデルも球やキューブ、シリンダーや他にも○面体等、結構多いので色々ためにし使ってみています。本当はキャラクターのモデルでも踊ってくれていれば良いのですが……。

### まとめ

ということでA-FrameでVRゲームづくりは非常に面白いので是非試してみてください！

ちなみにゲームはここです。まだとりあえず１曲でEASY & NORMALの難易度だけです。

[Sound Shooter | Web VR Sound Shooting Game](https://sound-shooter.webvr-game.net/)

もし良いと思った部分があったら是非いいねをお願いします！