じゅころぐAR/VR

AR/VRメインのブログ。時々ドローン。

Actions on GoogleとAmazon API Gatewayを連携させる

前回に引き続き、Google Home向けのサービスを作って遊びます。
今回はFulfillmentを使って、AWSにWebhookさせてみます。

Fulfillment  |  Dialogflow

概要

処理のフローは以下になります。

Google Homeに話しかける
→ Actions on GoogleのAppを呼び出し
→ Appにメッセージ送信
→ DialogflowのIntentsからFulfillmentでWebhook
→ Webhook先のAmazon API Gatewayで処理を行う
→ 結果(発話・テキスト)をDialogflowに返す

Google Homeに話しかけてDialogflowで定型文を返すところまでは簡単にできるので、FulfillmentでAmazon API Gatewayと連携する部分を作ります。

Fulfillmentを設定する

外部サービスへのWebhookを行うには、Fulfillmentを設定します。
Dialogflow内にメニューがあります。

f:id:jyuko49:20171213002917p:plain

とりあえずは連携だけ確認したいので、Webhook先のURLだけ設定します。
URLは後述のAmazon API Gatewayで作成したものです。

f:id:jyuko49:20171213004124p:plain

画面の下の方にSaveボタンがあるので、忘れずに更新してください。

Amazon API GatewayAPIを作る

API GatewayAWSの各サービスや外部のWebAPIをエンドポイントとして、APIを提供できるサービスです。

AWSアカウントでログインし、API Gatewayのコンソール画面を開いたら、まず[APIの作成]を行います。
API名はリクエストURLには含まれないので、わかりやすい名称なら何でもOK。その他は任意入力。

f:id:jyuko49:20171213005948p:plain

APIが作成されたらリソース、メソッドを作ります。リソース、メソッドに付いては一般的なRESTの概念なので、この場では説明を割愛。

アクション > メソッドの作成(リソースの作成)で追加できます。今回はリソースを作らず、ルート直下にPOSTメソッド(FulfillmentでのWebhookがPOSTのため)だけ作りました。

f:id:jyuko49:20171213012612p:plain

メソッドを作成したらエンドポイントにつなぎます。
設定次第でいろいろなサービスと連携できるのですが、今回はMockで固定値を返すようにします。

f:id:jyuko49:20171213013852p:plain

Mockの出力は統合レスポンスブロックの本文マッピングテンプレートになります。
FulfillmentのResponse仕様に沿って、レスポンスを整形します。

f:id:jyuko49:20171213014949p:plain

ここまで設定できたら、テスト実行が行なえます。出力固定のMockなので、何も入力せずに[テスト]ボタンを押して、レスポンス本文がマッピングテンプレートの通りになっていれば正しく設定できています。

f:id:jyuko49:20171213015229p:plain

テストが期待通りになっていたら、アクション > APIのデプロイで外部からアクセスできるようにします。

デプロイはステージを指定するようになっているので、運用前ならtest、betaなど、本運用に入ったらv1,v2,v3,...と切っていくと並行運用ができます。
デプロイメントの説明に書いた内容はデプロイ履歴に一覧で残りますし、デプロイ履歴からのロールバックもワンクリックでできるので、書いておくと便利です。

f:id:jyuko49:20171213004102p:plain

デプロイが完了すると、作成したステージに呼び出し用のURLが表示されます。
東京リージョン(ap-northeast-1)で作ったのであれば、以下になっているはず。

https://[ランダムな文字列].execute-api.ap-northeast-1.amazonaws.com/[ステージ]/([リソース]/...)

このURLをFulfillmentに設定してあげれば、Dialogflowから叩けるようになります。

Intentsを設定する

ここまでできれば、あと少し。
Dialogflowに戻って、Intentsを追加します。

User saysにメッセージを定義して、Fulfillmentの"Use webhook"にチェックを入れます。

f:id:jyuko49:20171213022535p:plain

レスポンスはWebhookで返すので、Responseの欄は何も入力しなくてOKです。
設定したらSaveを忘れずに。

Actions on Googleでテストする

Dialogflowで設定したActionをActions on GoogleのSimulatorでテストします。
マイクが使えない場合はキーボード入力で確認できます。

f:id:jyuko49:20171213021747p:plain

IntentsのUser saysに設定したメッセージを送信すると、Amazon API Gatewayに定義されているMockのメッセージが発話・テキスト表示されました!

まとめ

