Posted at

Vue.jsでWebVR(A-Frame)を扱うととても便利

この度、作品投稿プラットフォームのFantasficでWebVRを使ったイラスト閲覧機能をリリースしました。

この機能は投稿された画像作品をWebVR上で閲覧できるというシンプルなものです。

Vue.jsを介してA-Frameを扱うことで実現しています。

A-FrameはDOMで表現した3DモデルをVRでレンダリングしてくれるライブラリです。

DOMで空間を表現するため、Vue.jsなどのフロントエンドフレームワークと親和性が高い作りになっています。

イベントもDOMの仕組みで処理できます。なので、3Dの平面オブジェクトを表すDOMに直接@clickでイベントリスナーを定義してイベントを受け取るといったことができます。下記がコード例です。

<template>

<a-scene ref="scene">
<!-- 平面を表示 -->
<a-plane width="1" height="1" :position="planePosition" cursor @click="onClickPlane" />
</a-scene>
</template>
<script>
export default {
data() {
return {
planePositionSrc: {x: 0, y: 0, z: -1}
}
},
computed: {
planePosition () {
const pos = this.planePositionSrc
return `${pos.x} ${pos.y} ${pos.z}`
}
},
methods: {
// クリックすると平面が遠のく
onClickPlane() {
this.planePositionSrc.z += 0.5
}
}
}
</script>

フロントエンドフレームワークで実装するとコンポーネント化も簡単です。

下記はOculusのControllerを独自に拡張したコンポーネントの実装例です。

(若干きもちわるいことしてますが許してください)

<template>

<a-entity
id="controller"
app-oculus-controls
oculus-go-controls
daydream-controls
@triggerup="onTriggerUp"
@triggerdown="onTriggerDown"
@trackpadup="onTrackpadUp"
@trackpaddown="onTrackpadDown"
@touchstart="onTouchStart"
@touchend="onTouchEnd"
@axismove="onAxisMove"
></a-entity>
</template>
<script>
const handler = {
get: function(target, name) {
if (target.controller) {
return target.controller[name]
}
return _ => null
}
}
const target = {}
const proxy = new Proxy(target, handler)
window.AFRAME.registerComponent('app-oculus-controls', {
init: function() {
console.log('initialize controller')
},
tick: function() {
proxy.onTick(this.el)
}
})

export default {
data() {
return {
trigger: false,
touch: false
}
},
mounted() {
target.controller = this
},
destroy() {
target.controller = null
},
methods: {
onTriggerUp(e) {
this.trigger = false
this._startTriggerPos = null
this.$emit('triggerup', e)
},
onTriggerDown(e) {
this.trigger = true
this._startTriggerPos = e.target.object3D.position.clone()
this.$emit('triggerdown', e)
},
onTrackpadUp(e) {
this.trackpad = false
this._trackpadDownPos = null
this.$emit('trackpadup', e)
},
onTrackpadDown(e) {
this.trackpad = true
this._trackpadDownPos = e.target.object3D.position.clone()
this.$emit('trackpaddown', e)
},
onTouchStart(e) {
this.touch = true
this._startTouchPos = e.target.object3D.position.clone()
this.$emit('touchstart', e)
},
onTouchEnd(e) {
this.touch = false
this._startTouchPos = null
this._lastAxis = null
this.$emit('touchend', e)
},
onAxisMove(e) {
this.$emit('axismove', e)
if (this.touch && !this.trackpad) {
const axis = e.detail.axis
if (this._lastAxis === null) {
this._lastAxis = [].concat(axis)
}
if (this._lastAxis && this._lastAxis.length > 0) {
const dx = axis[0] - this._lastAxis[0]
const dy = axis[1] - this._lastAxis[1]
this.$emit('axischange', { diff: { x: dx, y: dy } })
}
}
},
onTick(target) {
if (this.trigger && this._startTriggerPos) {
const c = target.object3D.position.clone()
const s = this._startTriggerPos
const diff = new window.THREE.Vector3(s.x - c.x, s.y - c.y, s.z - c.z)
this.$emit('triggerpressmove', { diff })
this._startTriggerPos = c
}
if (this.trackpad && this._trackpadDownPos) {
const c = target.object3D.position.clone()
const s = this._trackpadDownPos
const diff = new window.THREE.Vector3(s.x - c.x, s.y - c.y, s.z - c.z)
this.$emit('trackpadpressmove', { diff })
this._trackpadDownPos = c
}
}
}
}
</script>

いくらかThree.jsについて知っている必要があるものの、Vueで普通のWebフロントエンドを書くような感覚でVRアプリを実装できるのは助かりますね。

Oculus Questの販売も控えていますし、VRは今年も躍進すると信じています。VRアプリをネイティブで記述するのは敷居が高いと感じる方はVueやReactを使ってWebVRから挑戦してみると入りやすいかもしれません。