ウェブアプリは、Interactive Canvas を使用するアクションの UI です。ウェブアプリの設計と開発には、既存のウェブ技術(HTML、CSS、JavaScript)を使用できます。Interactive Canvas は、ほとんどの場合、ブラウザなどのウェブ コンテンツをレンダリングできますが、ユーザーのプライバシーとセキュリティのためにいくつかの制限があります。UI の設計を開始する前に、Design guidelines
セクションで説明している設計原則を考慮してください。
ウェブアプリの HTML と JavaScript では、次のことを行います。
- Interactive Canvas イベントのcallbacksを宣言する。
- Interactive Canvas の JavaScript ライブラリを初期化する。
- 状態に基づいてウェブアプリを更新するカスタム ロジックを提供する。
このページでは、ウェブアプリを作成するためのおすすめの方法、ウェブアプリとフルフィルメント間の通信を有効にする方法、一般的なガイドラインと制限事項について説明します。
推奨ライブラリ
UI は任意の方法で作成できますが、次のライブラリを使用することをおすすめします。
- Greensock: 複雑なアニメーションを作成する場合。
- Pixi.js: WebGL で 2D グラフィックを描画する場合。
- Three.js: WebGL で 3D グラフィックを描画する場合。
- HTML5 Canvas の描画: シンプルな描画の場合。
- DOM 要素: 静的コンテンツの場合。
アーキテクチャ
シングルページ アプリケーション アーキテクチャを使用することを強くおすすめします。このアプローチにより、最適なパフォーマンスが得られ、継続的な会話のユーザー エクスペリエンスがサポートされます。Interactive Canvas は、状態管理に役立つ Vue、Angular、React などのフロントエンド フレームワークと組み合わせて使用できます。
HTML ファイル
HTML ファイルで UI の外観を定義します。このファイルは、ウェブアプリと会話型アクション間の通信を可能にする Interactive Canvas JavaScript ライブラリも読み込みます。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Immersive Canvas Sample</title>
<!-- Disable favicon requests -->
<link rel="shortcut icon" type="image/x-icon" href="data:image/x-icon;,">
<!-- Load Interactive Canvas JavaScript -->
<script src="https://www.gstatic.com/assistant/df-asdk/interactivecanvas/api/interactive_canvas.min.js"></script>
<!-- Load PixiJS for graphics rendering -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.8.7/pixi.min.js"></script>
<!-- Load Stats.js for fps monitoring -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/r16/Stats.min.js"></script>
<!-- Load custom CSS -->
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div id="view" class="view">
<div class="debug">
<div class="stats"></div>
<div class="logs"></div>
</div>
</div>
<!-- Load custom JavaScript after elements are on page -->
<script src="js/main.js"></script>
<script src="js/log.js"></script>
</body>
</html>
フルフィルメントとウェブアプリ間のやり取り
ウェブアプリとフルフィルメントを作成し、ウェブアプリ ファイルで Interactive Canvas ライブラリに読み込みました。次に、ウェブアプリとフルフィルメントの相互作用を定義する必要があります。そのためには、ウェブアプリのロジックを含むファイルを変更します。
action.js
このファイルには、callbacksを定義し、interactiveCanvas
を通じてメソッドを呼び出すためのコードが含まれています。ウェブアプリはコールバックにより会話型アクションからの情報やリクエストに応答し、メソッドは会話型アクションに情報やリクエストを送信する手段を提供します。
HTML ファイルに interactiveCanvas.ready(callbacks);
を追加し、callbacksを初期化して登録します。
//action.js
class Action {
constructor(scene) {
this.canvas = window.interactiveCanvas;
this.scene = scene;
const that = this;
this.commands = {
TINT: function(data) {
that.scene.sprite.tint = data.tint;
},
SPIN: function(data) {
that.scene.sprite.spin = data.spin;
},
RESTART_GAME: function(data) {
that.scene.button.texture = that.scene.button.textureButton;
that.scene.sprite.spin = true;
that.scene.sprite.tint = 0x0000FF; // blue
that.scene.sprite.rotation = 0;
},
};
}
/**
* Register all callbacks used by Interactive Canvas
* executed during scene creation time.
*
*/
setCallbacks() {
const that = this;
// declare interactive canvas callbacks
const callbacks = {
onUpdate(data) {
try {
that.commands[data.command.toUpperCase()](data);
} catch (e) {
// do nothing, when no command is sent or found
}
},
};
// called by the Interactive Canvas web app once web app has loaded to
// register callbacks
this.canvas.ready(callbacks);
}
}
main.js
このファイルは、ウェブアプリのシーンを作成します。この例では、sendTextQuery()
で返された Promise の成功と失敗のケースも処理しています。main.js
からの抜粋を次に示します。
// main.js
const view = document.getElementById('view');
// initialize rendering and set correct sizing
this.renderer = PIXI.autoDetectRenderer({
transparent: true,
antialias: true,
resolution: this.radio,
width: view.clientWidth,
height: view.clientHeight,
});
view.appendChild(this.element);
// center stage and normalize scaling for all resolutions
this.stage = new PIXI.Container();
this.stage.position.set(view.clientWidth / 2, view.clientHeight / 2);
this.stage.scale.set(Math.max(this.renderer.width,
this.renderer.height) / 1024);
// load a sprite from a svg file
this.sprite = PIXI.Sprite.from('triangle.svg');
this.sprite.anchor.set(0.5);
this.sprite.tint = 0x00FF00; // green
this.sprite.spin = true;
this.stage.addChild(this.sprite);
// toggle spin on touch events of the triangle
this.sprite.interactive = true;
this.sprite.buttonMode = true;
this.sprite.on('pointerdown', () => {
this.sprite.spin = !this.sprite.spin;
});
タッチ操作のサポート
Interactive Canvas アクションは、ユーザーのタップにも声入力にも反応できます。Interactive Canvas の設計ガイドラインに沿って、アクションを「音声ファースト」に開発する必要があります。ただし、一部のスマートディスプレイはタップ操作をサポートしています。
タップのサポートは、会話によるレスポンスのサポートと似ています。ただし、クライアントサイドの JavaScript は、ユーザーからの音声による応答ではなく、タップ操作を検索し、それを使用してウェブアプリの要素を変更します。
この例は、Pixi.js ライブラリを使用するサンプルで確認できます。
...
this.sprite = PIXI.Sprite.from('triangle.svg');
...
this.sprite.interactive = true; // Enables interaction events
this.sprite.buttonMode = true; // Changes `cursor` property to `pointer` for PointerEvent
this.sprite.on('pointerdown', () => {
this.sprite.spin = !this.sprite.spin;
});
...
この場合、spin
変数の値は interactiveCanvas
API を介して update
コールバックとして送信されます。フルフィルメントには、spin
の値に基づいてインテントをトリガーするロジックがあります。
...
app.intent('pause', (conv) => {
conv.ask(`Ok, I paused spinning. What else?`);
conv.ask(new HtmlResponse({
data: {
spin: false,
},
}));
});
...
その他の機能の追加
基本を学んだので、Canvas 固有の API を使用してアクションを拡張し、カスタマイズすることができます。このセクションでは、これらの API を Interactive Canvas アクションに実装する方法について説明します。
sendTextQuery()
sendTextQuery()
メソッドは、会話アクションにテキストクエリを送信して、インテントをプログラムで呼び出します。このサンプルでは、sendTextQuery()
を使用して、ユーザーがボタンをクリックしたときに三角形が回転するゲームを再起動します。ユーザーが「ゲームを再開」ボタンをクリックすると、sendTextQuery()
は Restart game
インテントを呼び出し、Promise を返します。この Promise により、インテントがトリガーされた場合は SUCCESS
、トリガーされていない場合は BLOCKED
が返されます。次のスニペットはインテントをトリガーし、Promise の成功と失敗のケースを処理します。
//main.js
...
that.action.canvas.sendTextQuery('Restart game')
.then((res) => {
if (res.toUpperCase() === 'SUCCESS') {
console.log(`Request in flight: ${res}`);
that.button.texture = that.button.textureButtonDisabled;
that.sprite.spin = false;
} else {
console.log(`Request in flight: ${res}`);
}
});
...
Promise の結果が SUCCESS
になった場合、Restart game
インテントはウェブアプリに HtmlResponse
を送信します。
//index.js
...
app.intent('restart game', (conv) => {
conv.ask(new HtmlResponse({
data: {
command: 'RESTART_GAME',
},
...
この HtmlResponse
が onUpdate()
コールバックをトリガーし、以下の RESTART_GAME
コード スニペットのコードを実行します。
//action.js
...
RESTART_GAME: function(data) {
that.scene.button.texture = that.scene.button.textureButton;
that.scene.sprite.spin = true;
that.scene.sprite.tint = 0x0000FF; // blue
that.scene.sprite.rotation = 0;
},
...
OnTtsMark()
OnTtsMark()
コールバックは、ユーザーへの SSML レスポンスに一意の名前の <mark>
タグを含めると呼び出されます。以下は、Snowman サンプルからの抜粋で、OnTtsMark()
はウェブアプリのアニメーションを対応する TTS 出力と同期します。アクションがユーザーに対し「same, you missing」と指定すると、ウェブアプリは正しい単語をスペルアウトし、その文字をユーザーに表示します。
インテント Game Over Reveal Word
には、ゲームに負けた際のユーザーに対するレスポンスにカスタムマークが含まれています。
//index.js
...
app.intent('Game Over Reveal Word', (conv, {word}) => {
conv.ask(`<speak>Sorry, you lost.<mark name="REVEAL_WORD"/> The word is ${word}.` +
`${PLAY_AGAIN_INSTRUCTIONS}</speak>`);
conv.ask(new HtmlResponse());
});
...
次のコード スニペットは、OnTtsMark()
コールバックを登録し、マークの名前を確認して、ウェブアプリを更新する revealCorrectWord()
関数を実行します。
//action.js
...
setCallbacks() {
const that = this;
// declare assistant canvas action callbacks
const callbacks = {
onTtsMark(markName) {
if (markName === 'REVEAL_WORD') {
// display the correct word to the user
that.revealCorrectWord();
}
},
...
制限
ウェブアプリを開発するときは、以下の制限事項を考慮してください。
- Cookie は使用不可
- ローカル ストレージは使用不可
- 位置情報は使用不可
- カメラは使用不可
- ポップアップは使用不可
- メモリの上限は 200 MB
- 3P ヘッダーが画面上部を占める
- 動画へのスタイルの適用は不可
- 一度に使用できるメディア要素は 1 つのみ
- HLS 動画は使用不可
- ウェブ SQL データベースは使用不可
- Web Speech API の
SpeechRecognition
インターフェースはサポートされていません。 - 録音または録画は不可
- ダークモード設定は適用されません
クロスオリジン リソース シェアリング
Interactive Canvas ウェブアプリは iframe でホストされ、オリジンが null に設定されているため、ウェブサーバーとストレージ リソースに対してクロスオリジン リソース シェアリング(CORS)を有効にする必要があります。これにより、アセットは null の生成元からのリクエストを受け入れることができます。
- メディアと画像が Firebase でホストされている場合は、カスタム ドメインのダイナミック リンクを作成するを参照して CORS を構成します。
- メディアと画像が Cloud Storage にある場合、クロスオリジン リソース シェアリング(CORS)の構成を参照して CORS を構成します。