FulfillmentによるWebhookは連携URLを指定するだけで簡単です。
リクエスト先では指定のフォーマットでレスポンスを返す必要があり、AWSを利用するのであれば、API GatewayやLambdaファンクションで整形してあげるとよいです。

Mockではなく、エンドポイントをAWSサービスやWebサービスとの連携にすれば、対話形式での検索やデータベースへの登録もできるし、セッションが管理できればインターネットでの予約・購入など、割と何でもできそう。
今はまだ定型文のメッセージでしか操作していないので、Entities、Actions and Parameters、Eventsなどを理解して使いこなせるようになりたい。

Google Homeと連携するサービスを自作するには

半額だったのでGoogle Home Mini買ったけど、何ができるかちゃんと調べてなかったので自分用メモ。

Google HomeからSNSに投稿する

IFTTT(イフト)を使うと、プログラミングなしでできるようだ。
ちなみに、アイエフティーティーティーだと思ってました。

yuki-no-yabo.com

Node.jsでGoogle Homeに通知を送る

google-home-notifierを使って、Google Homeにpush通知する使い方。

github.com

### 適当なディレクトリを作成して移動
$ mkdir googole-home
$ cd googole-home

### npmでインストール
$ npm init
$ npm install google-home-notifier

### vimでテスト用のスクリプト作成
$ vim index.js

index.jsはこんな感じです。メッセージと言語を変えた以外は、ほぼサンプルそのまま。

const googlehome = require('google-home-notifier')
const language = 'ja';

googlehome.device('Google-Home', language);

googlehome.notify('テストだよ', function(res) {
  console.log(res);
});

nodeコマンドで実行してみた。

$ node index.js
Device "Google-Home-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" at 192.168.0.5:8009
Device notified

実行して数秒後、Google Homeで通知音が鳴って、"テストだよ"と発話されました。
同一ネットワーク上のデバイスが自動で検出されて、名前に"Google-Home"を含む場合にpush通知が行われるようです。数秒のタイムラグはデバイスの検出にかかる時間も大きいと思われる。

利用シーンとしては、README.mdのListenerに書いてある手順でMacかラズパイにローカルサーバ立てといて、屋内のセンサデバイスからサーバにメッセージをPOST、Google Homeに発話させる形がよさそう。

Actions on Googleでチャットbotを作る

Dialogflowとやらを使うと、Google Homeが認識したメッセージに任意の応答を返せるようです。

つい最近までapi.aiという名称だったようで、以下の記事の手順で設定できました。 qiita.com

定型文に定型文を返すだけのbotは簡単にできます。

Fulfillmentを使えば、DialogflowからのWebhookで結果を返すこともできるらしい。
Fulfillment  |  Dialogflow

Dialogflowだけでもメッセージを複数パターン用意できるけど、WebhookすればDBやAIを使えるのでより柔軟な応答ができるはず。

Webサービスはpublicに公開されていてHTTPSで応答できないとダメなので、クラウドで立てるのが楽そうです。GoogleのドキュメントにはFirebaseやHerokuにデプロイする方法が書いてあります。たぶんAWSでやるけど。

WebAR-Articleを動かしてみた

WebARに注目して定点観測しているGoogle ARGithubプロジェクトにWebAR-Articleなるリポジトリが追加されていました。
実際に動かしてみたところ、WebサイトにARコンテンツを埋め込むサンプル(Google非公式扱い)でした。

github.com

動かしてみる

READMEのGetting Startedに従って、インストール&実行していきます。

Macのターミナルを起動し、以下のコマンドを実行します。
実際にはインストールログがたくさん出ます。

$ git clone https://github.com/google-ar/WebAR-Article.git
$ cd WebAR-Article/
$ npm install

無事インストールができたら、npm run devで実行してみます。

$ npm run dev
...
webpack: Compiled successfully.

上記のメッセージが出たら、webpackでローカルサーバが立ち上がっています。この状態のままブラウザ(Chrome等)でhttp://localhost:8000/public/にアクセスするとWebページが閲覧できるはずです。
プロセスを停止したいときは、ターミナルでCtrl+Cです。

PCブラウザで見ると以下のようなWebページになっています。 f:id:jyuko49:20171206124957p:plain

宇宙服の3Dモデルが表示されているcanvas(画像の右下)でthree.js,three.ar.jsが使われています。
フルスクリーンボタンをクリックすると全画面で3Dモデルをグリグリ動かすことができ、✕ボタンで元の記事に戻ります。 f:id:jyuko49:20171206231230p:plain

