Remix this example on Glitch (opens new window). Or view the demo (opens new window).

Let's build a basic Minecraft (voxel builder) demo that targets room scale VR with controllers (e.g., Vive, Rift). The example will be minimally usable on mobile and desktop. コントローラ(Vive、Riftなど)を使うルームスケールVRの基本的なMinecraft(ボクセルビルダー)デモを作りましょう。 この例は、モバイルとデスクトップで最低限使えるようにします。

# 骨格例

まずは骨格を作りましょう

<script2 src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>

<body>
  <a-scene>
  </a-scene>
</body>

# 地面を追加する

<a-plane><a-circle>は、地面を追加する際によく使われる基本的なプリミティブです。 ここでは、コントローラが使用するレイキャスターとうまく連動させるために、<a-cylinder>を使用します。 円柱の半径は、後で追加する空の半径と同じ30メートルにします。 A-Frameの単位は、WebVR APIから返される現実世界の単位と一致するようにメートルであることに注意してください。

使用する地面のテクスチャは、"https://cdn.aframe.io/a-painter/images/floor.jpg" でホストされています。 このテクスチャをアセットに追加し、そのテクスチャを指す細い円柱のエンティティを作成します。

See the Pen Minecraft VR Demo (Part 1) by mozvr (@mozvr) on CodePen.

<script2 src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>

<a-scene>
  <a-cylinder id="ground" src="https://cdn.aframe.io/a-painter/images/floor.jpg" radius="32" height="0.1"></a-cylinder>
</a-scene>

# アセットをプリロードする

src属性でURLを指定することで、実行時にテクスチャがロードされます。 ネットワークリクエストは描画のパフォーマンスに悪影響を与える可能性があるため、テクスチャをプリロードして、アセットが読み込まれるまでシーンのレンダリングを開始しないようにすることができます。これは、アセット管理システム (opens new window)を使用して行うことができます。

