360° Image Viewer インタラクティブに見渡すことのできる360°画像ギャラリーを作ってみましょう。 3つのパネルを作り、ユーザーはそれをクリックすることができます。クリックすると、背景がフェードアウトし、360°の画像が入れ替わります。

このガイドでは、エンティティコンポーネントに関連する3つのコンセプトを練習します。

  1. A-Frameに付属する標準コンポーネントを使用する。
  2. エコシステムのコミュニティコンポーネントを使用する。
  3. カスタムコンポーネントを作成し、やりたいことを実現する。 360度画像がA-Frameの注目のユースケースであるとは全く言いませんが、Web上での初期のユースケースとして多くの需要があるため、簡単な例として紹介します。

# 骨格

シーンの開始点です。 Glitch (opens new window)でRemixすることもできます.

<a-scene>
  <a-assets>
    <audio id="click-sound" src="https://cdn.aframe.io/360-image-gallery-boilerplate/audio/click.ogg"></audio>

    <!-- Images. -->
    <img id="city" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/city.jpg">
    <img id="city-thumb" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/thumb-city.jpg">
    <img id="cubes" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/cubes.jpg">
    <img id="cubes-thumb" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/thumb-cubes.jpg">
    <img id="sechelt" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/sechelt.jpg">
    <img id="sechelt-thumb" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/thumb-sechelt.jpg">
  </a-assets>

  <!-- 360-degree image. -->
  <a-sky id="image-360" radius="10" src="#city"></a-sky>

  <!-- Link template we will build. -->
  <a-entity class="link"></a-entity>

  <!-- Camera + Cursor. -->
  <a-camera>
    <a-cursor
      id="cursor"
      animation__click="property: scale; from: 0.1 0.1 0.1; to: 1 1 1; easing: easeInCubic; dur: 150; startEvents: click"
      animation__clickreset="property: scale; to: 0.1 0.1 0.1; dur: 1; startEvents: animationcomplete__click"
      animation__fusing="property: scale; from: 1 1 1; to: 0.1 0.1 0.1; easing: easeInCubic; dur: 150; startEvents: fusing"></a-cursor>
  </a-camera>
</a-scene>

これで私達は以下を先に定義しました。

  • <a-assets>内の[アセット管理システム]]amsにプリロードされるいくつかの画像。すべてのアセットが定義済みまたはプリロード済みである必要はないことに注意してください。
  • <a-sky> にある 360° 画像のプレースホルダー。
  • カメラに固定された、イベント駆動型アニメーションを使用したビジュアルフィードバック付きのカーソル

# Using Standard Components

標準コンポーネントは、A-Frameに同梱されているコンポーネントです。標準的なライブラリのようなものです。ここでは、これらのコンポーネントをエンティティにアタッチする方法と、HTMLから設定する方法を説明します。

今回は、テクスチャを貼った平面を作り、クリックすると360°画像が変化するリンクとして機能させたいと思います。まず、空のエンティティから始めます。コンポーネントがない場合、空のエンティティは何もせず、何も描画しません。

<a-entity class="link"></a-entity>

エンティティに形状を与えるために、平面形状に設定されたジオメトリコンポーネントをアタッチすることができます。コンポーネントのデータは、CSSのインラインスタイルに似た構文で指定します。

<a-entity
  class="link"
  geometry="primitive: plane; height: 1; width: 1"></a-entity>

次に、エンティティに外観を与えるために、マテリアルコンポーネントを取り付けます。シェーダーをフラットにして、画像が照明の悪影響を受けないようにします。 そして、src#cubes-thumbに設定します。これは、アセット管理理ステムにプリロードされている画像の1つへのセレクタです。また、画像の URL を渡すこともできます。

<a-entity class="link"
  geometry="primitive: plane; height: 1; width: 1"
  material="shader: flat; src: #cubes-thumb"></a-entity>

さらにコンポーネントをプラグインすることで、エンティティに機能を追加し続けることができます。もう一つ、標準的なコンポーネントであるサウンドコンポーネントを取り付けてみましょう。リンクをクリック(注視)すると、クリック音が鳴るようにしたいと思います。構文は前と同じですが、代わりにサウンドコンポーネントのプロパティを使用します。onclick に設定し、クリック時に音が鳴るようにしています。また、src#click-sound に設定し、<audio> 要素のセレクタを指定しています。

<a-entity class="link"
  geometry="primitive: plane; height: 1; width: 1"
  material="shader: flat; src: #cubes-thumb"
  sound="on: click; src: #click-sound"></a-entity>

これで、クリックするとクリック音が鳴るテクスチャ付きプレーンができました。

# コミュニティコンポーネントの利用

A-Frameには、標準のコンポーネントの小さなコアが付属していますが、多くのマジックは、A-Frameのエコシステムにある多数のオープンソースのコミュニティコンポーネントから生まれます。コミュニティコンポーネントは、npm (opens new window)などの場所から見つけることができます。それらをシーンにドロップして、そのままHTMLで使用することができます。コンポーネントは、あらゆることが可能で、何百行ものコードを1つのコンポーネントに抽象化し、HTML属性でプラグインすることができます。