WebARブラウザで見る

現状ChromeSafariにWebARの機能は実装されていないため、Google ARからWebARブラウザをインストールする必要があります。

各ARプラットフォームに対応したスマートフォンを用意してブラウザアプリをインストールします。
ARCore、Tangoはリンク先からAPKファイルを使ってインストールするだけでOKで、ARKitだけXcodeでのビルド&インストールが必要です。

jyuko49.hatenablog.com

WebARブラウザをインストールしたら、MacのローカルIPを使い、先程立ち上げたローカルサーバに同一のローカルネットワークから接続します。
MacのIPは、ターミナルでifconfigを実行すれば調べられます。

$ ifconfig
...
    inet 192.168.0.4 netmask 0xffffff00 broadcast 192.168.0.255
...

localhostの部分をこのIPアドレスに変えて、http://192.168.0.4:8000/public/をアドレスバーに入力すると、WebARonTangoでWebAR-ArticleのWebページが見えました。 f:id:jyuko49:20171206223323j:plain

canvasには、WebARブラウザで見たときだけ、AR閲覧用のボタン(右下)が表示されています。

このボタンをタップするとARモードに切り替わり、平面に3Dモデルを置いて見ることができます。フルスクリーンと同様に✕ボタンで元記事に戻れます。 f:id:jyuko49:20171206223240j:plain

ざっくり構成を理解して使う

WebAR-ArticleはApache 2.0ライセンスのため、ライセンス表記さえ守ればで、一部改変して利用ができます。

サンプルのWebページはpublic/index.html内でindex.min.jsを読み込んでおり、id="arapp"を付与したcanvasにWebAR対応コンテンツを埋め込んでいます。
その他の部分は、通常のWebと同様に記事を書けばよさそうです。

<canvas id="arapp" height=400></canvas>
<script src="js/index.min.js "></script>

canvasに表示するコンテンツの実体はsrc以下のスクリプトで、npm run buildを実行するとwebpackでバンドル・圧縮されて/public/js/index.min.jsが作られます。

$ npm run build

開発の手順としては、

src以下のソースを改変
npm run build
→index.htmlでindex.min.jsをinclude
canvas要素(id="arapp")を追加 →public以下のファイルをWebで公開

になります。

srcに手を入れると言ってもそこそこ複雑な作りなので、three.jsに詳しくなければ、以下の3ファイルをざっと見て、3Dモデルを差し替えて使う程度がベターだと思います。

  • src/index.js
    コンテンツを表示するcanvas要素のタグを定義しています。実質的なメイン処理はApp.jsです。
  • src/app/AppBase.js
    App.jsの基底クラスです。three.ar.jsを使って3Dシーンの基本構成を行なっています。
  • src/app/App.js
    シーンを構成するコンテンツをセットアップするメイン処理です。

3DモデルはApp.jsのloadMaterialloadModelでloadしており、このメソッド内に記載されているファイルパスを書き換えれば変更できます。

import DRACOLoader from '../../third_party/draco/DRACOLoader';

...

  loadMaterial = () => {
    let mtlLoader = new THREE.MTLLoader();
    mtlLoader.setPath('../public/models/Astronaut/');
    mtlLoader.load('Astronaut.mtl', materials => {
      materials.preload();
      this.loadModel(materials.materials.Astronaut_mat);
    });
  };

  loadModel = material => {
    let dracoLoader = new DRACOLoader('../third_party/draco/', {
      type: 'js',
    });

    dracoLoader.load('../public/models/Astronaut/Astronaut.drc', geometry => {
      geometry.computeVertexNormals();

      this.setupModel(new THREE.Mesh(geometry, material));
      this.setupModelShadow();
      this.setupLights();
      this.setupARControls();
      this.setupAREffects();
      this.setupReticle();
      this.setupModelTween();
      this.parseURL();
      this.HUD.hideLoadingIndicator();
    });
  };

ファイルフォーマットにDracoが使われているので、別のフォーマットを使いたい場合はパスの変更だけでなく、loaderも切り替える必要があります。
three.jsのリポジトリサードパーティ製ライブラリが揃っているので、必要なライブラリをimportして使えば問題ないと思います。

所感

Webサイトの記事にAR対応の3Dコンテンツを埋め込んで使う利用方法は、画像やブラウザ内の3D表示よりも実物をイメージしやすくてよいと感じます。
ショッピングサイトやニュースなど、利用シーンも想像しやすいです。

