Alexa Presentation Languageの限界を超えて、アニメーションや相槌を実現する

Alexa Presentation Language

こんにちは。音声UIの開発をしている武田です。今年もAmazon Alexaのコンテストが開催されます。このコンテストで専用の賞まで用意されている今熱いデザイン言語、Alexa Presentation Languageでできることを紹介します。

はじめに

Amazon Alexaのスキル、「コーデ相談 by WEAR(以下、コーデ相談)」をリリースしました。アイテム名から色々なコーディネートを探せるシンプルなスキルですが、Alexa Presentation Language(以下、APL)でできることを精一杯詰め込んでいます。この記事では、APLを使ってどんなチャレンジをしているか紹介します。ソースコードなどの実装の詳細は個別の記事で解説していく予定です。

APLとは

APLはAlexaの音声でのコミュニケーションを補助するためのUIを記述する仕組みです。用いることで視覚的な表現だけではなく、JavaScriptのようなインタラクティブな表現が可能となります。公式ドキュメントはこちらです。

例えば、写真を中央に表示するレイアウトはこのようになります。

{
  type: 'Container',

  item: {
    type: 'Image',

    id: 'image1',
    source: 'https://image-url',

    height: '100px',
    width: '100px'
  },

  alignItems: 'center',
  justifyContent: 'center',
  height: '100vh',
  width: '100vw'
}

レイアウトは使用するコンポーネントの種類(ContainerImage)を指定することで組んでいきます。コンポーネントに指定できるプロパティ(alignItemsheight)はHTML/CSSに近いです。しかし、alignItemsContainerコンポーネントのみで指定できるといったようにコンポーネントごとの役割がはっきりと決まっています。

APLはレイアウト以外にもJavaScriptのような動的な制御が可能です。コンテンツの制御にはコマンドと呼ばれるものを用いて以下のように指定します。

{
  type: 'Scroll',

  componentId: 'targetComponentId',
  distance: 1
}

コマンドもコンポーネントのように種類が存在しており、上記の例は指定したコンポーネント内をスクロールさせる命令です。コマンドはスキルの応答と同時に実行するか、タッチなどのインタラクションに合わせて実行できます。これから紹介する実装事例では、このコマンドをふんだんに使っていきます。

表示する際のトランジションを追加する

見ていただいた方が早いと思いますので、この処理が走っている検索結果の画面がこちらです。

検索結果画面

普通に画像を表示しているだけに見えますが、以下のような処理を走らせています。

  • 1番から順番に画像を表示
  • 表示する際の透明度が連続的に変わるようにする
  • 画像が表示されてから、それぞれの番号を表示

これらの処理はWebでしたら、CSSアニメーションで容易に実装可能ですが、APLでは連続的に値を書き換える便利な機能は存在しません。SetValueというコマンドでプロパティの値を書き換えることは可能ですが、途中の値を0、0.1、0.2のように補完してくれる訳ではないのでいきなり最後の値に切り替わってしまいます。

そこでコーデ相談では、コンポーネントのスクロールイベントを監視し、スクロール位置をよしなに計算することで透明度の値を書き換えるようにしました。実装はこちらのブログを参考にしています。

 Alexa Skill Teardown: Building the Interaction Model for the Space Explorer Skill

スクロールはコマンドの例で紹介したScrollを使うとして、スクロールイベントの監視と透明度の更新はこのようにしています。

{
  type: 'ScrollView',

  id: 'targetComponentId',
  item: {
    type: 'Container',

    height: '1000vh'
  },
  onScroll: [
    {
      type: 'SetValue',

      componentId: 'image1',
      property: 'opacity',
      value: '${event.source.value * 4}'
    },
  ],

  height: '100vh'
}

onScrollはスクロール位置の更新に合わせて実行されるコマンドを定義できるプロパティです。スクロール位置が格納されているevent.source.valueを計算し値を更新することで、連続的に値が変動するアニメーションを実現できるようになります。 onScrollには複数のコマンドを指定できますし、コマンドにはdelayという発火を遅らせるプロパティも存在しています。これらを組み合わせることで検索時のトランジションを実装しました。

またこのonScrollは画面に表示されていない要素がスクロールした場合でも、コマンドを発火してくれます。つまり透明度を0にしてユーザーに見えない状態でも大丈夫なので、コーデ相談ではトランジション専用のコンポーネントを用意して使っています。

トーストを実装する

コーデ相談はスキルの中に仲良し度という概念が存在しており、仲良し度の上昇を知らせるためにトーストを用いています。