ここでは、4つのコミュニティ・コンポーネントを使用する方法を説明します。

コミュニティのコンポーネントは、一般的にGitHubnpmで公開されています。コンポーネントをインクルードする簡単な方法は unpkg.com CDN (opens new window) を使うことです。npm でホストされているコンポーネントを script タグとしてインクルードでき、ファジーバージョンの指定もサポートされています。通常、コンポーネントのnpmパッケージ名とパスを知るだけで利用できます。

<html>
  <head>
    <title>360° Image Browser</title>
    <script2 src="https://aframe.io/releases/1.2.0/aframe.min.js"></script>
    <script2 src="https://unpkg.com/aframe-template-component@3.x.x/dist/aframe-template-component.min.js"></script>
    <script2 src="https://unpkg.com/aframe-layout-component@4.x.x/dist/aframe-layout-component.min.js"></script>
    <script2 src="https://unpkg.com/aframe-event-set-component@5.x.x/dist/aframe-event-set-component.min.js"></script>
     <script2 src="https://unpkg.com/aframe-proxy-event-component@2.1.0/dist/aframe-proxy-event-component.min.jss"></script>
    
  </head>
  <body>
    <a-scene>
      <!-- ... -->
    </a-scene>
  </body>
</html>

# リンク作成用テンプレート・コンポーネント

現在、リンクは1つです。360°画像ごとに1つずつ、合計3つのリンクを作成したいと思います。いずれもHTMLの定義を再利用できるようにしたいです。

その解決策の一つが、テンプレート・コンポーネント (opens new window)です。これは、テンプレート・エンジンをA-Frameのランタイムに統合するものです。これにより、エンティティのグループをカプセル化したり、データを渡してエンティティを生成したり、反復処理を行ったりすることができます。今回は、HTMLをコピー&ペーストすることなく、1つのリンクを3つにしたいので、テンプレート・コンポーネントを使うことができます。

理想的には、実行時に無駄なことをせず、ビルド時に行うことです(例:Super Nunjucks Webpack Loader (opens new window)を使用するなど)。 しかし、このチュートリアルでは、コンポーネントを簡単に説明するために、テンプレートコンポーネントを使用することにします。 実際には、Webpackのようなモジュールバンドルラーで行いたいところです。

テンプレート・コンポーネントのドキュメント (opens new window)を読むと、テンプレートを定義する1つの方法として、<head>内のscriptタグを使用する方法があることがわかります。このリンクをテンプレートにして、id を使って名前をつけてみましょう。

<head>
  <!-- ... -->
  <script2 id="plane" type="text/html">
    <a-entity class="link"
      geometry="primitive: plane; height: 1; width: 1"
      material="shader: flat; src: #cubes-thumb"
      sound="on: click; src: #click-sound"></a-entity>
  </script>
</head>

そして、そのテンプレートを使って、あまり手間をかけずに複数のプレーンを作ることができます。

<a-entity template="src: #plane"></a-entity>
<a-entity template="src: #plane"></a-entity>
<a-entity template="src: #plane"></a-entity>

でも、そうすると、どれも同じ画像テクスチャを表示して、同じように見えてしまいます。そこで、変数の代入が可能なテンプレートエンジンを使います。テンプレートコンポーネントには、単純なES6文字列補間 (opens new window)(つまり${var}形式)が付属しています。

テンプレートの各インスタンスをカスタマイズできるようにするため、テンプレート内に${thumb}変数を定義し、データ属性を使って渡せるようにします。

To allow each instance of the template to be customizable, we define a ${thumb} variable in the template, which we can pass using data attributes (opens new window):

<a-assets>
  <!-- ... -->
  <script2 id="plane" type="text/html">
    <a-entity class="link"
      geometry="primitive: plane; height: 1; width: 1"
      material="shader: flat; src: ${thumb}"
      sound="on: click; src: #click-sound"></a-entity>
  </script>
</a-assets>

<!-- ... -->

<!-- Pass image sources to the template. -->
<a-entity template="src: #plane" data-thumb="#city-thumb"></a-entity>
<a-entity template="src: #plane" data-thumb="#cubes-thumb"></a-entity>
<a-entity template="src: #plane" data-thumb="#sechelt-thumb"></a-entity>

テンプレートコンポーネントのおかげで、多くのHTMLを繰り返す必要がなく、非常に読みやすいシーンを維持することができます。

# レイアウトコンポーネントによるリンクの配置

エンティティのデフォルトの位置は0 0 0なので、エンティティは重なり合うことになります。各リンクを手動で配置することもできますが、代わりにレイアウトコンポーネント (opens new window)を使用して配置を行うことができます。レイアウトコンポーネントは、指定されたレイアウトに自動的に子要素を配置します。