ただ、WebAR全般の課題としてARCore/ARKitの対応デバイスが普及し、機能がChromeSafariに標準搭載されてくれないと利用者が限定されてしまうため、実際にこのような使い方ができるのは、もう少し先の話になりそうです。

また、このサンプルだとthree.jsやwebpackを使えないとWebARコンテンツが変更できないので、A-FrameのようにHTMLレベルでモデルデータを指定して埋め込めるとより手軽に使えてよいですね。

Lenovo Mirage AR ヘッドセットでthree.jsをホログラム表示する

ジェダイ・チャレンジを購入して手に入れたLenovo Mirage AR ヘッドセットは、スマートフォンの画面で表示されている映像をホログラムで表示してくれます。
f:id:jyuko49:20171202133009j:plain

このヘッドセットを使って、自作のコンテンツをホログラム表示してみました。

ホログラム表示するには

Mirage AR ヘッドセットはトレイに入れたスマートフォンの画面をミラーで反射させています。
スマートフォンで表示されているコンテンツは、モバイルVRでよく使われている視差効果の付いた背景が黒のステレオ画面です。

また、トレイに入れると画面の一部が隠れるため、穴が開いている領域を左右に二分割したコンテンツを表示させる必要があります。 f:id:jyuko49:20171202161839j:plain

実装方法

上述の表示ができれば、プラットフォームは何でもよいのですが、慣れているWebでやってみました。

three.jsでステレオ表示を行う

Webでの3D表示にはthree.jsを使っています。
downloadで保存されるzipファイルを展開して、htmlファイルと同じ場所に置いておきます。

今回はステレオ表示を行うためthree.min.jsの他にStereoEffect.jsも使います。

<script src="three.js-master/build/three.min.js"></script>
<script src="three.js-master/examples/js/effects/StereoEffect.js"></script>

通常のレンダリングではanimate()に相当する処理でrenderer.render()を実行しますが、今回はStereoEffectを通します。

使い方は簡単で、まずStereoEffectrendererを渡します。
.setEyeSeparation()で左右の映像に視差を付けることができます。

effect = new THREE.StereoEffect(renderer);
effect.setEyeSeparation(0.5);

上記で初期化は完了で、renderer.render()の代わりにeffect.render()を実行すれば、ステレオで表示されます。 f:id:jyuko49:20171203224003j:plain

背景はデフォルトで黒なので、そのままにしておきます。

PWAで全画面表示にする

ステレオ表示はできたので、トレイに合わせて描画位置を調整したいのですが、ブラウザのアドレスバーが邪魔で上手く位置が調整できません。
そこで、PWA(Progressive Web Apps)を使って、全画面表示にします。

PWAはざっくり言うと、Webアプリをネイティブアプリのように動かす仕組みです。
Progressive Web Apps

Chromeで"ホーム画面に追加"を行い、ホーム画面のアイコンから起動することでPWAとして動作します。 f:id:jyuko49:20171203232420j:plain f:id:jyuko49:20171203232344j:plain

PWAとして動作させることで、全画面表示ができるようになります。プラットフォームによって対応状況が異なり、設定方法は複数あります。

今回はChromeを使って、Web App Manifestで設定しました。
全画面表示&横画面となるようにmanifest.jsonを作成してhtmlからリンクします。

<link rel="manifest" href="manifest.json">

これでアドレスバーが非表示になりました。 f:id:jyuko49:20171203234333j:plain

描画位置を調整する

最後にトレイの位置に合わせて描画位置を調整します。

描画領域の大きさはrenderer.setSize()で変更できます。

var width = 630; //端末に合わせて調整
var height = 275; //端末に合わせて調整
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( width, height );
document.body.appendChild( renderer.domElement );

描画領域の位置はCSSで調整しました。
renderer.domElementで追加されるのはcanvas要素なので、canvasにpaddingを付けることで調整できます。

canvas {
  padding-left: 76px; /* 端末に合わせて調整 */
}

端末によって必要な余白が異なるため、実際にトレイに入れながら微調整しました。 f:id:jyuko49:20171204000117j:plain

調整できたらbodyの背景も黒にしておきます。 f:id:jyuko49:20171203224104j:plain

試してみる

実際にMirage AR ヘッドセットで見てみるとホログラム表示になりました。

まとめ