仲良し度の上昇

今回実装したトーストの動きを分解すると以下のような処理になります。

  • 下から移動して出てくる
  • 透明度が連続的に変わる
  • 少し経ったら消える

トランジションで紹介したSetValueコマンドは、2019年5月時点ではopacitytextプロパティのみが更新できます。つまり、コンポーネントの位置を変更することが非常に厳しいと言えます。ただ、トーストのような下から移動する動きはコンポーネント内をスクロールさせることによって、同じような見た目を実現することが可能です。スクロールで動かせばそのままonScrollが使えるので、透明度の更新もトランジションの時と同じ方法で実現できます。

トーストのコンポーネントを簡単に書くとこのようになります。

{
  type: 'ScrollView',

  item: {
    type: 'Container',

    id: 'notifyContainer',
    items: [
      {
        type: 'Text',

        text: 'トーストのメッセージ'
      }
    ],

    paddingTop: '100px' // 最初はメッセージを見せないための空間
  },
  onScroll: [
    {
      type: 'SetValue',

      componentId: 'notifyContainer',
      property: 'opacity',
      value: '${event.source.value}'
    }
  ],

  height: '100px'
}

あとは、このScrollViewを表示のタイミングでスクロールさせてあげるだけです。

スクロールできる領域なのでユーザーが触ると動いてしまいますが、透明なコンポーネントを上に重ねればタッチイベントがスクロール領域に届かなくなるので大丈夫です。 ScrollViewと挙動が似ているPagerコンポーネントは、ユーザーのインタラクションを無視するようにできるプロパティが用意されています。しかし、横方向しか移動できないため今回のトーストでは活用できませんでした。

相槌を打つ

Alexaが反応を返すまでの流れは以下のような手順となります。

Alexaとのやりとりの一般的な流れ

これは一般的な流れではありますが、外部のAPIを叩く場合などではサーバーの処理の時間が長くなり、ユーザーに返答が返るまでの時間が長くなってしまいます。そこでコーデ相談では、相槌のようにとりあえず一度返事をして、その後に重い処理を実行するようにしました。流れにするとこのようになります。

相槌を含む流れ

上記の流れでは空のリクエストを飛ばし直していますが、普通ですとユーザーが何かアクションを起こさなければサーバーにリクエストは飛びません。リクエストを飛ばすにはSendEventコマンドを実行すれば良いのですが、このSendEvent応答の際に実行するコマンドとして直接使用できません。そこでコーデ相談では、SetPageコマンドとonPageChangedプロパティを組み合わせてSendEventコマンドを実行するようにしています。SetPagePager内で表示しているコンポーネントを切り替えるためのコマンドで、以下のようにonPageChangedで指定したコマンドを発火させられます。

{
  type: 'Pager',

  items: [
    {
      type: 'Container'
    },
    {
      type: 'Container'
    }
  ],
  onPageChanged: [
    {
      type: 'SendEvent',

      arguments: []
    }
  ]
}

このPagerに対してページを切り替えるSetPageコマンドを実行することで、サーバーにリクエストが送られるようになります。この流れにすることにより、全体的な処理が完了するまでの時間は伸びています。しかし人間と会話するときのように、まずは何かしらの返答を返すコミュニケーションが取れるようになったと感じています。

さいごに

全体的にだいぶ無理をした実装をしており、公式ブログで紹介されている方法を利用しているとはいえ無理矢理感は否めません。運用やサービスの信頼性を考えると不安がないと言えば嘘になります。例えばonScrollonPageChangedは公式のドキュメントには載っていませんし、いつ仕様が変わるかわからないプロパティです。今回の実装は良くも悪くもAPLの限界を超えていると感じています。

しかし、ユーザーにとって良いであろう体験をプラットフォームが成熟していないからという理由で諦めたくもありませんでした。ユーザーからしたら、「プラットフォームがサポートしていない機能」も「無理をした不安定な実装」も関係ないからです。重要なのは使いにくかったりうまく動かない状況になったら、それを迅速に把握して修正することだと思います。情報のキャッチアップをして今の実装が明らかに適切でないならば直しますし、相槌をうたない方が使いやすいよね、となるなら一般的な流れに戻すつもりです。

ZOZOテクノロジーズのR&D新規開発チームでは今後普及するであろう技術を先行研究し、様々な技術を用いたサービスを開発しています。今回のAlexaの開発の様子はこちらの記事で詳しく話しています。より良いユーザー体験を提供するために、技術を駆使して最高のプロダクトを作りませんか?

www.wantedly.com

カテゴリー