リンクの周りにラッパーエンティティを作成し、ラインレイアウトを使用してレイアウトコンポーネントをアタッチします。

<a-entity id="links" layout="type: line; margin: 1.5" position="-3 -1 -4">
  <a-entity template="src: #plane" data-thumb="#city-thumb"></a-entity>
  <a-entity template="src: #plane" data-thumb="#cubes-thumb"></a-entity>
  <a-entity template="src: #plane" data-thumb="#sechelt-thumb"></a-entity>
</a-entity>

これで、位置を計算したりいじったりしなくても、リンクが重ならなくなりました。レイアウトコンポーネントは、グリッド、サークル、12面体など他のレイアウトもサポートしています。レイアウトコンポーネントは非常にシンプルですが、将来的には、シンプルな使い勝手を維持したまま、より強力なものになることが予想されます。

# ホバー時に視覚的なフィードバックを返すイベントセットコンポーネント

最後に、リンクにビジュアルフィードバックを追加します。カーソルを合わせたりクリックしたりすると、リンクが拡大・縮小されるようにしたいのです。これには、カーソルイベントに応答してスケール コンポーネントsetAttributes を実行するイベント リスナーを記述します。これはかなり一般的なパターンなので、イベントに応答してsetAttributeを実行するイベントセットコンポーネント (opens new window)が用意されています。

リンクにイベントリスナーを付けて、注視されたら拡大、クリックされたら縮小、注視されなくなったら縮小を行うようにしましょう。イベント名は、_eventプロパティか、下図のように__<id>で指定することができます。残りのプロパティは、setAttribute の呼び出しを定義しています。イベントセット・コンポーネントは、複数のインスタンスを持つことができることに注意してください。

<script2 id="link" type="text/html">
  <a-entity class="link"
    geometry="primitive: plane; height: 1; width: 1"
    material="shader: flat; src: ${thumb}"
    sound="on: click; src: #click-sound"
    event-set__mouseenter="scale: 1.2 1.2 1"
    event-set__mouseleave="scale: 1 1 1"
    event-set__click="_target: #image-360; _delay: 300; material.src: ${src}"></a-entity>
</script>

クリックするとフル画像が読み込まれるように、リンクの実体に data-src 属性を追加することを忘れないでください。

<a-entity template="src: #plane" data-src="#city" data-thumb="#city-thumb"></a-entity>
<a-entity template="src: #plane" data-src="#cubes" data-thumb="#cubes-thumb"></a-entity>
<a-entity template="src: #plane" data-src="#sechelt" data-thumb="#sechelt-thumb"></a-entity>

次に、実際に新しい背景画像を設定したいと思います。黒にフェードする効果を追加します。

最後のevent-set__clickはより複雑で、別のエンティティ(ID #image-360と書かれた背景)のプロパティを300msのディレイで設定し、テクスチャをmaterial.srcで設定します。300msのディレイは、テクスチャを設定する前に、黒にフェードするアニメーションを実行するためのものです。

# Proxy-Event コンポーネントによる背景の変更

次に、リンクをクリックすると実際に背景が変化するように配線したいと思います。あるエンティティから別のエンティティにイベントを渡すには、Proxy-setを使用します。これは、アニメーションを開始するために、リンクの1つがクリックされたことを背景に伝える便利な方法です。

<a-entity
  class="link"
  <!-- ... -->
  proxy-event="event: click; to: #image-360; as: fade"></a-entity>

リンクがクリックされると、背景(IDは#image-360)にもイベントが発行され、イベントの名前がclickからfadeに変更されます。では、このイベントを処理して、アニメーションを始めましょう。

<!-- 360-degree image. -->
<a-sky
  id="image-360" radius="10" src="#city"
  animation__fade="property: components.material.material.color; type: color; from: #FFF; to: #000; dur: 300; startEvents: fade"
  animation__fadeback="property: components.material.material.color; type: color; from: #000; to: #FFF; dur: 300; startEvents: animationcomplete__fade"></a-sky>

2つのアニメーションを設定し、1つは色が黒くフェードするように設定し、もう1つは色が通常通りにフェードするように設定します。animation__fadeは黒に設定し、先ほどプロキシ化したフェードイベントをリッスンしています。

animation__fadebackは、アニメーションが終了したときにアニメーションコンポーネントが発するanimationcomplete__fadeイベントをリスンし、animation__fadeが完了すると開始するという点で興味深いものです。これらのアニメーションを効果的に連鎖させています。

コンポーネントを使用することで、数十行のHTMLで多くのことを行うことができ、ほとんどのヘッドセットとブラウザでVRを動作させることができました。エコシステムは一般的なニーズに対して多くのものを提供していますが、明白ではないVRアプリケーションでは、アプリケーション固有のコンポーネントを記述する必要があります。これについては、コンポーネントを書くで説明し、できれば後のガイドでも説明したいと思います。

Try it out! (opens new window)