three.jsのステレオ表示を使えば、Mirage AR ヘッドセット向けのコンテンツが簡単に作れます。

rendererのサイズと位置を調整して、StereoEffectをかけているだけなので、シーンの作成は通常のthree.jsと同様に行えます。
three.jsに慣れていれば、コンテンツの差し替えは容易です。 f:id:jyuko49:20171203224147j:plain コロコロ (c) gdgd妖精s (ぐだぐだフェアリーーーズ), 2代目gdgd妖精s

現在はホログラムで表示させているだけでヘッドセットが動いても見え方が変わりませんが、スマートフォンの回転やBluetooth接続した機器の情報は使えるので、作り込めば色々できそうな気はします。

ジェダイ・チャレンジでフォースの覚醒めを感じる

突然ですが、この度、ジェダイ見習いになりました。

ジェダイになるには

誰でもすぐにジェダイになれる訳ではありません。
正義の心と強靭な精神力、そしてジェダイ・チャレンジとスマートフォンを用意する必要があります。

ジェダイ・チャレンジを手に入れる

AmazonLenovoの直販サイト、家電量販店などで購入できます。
ヘッドセット、ライトセーバー・コントローラー、ビーコンのセットで価格は3万円くらい。

Lenovo Star Wars™/ジェダイ・チャレンジ ARヘッドセット ZA390004JP / ライトセーバー・コントローラー付属

www3.lenovo.com

対応スマートフォンを手に入れる

ジェダイ・チャレンジは、スマートフォン(Android/iOS)にアプリをインストールして使います。

play.google.com

Star Wars™: Jedi Challenges

Star Wars™: Jedi Challenges

  • Disney
  • エンターテインメント
  • 無料

あとで詳しく書きますが、アプリがインストールできても対応端末でないと遊べない可能性があるので、Lenovoの公式サイトで事前にチェックしておきましょう。
と言いつつ、私はASUS ZenFone ARを使っています。

対応端末(2017/12時点)

装備品をチェックする

いざ開封。

ライトセーバー・コントローラー

スターウォーズと言えばこれ。ライトセーバー

刀身はなく、ARで表示されます。
白や青色に光ります。

Bluetoothでアプリと接続します。 f:id:jyuko49:20171202141813j:plain

ライトセイバーも8の字に回すといいらしい。 f:id:jyuko49:20171202141701j:plain

マイクロUSBケーブルで充電ができます。

ラッキング・ビーコン

1.5-2.5m前方にビーコンを置くことで、ヘッドセットに付いたカメラがビーコンの位置を捉え、トラッキングしてくれます。

怪しく光るので、ムードを出したいときの間接照明にも使えます。

ビーコンは充電式ではなく、単三電池が2本。

Lenovo Mirage AR ヘッドセット

スマートフォンの画面をARでホログラム表示してくれます。

横蓋を開くと、マイクロUSBコネクタがあり、スマートフォンを収納するトレイが取り出せます。

スマートフォンとヘッドセットをつなぐコネクタはUSB-C、マイクロSD、Lightning(iPhone用)の3種類が付属していました。

トレイはZenFone AR(画面5.7インチ、サイズ77.7mm × 158.98mm)がちょうど入る大きさです。これ以上大きいスマートフォンだと上手く収まらない可能性があります。

また、トレイで画面が半分程度隠れるため、端末に合わせて表示エリアを調整する必要があります。対応端末であれば専用の設定がありますが、非推奨の端末は近い設定を選ぶ形になり、ホログラムが正しく表示されない可能性があります。
ZenFone ARはMoto Z2の設定が一番違和感なく見えました。 f:id:jyuko49:20171202172718j:plain

ゴーグルを装着して見ると、ホログラムで見えます。

ライトセーバー・バトル

ゲームモードは3つありますが、ジェダイを目指すなら、やはり"ライトセーバー・バトル"で実践的な訓練を積むのがよさそうです。

通常戦闘では遠い敵のライフルを弾き返しつつ、近付いた敵を斬っていきます。
ホログラムの撮影は難しいので、スマートフォンの画面を録画しました。

ゲームを進めていくと、ダース・モールダース・ベイダーなどがボスとして登場します。相手の攻撃に合わせて、ライトセイバーの向きを変えて防ぎ、隙ができたら斬りつけます。
こちらは公式の動画があります。

youtu.be

まとめ

発表されたときから期待していたものの、実際に体験するとすごく楽しいです。相手の動きに合わせてライトセーバーを振るので、結構身体も動かします。

