APLでのあれこれのメモ
1.はじめに
先日、ねこねこゲーム という画面対応のAlexaスキルを公開しました。APL対応スキルとして私がつくった3番目のものですが、いくつか試行錯誤した部分を自分のメモも兼ねて残します。
1.1. 前提知識
このドキュメントは以下の知識を前提としています。
- Alexaスキル開発の一般的手順・経験
- APL(Alexa Presentation Language)のある程度基礎的な知識
1.2. 「ねこねこゲーム」概要
ひと言で表せば、音とイラストの神経衰弱ゲームです。
Alexa Developer スキルアワード2019 に応募した際の動画=> https://youtu.be/n2K_YbenX70
ゲームの処理としてはこんな感じです。
- 数字で1から8まで書かれたカードを表示し、ユーザーの選択を求める。
- ユーザーが1から8までのカードのうち2つを選択する
- 選択したカード2枚を表示し、同じカードかどうかを示す
- 選択した2枚のカード番号を表示する
- 1つめのカードをめくり、カードに紐付けられた中身(音と画像)を提示する
- 2つめのカードをめくり、カードに紐付けられた中身(音と画像)を提示する
- カード2枚が一致したかどうか、判定結果を(音と画像で)提示する
- カード選択画面に戻る。このとき、一致したカードについては数字では無く中身画像を提示する。
このゲームを作るときに試行錯誤したあれこれのポイントを以下にまとめました。
2.あれこれ本編
2.1. 画面操作と再生タイミング(speak(speechOutput),reprompt(repromptText),addDirective( directive )の順番)
今回一番時間がかかったのが、ゲーム概要の「選択したカード2枚を表示し、同じカードかどうかを示す」部分です。
カードをめくると同時に音を再生したいのですが、単純に speak(speechOutput) に全部を載せてもタイミングは合いません。
たとえば、こんな実装の場合を考えましょう。
handlerInput.responseBuilder
.addDirective( directive_RenderDocument )
.addDirective( directive_ExecuteCommands )
handlerInput.responseBuilder
.speak(speechOutput)
.reprompt(repromptText)
EchoShow端末で提示される順番はこのようになります。
- directive_RenderDocument (APL画面)
- speechOutput
- directive_ExecuteCommands (APL画面への操作)
- repromptText
speechOutputで判定結果を喋らせると、そのあとにdirective_ExecuteCommands で画面が動き、最後にrepromptTextが来ます。あとから動く画面、全然ドキドキしません。
ということで、「カードをめくると同時に音を再生」を実現するために ExecuteCommands を使いました。
2.2. カードをめくるような表示(SequenceにScrollToIndex)と画面切り替え(PagerにSetPage)
カードをめくる動作として、次の2つを考えました
- Pager に対して SetPage する
- Sequence に対して ScrollToIndex する
試した結果、次のように使い分けました
- カードをめくる:PagerにSetPageを使う。画像がスルスルと動く感じが出る。
- 画面を切り替える:Sequence にScrollToIndexを使う。画面がスパッと切り替わる。
2.3. カードをめくったあとにテキストを読み上げる(SpeakItemとtransformers)
カードをめくった後にテキストを読み上げ、さらに別のカードをめくる・・ということで、次のように実現しました。
- Textコンポーネントを用意する
- 高さ・幅ともに 0 とする
- 読み上げるテキストと、transformersの宣言を行う
- inputPathに読み上げさせたいテキストを指定
- transformer属性はssmlToSpeechを指定
- outputNameで出力名を定義
- その出力名をTextコンポーネントのspeechプロパティに設定
- ExecuteCommandsで、TextコンポーネントをSpeakItemさせる
実際のデータやコードはこんな感じになります。あちこち省略していますので、そのままコピペしても動かないと思います。
{
"layouts": {
"twoPagersLayout": {
"item": [
{
"item": [
{
"type": "Text",
"id": "talk_left_pre",
"text": "ひとつめ",
"speech": "${payload.bodyTemplate3Data.properties.Speech_0}",
"position": "absolute",
"width": 0,
"height": 0
{
"bodyTemplate3Data": {
"properties": {
"Ssml_0": "<speak>1番の鳴き声</speak>",
},
"transformers": [
{
"inputPath": "Ssml_0",
"outputName": "Speech_0",
"transformer": "ssmlToSpeech"
},
const directive_RenderDocument = {
type : 'Alexa.Presentation.APL.RenderDocument',
version: '1.0',
"token" : "token",
document: require('./homepage.json'),
datasources: require('./data.json')
};
const directive_ExecuteCommands = {
"type" : "Alexa.Presentation.APL.ExecuteCommands",
"token" : "token",
"commands": [
{
"type": "SpeakItem",
"componentId": "talk_left_pre"
}]
};
handlerInput.responseBuilder
.addDirective( directive_RenderDocument )
.addDirective( directive_ExecuteCommands )
2.4. カードをめくったあとにmp3を再生する(mp3にも制約がある)
mp3の再生は、テキストの読み上げとは似て非なる処理となりました。
- Textコンポーネントを用意する
- 高さ・幅ともに 0 とする
- speech属性に、mp3のURLが設定されたdatasoureのパスを指定する
- ExecuteCommandsで、TextコンポーネントをSpeakItemさせる
大筋ではこれで再生できます。ただ、私はここでつまずきました。用意したmp3は、公式資料 に書かれている次の制約に引っかかっていたのです。
- ビットレートは48 kbps
- サンプルレートは22050Hz、24000Hz、16000Hzのいずれか
そこで、次の記事を参考にffmpegでデータを変換し、無事に解決しました。
2.5. 同時に動かす(Parallelコマンド)
カードをめくる(ScrollToIndex)と同時にテキストを読み上げる(SpeakItem)は、Parallelコマンドで実現します。
const directive_ExecuteCommands = {
"type" : "Alexa.Presentation.APL.ExecuteCommands",
"token" : "token",
"commands": [
{
"type": "Parallel",
"commands": [
{
"type": "SpeakItem",
"componentId": "talk_left"
},
{
"type" : "ScrollToIndex",
"componentId": "leftSequence",
"index": 1
},
3. 参考にした資料
4.おわりに
少しでもどなたかの参考になれたなら幸いです。