<a-assets><a-scene> に配置し、アセット(画像、動画、モデル、サウンドなど)を <a-assets> に配置し、セレクタ(例:#myTexture)でエンティティからそれらを参照するようにします。

地面のテクスチャを<a-assets>に移動して、<img>要素を使用してプリロードしてみましょう。

See the Pen Minecraft VR Demo (Part 2: Preloading Texture) by mozvr (@mozvr) on CodePen.

<script2 src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>

<a-scene>
  <a-assets>
    <img id="groundTexture" src="https://cdn.aframe.io/a-painter/images/floor.jpg">
  </a-assets>

  <a-cylinder id="ground" src="#groundTexture" radius="32" height="0.1"></a-cylinder>
</a-scene>

# 背景を追加する

<a-scene><a-sky>を用いて360°の背景を追加してみましょう。<a-sky> は大きな3D球体で、その内側にマテリアルがマッピングされています。 通常の画像と同様に、<a-sky>srcで画像パスを指定することができます。これにより、最終的には1行のHTMLで360°の没入型画像を作成することができます。 後に練習として、Flickrの等緯経度画像プール (opens new window)から360°画像を見つけていくつか使ってみてください。

無地の背景(<a-sky color="#333"></a-sky>など)やグラデーションを追加することもできますが、ここでは画像でテクスチャを追加してみましょう。今回使用する画像は、"https://cdn.aframe.io/a-painter/images/sky.jpg" で公開されています。

使用する画像テクスチャは半球体をカバーしているので、球体をtheta-length="90 "で半分に切り、球体の半径を30メーターにして地面と同じにします。

See the Pen Minecraft VR Demo (Part 3: Adding a Background) by mozvr (@mozvr) on CodePen.

<script2 src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>

<a-scene>
  <a-assets>
    <img id="groundTexture" src="https://cdn.aframe.io/a-painter/images/floor.jpg">
    <img id="skyTexture" src="https://cdn.aframe.io/a-painter/images/sky.jpg">
  </a-assets>

  <a-cylinder id="ground" src="#groundTexture" radius="30" height="0.1"></a-cylinder>

  <a-sky id="background" src="#skyTexture" theta-length="90" radius="30"></a-sky>
</a-scene>

# ボクセルの追加

VRアプリケーションのVoxelsは<a-box>のようなものですが、いくつかのカスタムA-Frameコンポーネントが付属しています。しかし、まず最初にエンティティ・コンポーネントのパターンについて説明します。<a-box>のような使いやすいプリミティブが、どのように構成されているかを見てみましょう。

このセクションでは、後に、いくつかのA-Frameコンポーネントの実装について深く掘り下げていきます。しかし、実際には、コンポーネントをゼロから構築するよりも、A-Frameコミュニティの開発者がすでに書いているHTMLを経由してコンポーネントを利用することが多いようです。

###エンティティコンポーネントパターン

A-Frameのシーンにあるすべてのオブジェクトは<a-entity> (opens new window)で、それ自身は何もしない、空の

のようなものです。 そのエンティティにコンポーネント(WebやReact Componentsと混同しないようにしてください)を接続して、外観、動作、ロジックを提供します。

ボックスの場合、A-Frameの基本的なジオメトリ (opens new window)マテリアル (opens new window)のコンポーネントをアタッチして設定します。 コンポーネントはHTMLの属性として表現され、コンポーネントのプロパティはデフォルトでCSSスタイルのように定義されます。 <a-box> を基本的な構成要素に分解すると、以下のようになります。<a-box>はコンポーネントを包みます。

<!-- <a-box color="red" depth="0.5" height="0.5" shader="flat" width="0.5"></a-box> -->
<a-entity geometry="primitive: box; depth: 0.5; height: 0.5; width: 0.5"
          material="color: red; shader: standard"></a-entity>

コンポーネントの利点は、組み合わせ可能であることです。既存のコンポーネントを組み合わせて、さまざまなタイプのオブジェクトを構築することができます。 3D開発では、構築できるオブジェクトの種類は無限にあり、複雑なため、従来の継承ではなく、新しい種類のオブジェクトを簡単に定義する方法が必要になります。 2Dウェブの世界では、固定されたHTML要素の小さなプールを使って開発し、それらを階層化しています。

# ランダムカラーコンポーネント

A-Frameのコンポーネントは、JavaScriptで定義されていて、three.jsとDOM APIに完全にアクセスでき、何でもできます。 私たちは、すべてのオブジェクトをコンポーネントの束として定義しています。

ここでは、このパターンを使って、箱にランダムな色を設定するA-Frameコンポーネントを書いてみましょう。コンポーネントは、AFRAME.registerComponentで登録します。スキーマ(コンポーネントのデータ)とライフサイクルハンドラーメソッド(コンポーネントのロジック)を定義することができます。ランダムカラーコンポーネントの場合、スキーマは設定できないので、スキーマを設定することはありません。しかし、コンポーネントがアタッチされたときに一度だけ呼び出される init ハンドラは定義しておきます。

AFRAME.registerComponent('random-color', {
  init: function () {
    // ...
  }
});

ランダムカラーコンポーネントでは、このコンポーネントがアタッチされているエンティティにランダムな色を設定したいと思います。 コンポーネントはハンドラメソッドからthis.elでエンティティへの参照をしています。 そして、JavaScriptで色を変更するには、.setAttribute()を使ってマテリアルコンポーネントのcolorプロパティを変更します。 A-Frameは、いくつかのDOM APIの動作を少し強化していますが、APIはほとんどバニラウェブ開発を反映しています。 A-Frameを使ったJavaScriptとDOM APIの使い方について、詳しくはこちらをご覧ください (opens new window)

また、materialコンポーネントを、このコンポーネントよりも先に初期化するコンポーネントのリストに追加します。

AFRAME.registerComponent('random-color', {
  dependencies: ['material'],

  init: function () {
    // Set material component's color property to a random color.
    this.el.setAttribute('material', 'color', getRandomColor());
  }
});

function getRandomColor() {
  const letters = '0123456789ABCDEF';
  var color = '#';
  for (var i = 0; i < 6; i++ ) {
    color += letters[Math.floor(Math.random() * 16)];
  }
  return color;
}

コンポーネントが登録されると、このコンポーネントをHTMLから直接アタッチすることができるようになります。A-Frameのフレームワークの中で書かれたコードはすべてHTMLを拡張しており、その拡張内容は他のオブジェクトや他のシーンで使うことができます。 素晴らしいのは、開発者がオブジェクトに物理を追加するコンポーネントを書けば、JavaScriptを知らない人でも、自分のシーンに物理を追加できることです。

先ほどのボックスのエンティティに、 HTML属性でrandom-colorを付けて、random-colorコンポーネントをプラグインしてみましょう。このコンポーネントをJSファイルとして保存し、シーンの前にインクルードします。

See the Pen Minecraft VR Demo (Part 4: Random Color Component) by mozvr (@mozvr) on CodePen.

<script2 src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script2 src="components/random-color.js"></script>

<a-scene>
  <a-assets>
    <img id="groundTexture" src="https://cdn.aframe.io/a-painter/images/floor.jpg">
    <img id="skyTexture" src="https://cdn.aframe.io/a-painter/images/sky.jpg">
  </a-assets>

  <!-- Box with random color. -->
  <a-entity geometry="primitive: box; depth: 0.5; height: 0.5; width: 0.5"
            material="shader: standard"
            position="0 0.5 -2"
            random-color></a-entity>

  <a-cylinder id="ground" src="#groundTexture" radius="30" height="0.1"></a-cylinder>

  <a-sky id="background" src="#skyTexture" theta-length="90" radius="30"></a-sky>
</a-scene>

コンポーネントは、従来の継承のようにクラスを作成したり拡張したりすることなく、あらゆるエンティティにプラグインすることができます。例えば、<a-sphere><a-obj-model>に付けようと思えば、付けられるのです。

<!-- Reusing and attaching the random color component to other entities. -->
<a-sphere random-color></a-sphere>
<a-obj-model src="model.obj" random-color></a-obj-model>

このコンポーネントを他の人が使えるように共有したいと思えば、私たちもできます。A-Frameレジストリ (opens new window)は、Unityアセットストアのようなもので、このエコシステムから多くの便利なコンポーネントを集めています。コンポーネントを使用してアプリケーションを開発した場合、すべてのコードは本質的にモジュール化され、再利用可能になります。

# スナップコンポーネント

ボックスが重ならなず、かつグリッドにスナップさせるために、スナップコンポーネントを用意します。このコンポーネントの実装方法の詳細については説明しませんが、スナップコンポーネントのソースコード(20行のJavaScript) (opens new window)をご覧ください。

スナップ コンポーネントをボックスに取り付けて、0.5メートル毎にスナップするようにし、ボックスを中央に配置するためのオフセットを設定します。

<a-entity
   geometry="primitive: box; height: 0.5; width: 0.5; depth: 0.5"
   material="shader: standard"
   random-color
   snap="offset: 0.25 0.25 0.25; snap: 0.5 0.5 0.5"></a-entity>

これで、シーンのすべてのボクセルを記述するために使用できるコンポーネントのバンドルとして表されるボックスのエンティティができました。

# Mixin

再利用可能なコンポーネントの束を定義するために、mixin (opens new window)を作成することができます。 シーンにオブジェクトを追加する<a-entity>の代わりに、プレハブのようにボクセルを作成するために再利用できる<a-mixin>を使って記述することにしましょう。

See the Pen Minecraft VR Demo (Part 5: Mixins) by mozvr (@mozvr) on CodePen.

<script2 src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script2 src="components/random-color.js"></script>
<script2 src="components/snap.js"></script>

<a-scene>
  <a-assets>
    <img id="groundTexture" src="https://cdn.aframe.io/a-painter/images/floor.jpg">
    <img id="skyTexture" src="https://cdn.aframe.io/a-painter/images/sky.jpg">
    <a-mixin id="voxel"
       geometry="primitive: box; height: 0.5; width: 0.5; depth: 0.5"
       material="shader: standard"
       random-color
       snap="offset: 0.25 0.25 0.25; snap: 0.5 0.5 0.5"></a-mixin>
  </a-assets>

  <a-cylinder id="ground" src="#groundTexture" radius="30" height="0.1"></a-cylinder>

  <a-sky id="background" src="#skyTexture" theta-length="90" radius="30"></a-sky>

  <a-entity mixin="voxel" position="-1 0 -2"></a-entity>
  <a-entity mixin="voxel" position="0 0 -2"></a-entity>
  <a-entity mixin="voxel" position="0 1 -2"
            animation="property: rotation; to: 0 360 0; loop: true"></a-entity>
  <a-entity mixin="voxel" position="1 0 -2"></a-entity>
</a-scene>

このMix-inを使ってボクセルを追加します

<a-entity mixin="voxel" position="-1 0 -2"></a-entity>
<a-entity mixin="voxel" position="0 0 -2"></a-entity>
<a-entity mixin="voxel" position="0 1 -2"
          animation="property: rotation; to: 0 360 0; loop: true"></a-entity>
<a-entity mixin="voxel" position="1 0 -2"></a-entity>

次に、トラッキングされたコントローラーを使ったインタラクションによって、ボクセルを動的に生成していきます。それでは、アプリケーションに手を加えてみましょう。

# ハンドコントローラーを追加する

HTC ViveやOculus Touchのトラッキングコントローラを簡単に追加することができます。

<!-- Vive. -->
<a-entity vive-controls="hand: left"></a-entity>
<a-entity vive-controls="hand: right"></a-entity>

<!-- Or Rift. -->
<a-entity oculus-touch-controls="hand: left"></a-entity>
<a-entity oculus-touch-controls="hand: right"></a-entity>

ViveRiftの両方の操作を抽象化して動作させるハンドコントロールを使用し、基本的な手のモデルを提供します。 左手はテレポート、右手はスポーンやブロックの配置を担当するようにします。

<a-entity id="teleHand" hand-controls="hand: left"></a-entity>
<a-entity id="blockHand" hand-controls="hand: right"></a-entity>

# 左手にテレポーテーション機能を追加する

We'll plug in teleportation capabilities to the left hand such that we hold a button to show an arc coming out of the controller, and let go of the button to teleport to the end of the arc. Before, we wrote our own A-Frame components. But we can also use open source components already made from the community and just use them straight from HTML!

ボタンを押すとコントローラから円弧が出て、ボタンを離すと円弧の先端にテレポートするように、左手にテレポートの機能を追加します。 以前は、A-Frameのコンポーネントを自作していましたが、すでにコミュニティで作られているオープンソースのコンポーネントを使うこともできますし、HTMLからそのまま使うこともできます。

テレポートについては、@fernandojsgによる teleport-controls コンポーネント (opens new window)があります。 READMEに従って、<script>タグでコンポーネントを追加し、エンティティ上のコントローラにteleport-controlsコンポーネントを設定するだけです。

<script2 src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script2 src="https://unpkg.com/aframe-teleport-controls@0.3.x/dist/aframe-teleport-controls.min.js"></script>

<!-- ... -->

<a-entity id="teleHand" hand-controls="hand: left" teleport-controls></a-entity>
<a-entity id="blockHand" hand-controls="hand: right"></a-entity>

次に、teleport-controlsコンポーネントが円弧型のテレポートを使用するように設定します。デフォルトではteleport-controlsは地面のみテレポートしますが、collisionEntitiesでブロックと地面へのテレポートをセレクタで指定することができます。これらのプロパティは、teleport-controls コンポーネントが作成された API の一部です。

<a-entity id="teleHand" hand-controls="hand: left" teleport-controls="type: parabolic; collisionEntities: [mixin='voxel'], #ground"></a-entity>

これだけです! 1つのスクリプトタグと1つのHTML属性だけで、テレポートが可能になります。その他のクールなコンポーネントは、A-Frame Registry (opens new window)をご覧ください。

# 右手にボクセル発生機能を追加する

WebVRでは、2Dアプリケーションのようにオブジェクトをクリックする機能は組み込まれていません。自分たちで用意しなければならないのです。 幸いなことに、A-Frameにはインタラクションを処理するためのコンポーネントがたくさんあります。 VRでカーソルのようなクリックを行うには、レイキャスターというレーザーを照射し、交差したオブジェクトを返すという方法が一般的です。 そして、インタラクションイベントをリスニングし、レイキャスターの交差をチェックすることでカーソルの状態を実装しています。

A-Frameでは、コントローラのレーザーインタラクションのために、VRトラッキングされたコントローラにクリックレーザーを取り付けるlaser-controlsコンポーネントを提供しています。teleport-controlsコンポーネントと同様に、scriptタグを記述し、laser-controlsコンポーネントを付与します。今回は右手に付与します。

<script2 src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
<script2 src="https://unpkg.com/aframe-teleport-controls@0.3.x/dist/aframe-teleport-controls.min.js"></script>

<!-- ... -->

<a-entity id="teleHand" hand-controls="hand: left" teleport-controls="type: parabolic; collisionEntities: [mixin='voxel'], #ground"></a-entity>
<a-entity id="blockHand" hand-controls="hand: right" laser-controls></a-entity>

これで、トラッキングされたコントローラのトリガーボタンを引くと、laser-controlsは、コントローラとその時に交差しているエンティティの両方でクリックイベントを発します。mouseentermouseleaveのようなイベントも提供されます。このイベントには、交差ポイントに関する詳細が含まれています。

これでクリックの機能が提供されましたが、ブロックを生成するために、それらのクリックを処理するコードを配線する必要があります。イベントリスナーとdocument.createElementを使用することができます。

document.querySelector('#blockHand').addEventListener(`click`, function (evt) {
  // Create a blank entity.
  var newVoxelEl = document.createElement('a-entity');

  // Use the mixin to make it a voxel.
  newVoxelEl.setAttribute('mixin', 'voxel');

  // Get normal of the face of intersection and scale it down a bit
  var normal = evt.detail.intersection.face.normal;
  normal.multiplyScalar(0.25);

  // Get the position of the intersection and add our scaled normal
  var position = evt.detail.intersection.point;
  position.add(normal);

  // Set the position using intersection point. The `snap` component above which
  // is part of the mixin will snap it to the closest half meter.
  newVoxelEl.setAttribute('position', position);

  // Add to the scene with `appendChild`.
  this.appendChild(newVoxelEl);
});

交差イベントからエンティティを作成することを一般化するために、任意のイベントとプロパティのリストで構成できるintersection-spawnコンポーネントを作成しました。 実装の詳細は省きますが、シンプルなintersection-spawnコンポーネントのソースコードはGitHub (opens new window)で確認できます。右手にintersection-spawnの機能をアタッチしています。

<a-entity id="blockHand" hand-controls="hand: right" laser-controls intersection-spawn="event: click; mixin: voxel"></a-entity>

これでクリックするとボクセルを誕生させることができるようになりました!

# モバイルとデスクトップへの対応追加

コンポーネントを組み合わせることで、カスタムタイプのオブジェクト(クリック機能を持ち、クリック時にブロックを生成するハンドモデルを持つ追跡型ハンドコントローラ)を構築できたことがわかります。 コンポーネントの素晴らしいところは、他の文脈でも再利用できることです。intersection-spawnコンポーネントを視線ベースのカーソルコンポーネントと組み合わせて、モバイルとデスクトップでブロックを生成できるようにすることもできます。

<a-entity id="blockHand" hand-controls="hand: right" laser-controls intersection-spawn="event: click; mixin: voxel"></a-entity>

<a-camera>
  <a-cursor intersection-spawn="event: click; mixin: voxel"></a-cursor>
</a-camera>

# 試してみましょう!

Read the source code on GitHub (opens new window).

デスクトップでは、ドラッグ&クリックでブロックを生成することができます。 モバイルでは、デバイスをパンして、タップしてブロックを生成することができます。 VRヘッドセット(HTC Vive、Oculus Rift + Touchなど)を持っていれば、WebVR対応のブラウザ (opens new window)を使って、デモを試す (opens new window)ことができます。 HTC ViveやOculus Riftを接続し、WebVR対応のブラウザ (opens new window)を使用して、VRで試してみてください。

See the Pen Minecraft VR Demo (Final) by mozvr (@mozvr) on CodePen.

私たちのデスクトップやモバイルデバイスからVRで使用したときにどのような体験ができるかを表示したい場合は、事前に記録されたVRモーションキャプチャとジェスチャーを使ったデモ (opens new window)をチェックしてください。