ホログラム表示も鮮明ですし、画面の描画領域さえ調整すれば自作アプリのホログラム表示にも使えそうです。ライトセーバー付きのARヘッドセットが3万円で買えると思えば、損はない感じでした。

対応端末とほぼ同じサイズでないと使えないかもしれないので、購入する場合は、最新の対応端末を確認しましょう。
非対応機種で試す場合は自己責任で。

めざせ!ジェダイ・マスター!

A-FrameでPhysicsを扱う

前回に引き続き、A-Frameで簡単なVRアプリを実装してみます。
今回は、aframe-physics-systemによる物理演算のサンプルです。

動作デモ

先に何ができるかイメージした方がよさそうなので、デモ動画を載せておきます。
前回同様にgdgd妖精sMMDを使用したので、サーバ公開はなしで動画のみです。

model: シルシル、コロコロ、ピクピク
copyright: (c) gdgd妖精s (ぐだぐだフェアリーーーズ), 2代目gdgd妖精s

カメラの向いている方向にボールを飛ばし、的にぶつけて落としています。また、衝突を検知してパーティクルによるエフェクトの実装も試しました。

aframe-physics-systemの利用

A-Frameで物理演算を行うには、aframe-physics-systemを使うのが簡単です。
aframe-physics-systemを単体で利用してもよいですが、aframe-extrasにインクルードされているため、他の機能を利用することも考えて、aframe-extrasを使った方が便利です。

HTML内で"aframe-extras.min.js"をリンクすれば物理演算を含む拡張機能が使えるようになります。

<script src="https://cdn.rawgit.com/donmccurdy/aframe-extras/v3.12.4/dist/aframe-extras.min.js"></script>

今回はサンプルなのでCDNからリンクしました。もちろんローカルコピーでもOKです。
規模が大きくなったらnpmで管理した方がいいかなと思いますが、まあそのうち。

シーンの設定

物理演算を有効にするには、<a-scene>タグにphysics属性を付けます。

 <a-scene physics="debug: false;">

プロパティでは重力、反発係数、摩擦係数など指定できます。
※シーン全体の設定であることに注意

debugプロパティをtrueにすると、physicsの当たり判定(collider)がワイヤーフレームで表示されます。 f:id:jyuko49:20171126210708j:plain

static-body、dynamic-bodyの指定

物理演算を行うentityに、static-bodydynamic-bodyのいずれかの属性を付与します。
どちらも付与していないentityは他の物体に関与せず、衝突せずに通り抜けます。

static-bodyを付与すると位置は固定され、衝突した物体に影響を与えます。床や壁などに指定します。

<a-entity id="board"
  geometry="primitive: box; depth: 10; height: 0.5; width: 40" material="color: #deb887"
  static-body
  position="0 0 -20">
</a-entity>

dynamic-bodyを付与すると、重力や外部からの力を受けて反射・摩擦を行います。ボールを飛ばす場合や動かせる障害物などに指定します。
dynamic-bodyの場合は、プロパティでcolliderの形状(shape)や物体の質量(mass)が指定できます。

<a-entity id="コロコロ"
  mmd-model="model: コロコロ/コロコロ.pmx;"
  dynamic-body="shape: box; mass: 10"
  position="0 5 -20" scale="0.5 0.5 0.5">
</a-entity>

shapeのデフォルト値はautoになっており、基本的なオブジェクトであれば適切な形状を設定してくれます。
ただし、各種3Dフォーマットからロードした複雑な形状のオブジェクトの場合、autoだとcolliderが設定されないことがあり、boxcylindersphereのいずれかが無難です。
この辺りは、シーンの設定でdebugを有効にしながら設定するとわかりやすいです。

ボールを投げる

シューティングゲームやボールを投げてモンスターを捕まえるゲーム用に、ボールを投げる処理を実装してみます。

dynamic-body属性を付与された要素は、自動的にvelocity属性を持ちます。このvelocity属性に飛ばしたい方向の速度ベクトルを設定してあげれば、後は物理演算ライブラリがよしなにしてくれます。
処理はJavaScriptになりますが、HTML要素の操作になるのでgetAttribute()setAttribute()appendChild()など、Web制作で見慣れたコードになります。

サンプルコードにコメントを入れたので参考にしてください。

var _shot = function () {
  // カメラの位置と向きを取得する
  var camera = document.getElementById('camera');
  var rotation = camera.getAttribute('rotation');
  var position = camera.getAttribute('position');

  // カメラ前方方向に速さを掛けて、速度ベクトルを求める
  var velocity = new THREE.Vector3();
  var speed = 50;
  velocity.x = Math.sin(rotation.y * Math.PI / 180) * Math.cos(rotation.x * Math.PI / 180) * -speed;
  velocity.y = Math.sin(rotation.x * Math.PI / 180) * speed;
  velocity.z = Math.cos(rotation.y * Math.PI / 180) * Math.cos(rotation.x * Math.PI / 180) * -speed;

  // <a-entity>を新規作成して速度を与える
  var ball = document.createElement('a-entity');
  ball.setAttribute('id', 'ball');
  ball.setAttribute('velocity', velocity);
  ball.setAttribute('position', position);
  ball.setAttribute('geometry', 'primitive: sphere; radius: 0.5');
  ball.setAttribute('dynamic-body', 'mass: 1');

  // <a-scene>に追加する
  var scene = document.querySelector('a-scene');
  scene.appendChild(ball);
};

// keydownイベントでボールを発射する処理を呼び出す
document.addEventListener('keydown', _shot);

dynamic-bodyが付与されていれば、同じ要領でボール以外も飛ばせます。

衝突時に処理を行う

物体が他の物体と衝突した際に、対象オブジェクトを消したり、スコアを加算したり、アニメーションを起こしたりと何らかの処理を行いたいケースは多いです。

static-bodyまたはdynamic-bodyを設定したentityにcollideイベントを登録することで、他の物体と衝突した際の処理を記述できます。

サンプルとして、パーティクルを使ったエフェクトを実装してみました。

var _init = function () {
  // collideイベントを登録したい要素を取得する
  var targetElement = document.getElementById('コロコロ');

  // collideイベントを登録する
  targetElement.addEventListener('collide', function (e) {
    // ボール(id="ball")と衝突した場合のみ処理を行う
    if (e.detail.body.el.id === 'ball'){
      // 衝突したボールと同じ位置にパーティクルを発生させる
      var effect = document.createElement('a-entity');
      var position = e.detail.body.el.getAttribute('position');
      effect.setAttribute('position', position);
      effect.setAttribute('particle-system', 'color: #EF0000,#44CC00; particleCount: 500; duration: 0.2;');

      // パーティクルを<a-scene>に追加する
      var scene = document.querySelector('a-scene');
      scene.appendChild(effect);
    };
  });
};

// DOMのloadが終わってから処理する
document.addEventListener('DOMContentLoaded', _init);

サンプルでは衝突したオブジェクトの情報をe.detail.body.elから取得しています。同様に、衝突されたオブジェクトや衝突地点の情報も取得できます。
※詳細はこちらを参照

エフェクトには、aframe-particle-systemを使っています。
こちらは、aframe-extrasに含まれないので別途リンクしてください。(下記はCDNの例)

<script src="https://unpkg.com/aframe-particle-system-component@1.0.x/dist/aframe-particle-system-component.min.js"></script>

ソースコード(サンプル)

冒頭の動画のうち、物理演算を使った処理は粗方説明したので、ソースコード全文を載せておきます。
MMDモデルの取得のみコピペでは動かないので、手持ちのモデルデータに置き換えて使ってください。

A-Frameだと100行に満たないHTMLで色々なサンプルが作れちゃうので、Gistとの相性がよいですね。

まとめ

aframe-physics-systemを使った物理演算を行ってみました。

Unityなどのゲームエンジンに比べると機能がシンプルで制約もありますが、基本的な処理は実装できます。

A-Frameの特徴に従い、HTML要素やイベントリスナの操作になるので、Webフロントエンドの基礎レベルの知識があれば実装できてしまう点が一番のメリットかなと思います。

A-Frameでnav meshを使う

WebVRフレームワークのA-Frameは、HTML/JavaScriptで記述でき、様々なコンポーネントを組み合わせて使うことで簡単にマルチプラットフォーム向けのVRコンテンツが作成できます。

便利な機能がまとめてincludeできるaframe-extrasを眺めていたところ、nav meshによる移動を扱うコンポーネントを見つけたため、試しに実装してみました。

概要

READMEを見ると、aframe-extrasのnav-meshコンポーネントは内部でPatrolJSを利用しているようです。 A-Frameはthree.jsを内包しているため、three.js向けのプラグインが活用しやすくなっています。

nav meshの作成方法を含む詳しい使い方は、以下の記事に書かれています。

medium.com

Blenderを使ってnav meshを作成し、glTFでエクスポートしてA-Frameで使うようです。

nav meshの作成

参考記事に倣って、Blenderで作成します。

適当にオブジェクトを結合してシーンに配置した後、以下の手順を行うとNavmeshが追加されます。

  • 画面上部メニューで"Blenderゲーム"モードに変更
  • 画面右のパネルで"Scene"を選択
  • nav meshの対象となるオブジェクトを選択
  • パラメータを調整して、ナビゲーションメッシュを構築

f:id:jyuko49:20171123232658p:plain

glTFでエクスポート

作成したNavmeshをA-Frameで利用するため、エクスポートします。

glTF(GL Transmission Format)は、OpenGLと親和性が高く、通信とロードを効率化した3Dフォーマットです。WebGLベースで3Dデータをネットワーク経由で配布するWebVRに適したフォーマットと言われています。

BlenderからglTFでエクスポートするにはアドオンを追加する必要があります。 Blenderのアドオン一覧には表示されていないため、下記のGithubからダウンロードします。

github.com

複数ファイルで構成されているアドオンのため、BlenderのUIでファイルから追加を行なってもアドオン一覧に表示されません。 対応としては、PC内でBlenderのアドオンが保存されているフォルダを探して、"io_scene_gltf2"を丸ごとコピーしてからBlenderを再起動するとアドオンが表示され、有効にできます。

f:id:jyuko49:20171124014438p:plain

アドオンの追加は以下の記事が参考になりました。 qiita.com

アドオンが有効になったらNavmeshを選択した状態で、"ファイル" > "エクスポート" > "glTF 2.0 (.gltf)"でエクスポートできます。

Navmeshを作成する際は"Export selected only"にチェックしないと、元のメッシュごとエクスポートしてしまいます。

f:id:jyuko49:20171124014716p:plain

エクスポートに成功すると、.gltfと.binの2ファイルが同じ階層に作成されているはずです。

navmesh.gltfの確認

エクスポートしたglTFのnav meshが正しく作成されているかは、glTF Viewerで確認できます。

gltf-viewer.donmccurdy.com

無料で使えるWebのビューアで.gltfと.binの2ファイルをまとめてドラッグ&ドロップすればOKです。

f:id:jyuko49:20171124002810p:plain

A-Frameでの実装

サンプルとして70行程度のHTMLファイル1つで記述しました。

移動先を決定する処理は元記事のJavaScriptをそのまま利用、移動するエージェントはglTFではなくa-mmdMMDフォーマットを使っています。

ポイントとして、nav meshオブジェクトに"nav-mesh"属性、エージェントに"nav-controller"属性を付けます。

移動先の変更はclickイベント発生時のスクリプトで制御しています。"nav-controller"にdestinationをセットした上で、activeをtrueにして移動を開始させています。

const ctrlEl = el.sceneEl.querySelector('[nav-controller]');
ctrlEl.setAttribute('nav-controller', {
  active: true,
  destination: e.detail.intersection.point
 });

動作確認

実装例ではgdgde妖精sMMDを利用しており、商用利用・再配布がNGです。

権利的にパブリックなサーバにはアップできないため、apachectlでMac上にWebサーバを起動し、ifconfigコマンドでIPアドレス(192.168.0.XXX)を調べてローカル接続しています。

マテリアルをセットしていないのでメッシュが黒いのは気にしないでください。

カメラを向けた方向でカーソルを固定すると、視線によるclickイベントが発生し、その地点にキャラクターが移動します。 Physicsは利用していないためコンテンツのロード時はキャラクターが宙に浮いた状態で、最初の移動でnav meshとの接地面まで降りてきてくれました。

まとめ

Blenderでnav meshを作成、glTFでエクスポートすることで、A- Frameでnav meshに基づいた移動が実現できました。 nav meshに関しては、glTF以外での動作は確認できておらず、単純にplaneを配置して"nav-mesh"属性を付与しただけでは、移動のパスが見つけられませんでした。 移動させるエージェント(キャラクター)や表示用のシーンオブジェクトは、他の3Dフォーマットも利用できそうです。

事前にBlenderで作成する手間はあるものの、やり方を覚えてしまえば簡単に作れます。 シーンを構成するデータと合わせて配信して、シーン内の移動を制御する用途なら十分使えそうです。