新規スキルのプロジェクトを作成します。こちらにアクセスしてください。

https://developer.amazon.com/ja/alexa-skills-kit

1-1. スキル開発を始めよう!

[スキル開発を始める]をクリックします。

s100

Amazonのアカウント情報を入力してログインします。

s101

[スキルの作成]ボタンをクリックします。

s102

スキル名を入力して、[カスタム]と[Alexaがホスト]をそれぞれ選択します。

最後に[スキルを作成]ボタンをクリックします。

s103

左側のメニューにある[呼び出し名]をクリックします。スキル名を「ハロー」に変更します。

必ず変更したら必ず[モデルを保存]と[モデルをビルド]をクリックします。

※保存してビルドをしないと反映されないので注意しましょう!

s104

1-2. シミュレーターで動かそう

Alexaは実機が無くても動作を確認することができます。

テストタブをクリックして、プルダウンメニューから[開発中]を選択します。

s110

これでシミュレーターの準備ができました。「ハローをひらいて」と入力して、スキルが動作することを確認します。

今は英語の文章が返ってくれば問題ありません。

s111

いよいよAlexa Presentation Language(以降 APL)に触れていきたいと思います。

APLは2018年10月30日にパブリックベータ版として公開されました。

https://developer.amazon.com/ja/blogs/alexa/post/be1b08ef-fa61-4884-9e30-1d6ff6882dba/jp-alexa-presentation-language-publicbeta

簡単にAPLを解説するとEcho ShowやEcho Spotなどの画面付きスマートスピーカーに対してよりリッチな表現を実現するための新デザイン言語です。

2-1. APLを有効化

APLは初期状態では無効設定になっていますので、必ず使用する際は設定を有効にする必要があります。

ビルドタブを押して元の画面に戻ります。

左側のメニューの[インターフェース]をクリックして、

「Displayインターフェース」と「Alexa Presentation Language」のチェックを有効にします。

必ずこの2つを有効にしてください。最後に[保存]と[ビルド]ボタンをクリックしてください。

s200

2-2. APLに触れる

左側のメニューにある[画面表示]をクリックします。すると別の画面の画面が表示されます。
直接リンクから飛ぶこともできます。

https://developer.amazon.com/alexa/console/ask/displays

s210

画面が表示されたら、[コードをアップロード]をクリックします。

アップロードするコードはこちらからダウンロードしてください。

https://raw.githubusercontent.com/gaomar/apl_handson/master/files/apl_hello.json

s211

アップロードすると編集画面が表示されます。

s212

2-3. 文字を変えてみよう

「ハローAPL」と書かれている文字を違う文字に変えてみましょう。

編集方法は「JSONデータ」タブをクリックします。すると表示されている内容が変わります。「ハローAPL」と書かれている部分を好きな文字に変えてみましょう。するとリアルタイムで文字列が変更されます。

s213

2-4. 背景画像を入れてみよう

背景が黒一色だと寂しいですね。レイアウト画面からImageを追加して、背景を実装してみましょう。

レイアウト選択で一番最初にあるContainerをクリックして、[+]ボタンをクリックします。

s220

ポップアップが表示されるので、プルダウンメニューからImageを選択し、[typeを追加]ボタンをクリックします。

s221

Imageをクリックして、コードを編集します。

s222

プロパティ値は以下の通りです。

source

https://github.com/gaomar/apl_handson/raw/master/files/background.jpg

height

100vh

position

absolute

scale

best-fill

width

100vw

このままだと最初に表示した文字列が背景より後ろになってしまうので、表示順番を入れ替えます。

三部分をドラッグしてImageを上にもっていきます。

s223

これで文字が見えるようになりました。

s224

2-5. 背景色にオーバーレイカラーを載せる

このままだと文字が見えにくいので背景色にオーバーレイカラーを載せて、文字を見やすくしてみましょう。

コードを下記のように修正してみましょう。overlayColorというプロパティを追加します。

overlayColor

rgba(0, 0, 0, 0.6)

これで文字が見やすくなりました。わざわざオーバーレイカラーの画像を作成しなくてもこのプロパティを追記するだけで簡単に作れます。

s225

2-6. デバイスの判定プロファイルを適用する

Echo Spotはこのレイアウト、Echo Showではこのレイアウトという風に、デバイスに応じてレイアウトを変更することがあるかと思います。

デバイスの判定はalexa-viewport-profilesというものをインポートすると判定することができます。

s230

コード

{
    "type": "APL",
    "version": "1.0",
    "theme": "dark",
    "import": [
        {
            "name": "alexa-viewport-profiles",
            "version": "1.0.0"
        }
    ]
}

2-7. 小型デバイスレイアウトを追加する

小型デバイスのEcho Spot用のレイアウトを作ってみましょう。丸い形を活かしたデザインにします。

最終的にこのようなレイアウトになれば完成です。

s253

既にあるImageを削除します。

s240

出てきたポップアップでProceedをクリックします。これでImage要素が削除されます。

s241

次に2つ目のContainerを選択して、ゴミ箱をクリックして削除します。

s242

Containerを選択して[+]ボタンをクリックします。

s242-1

出てきたポップアップでContainerを選択して、[追加]ボタンをクリックします。

s243

もう1つContainerを追加します。

s244

このように2つできればOKです。

s245

2-8. 小型デバイスレイアウトを適用する

追加した1つ目のContainerを選択して、whenプロパティに値を設定します。

when

${@viewportProfile == @hubRoundSmall}

s246

レイアウト判定はwhenプロパティを使います。whenはif文みたいなものです。

判定のプロパティ値は下記の通りです。

hubRoundSmall

小型デバイス(Echo Spot)

hubLandscapeMedium

中型デバイス(Echo Show 第1世代)

hubLandscapeLarge

大型デバイス(Echo Show 第2世代)

tvLandscapeXLarge

超大型TV(Fire TVなど)

2つ目のContainerをクリックして選択します。その状態で、右上の[+]をクリックしてImageを追加します。

s247

Imageを選択して、プロパティ値を入れます

source

https://github.com/gaomar/apl_handson/raw/master/files/ball.png

height

100vh

position

absolute

scale

best-fill

wight

100vw

2つ目のContainerをクリックして更にContainerを追加します。

できたContainerを選択してプロパティ値を設定します。

s248

height

100vh

justifyContent

center

追加したContainerを選択して、更にTextを追加します。

s249

text

fontSize

20vh

textAlign

center

2-9. それ以外のレイアウトを適用する

追加した一番下にあるContainerをクリックして、Imageを追加します。

Imageを選択して、プロパティ値を設定します。

s250

source

https://github.com/gaomar/apl_handson/raw/master/files/background.jpg

height

100vh

overlayColor

rgba(0, 0, 0, 0.6)

position

absolute

scale

best-fill

width

100vw

一番下にあるContainerを選択して、更にContainerを追加します。

追加したContainerを選択して、プロパティ値を設定します。

s251

height

100vh

justifyContent

center

一番下にあるContainerを選択してTextを追加します。

追加したTextを選択してプロパティ値を設定します。

s252

text

${payload.hello.text}

fontSize

5vh

textAlign

center

最終的にこのようなレイアウトになれば完成です。

s253

うまくいかない方はこちらからダウンロードしてファイルをインポートしてください。

https://raw.githubusercontent.com/gaomar/apl_handson/master/files/apl_layout.json

2-10. レイアウトを確認する

小型デバイスや中型デバイスに切り替えると適用したレイアウトに切り替わると思います。

s260

これまで作ってきたAPLをスキルから呼び出してみましょう。

まずはコードを一旦書き出してみます。右上にある[コード書き出し]ボタンをクリックしてダウンロードできます。

s300

apl_template_export.jsonファイルがダウンロードされますので開いてください。

document部分のみを選択してください。

s300-1

3-1. documentを新規作成する

document部分のみをコピーしてデベロッパーコンソールの[コードエディタ]タブをクリックしてから左上の新規ドキュメントアイコンをクリックして作成します。

s301

ファイル名をapl_document.jsonとして[Create File]ボタンをクリックします。

s302

先程コピーしたコードを貼り付けます。貼り付けたら[Save]ボタン押します。

s303

コードはこちら。
https://raw.githubusercontent.com/gaomar/apl_handson/master/files/apl_document.json

{
    "type": "APL",
    "version": "1.0",
    "theme": "dark",
    "import": [
        {
            "name": "alexa-viewport-profiles",
            "version": "1.0.0"
        }
    ],
    "resources": [
        {
            "strings": {
                "skillName": "HelloAPL"
            }
        }
    ],
    "styles": {},
    "layouts": {},
    "mainTemplate": {
        "parameters": [
            "payload"
        ],
        "items": [
            {
                "type": "Container",
                "items": [
                    {
                        "when": "${@viewportProfile == @hubRoundSmall}",
                        "type": "Container",
                        "items": [
                            {
                                "type": "Image",
                                "source": "https://github.com/gaomar/apl_handson/raw/master/files/ball.png",
                                "scale": "best-fill",
                                "width": "100vw",
                                "height": "100vh",
                                "position": "absolute"
                            },
                            {
                                "type": "Container",
                                "justifyContent": "center",
                                "height": "100vh",
                                "item": [
                                    {
                                        "type": "Text",
                                        "text": "★",
                                        "textAlign": "center",
                                        "fontSize": "20vh"
                                    }
                                ]
                            }
                        ]
                    },
                    {
                        "type": "Container",
                        "items": [
                            {
                                "type": "Image",
                                "source": "https://github.com/gaomar/apl_handson/raw/master/files/background.jpg",
                                "scale": "best-fill",
                                "width": "100vw",
                                "height": "100vh",
                                "position": "absolute",
                                "overlayColor": "rgba(0, 0, 0, 0.6)"
                            },
                            {
                                "type": "Container",
                                "justifyContent": "center",
                                "height": "100vh",
                                "item": [
                                    {
                                        "type": "Text",
                                        "text": "${payload.hello.text}",
                                        "textAlign": "center",
                                        "fontSize": "5vh"
                                    }
                                ]
                            }
                        ]
                    }
                ]
            }
        ]
    }
}

3-2. スキルプログラムを編集する

既にあるindex.jsファイルを開きます。6行目にあるLaunchRequestHandlerの関数を編集します。
addDirectiveという部分でAPLのdocumentを指定します。

datasourcesに中型デバイスで表示する文字列を指定します。

コードを編集したら、[Save]と[Deploy]をクリックしてください。

s304

const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
    },
    handle(handlerInput) {
        const speechText = 'ハローAPL';
        // documentとdataをそれぞれ指定する
        return handlerInput.responseBuilder
            .speak(speechText)
            .addDirective({
                type : 'Alexa.Presentation.APL.RenderDocument',
                version: '1.0',
                token: "token",
                document: require('./apl_document.json'),
                datasources: {
                    "hello": {
                        "text": "ハローAPL"
                    }
                }
            })            
            .getResponse();
    }
};

[テスト]タブをクリックしてシミュレーター画面を表示してください。シミュレーターで実行するとAPLが適用されたレイアウトを確認することができます。

s305

実機で確認もしてみてください。
Echo Spotだとますますドラゴ◯ボールっぽく見えますね

s306

3-3. Alexa-hostedのS3から画像を呼ぶ

Alexa-hostedにはS3も使えます。
コードエディタの左下にS3のリンクがあるのでクリックします。

s310

S3のページが表示されたら、画像をアップロードするので[アップロード]ボタンを押します。

s311

お好きな画像でも良いですし、こちらのball.pngファイルでも構いません。
好きなファイルをアップロードしましょう。

s312

apl_document.jsonを開いて、34行目のsourceの値を変更します。
${payload.hello.ballImage}に変えてください。

s313

index.jsも編集します。S3に保存している画像ファイルのURLを取得しましょう。

s314

下記のように編集します。

const Alexa = require('ask-sdk-core');
const Util = require('util.js');

const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
    },
    handle(handlerInput) {
        const speechText = 'ハローAPL';
        const pictureUrl = Util.getS3PreSignedUrl("Media/ball.png");

        // documentとdataをそれぞれ指定する
        return handlerInput.responseBuilder
            .speak(speechText)
            .addDirective({
                type : 'Alexa.Presentation.APL.RenderDocument',
                version: '1.0',
                token: "token",
                document: require('./apl_document.json'),
                datasources: {
                    "hello": {
                        "text": "ハローAPL",
                        "ballImage": pictureUrl
                    }
                }
            })            
            .getResponse();
    }
};

今まではGithub経由で呼び出していた画像が、これでAlexa-hostedのS3側にあるファイルから呼び出すことができました。

実際に動いているかテストしてみましょう。

画面タッチ処理を実装します。タッチしたらイベントを検知して数をカウントする

昔流行った「へぇボタン」を作ります。

s400

4-1. インテントを追加する

へぇボタンの画面を表示させるために、インテントを新規作成します。

[ビルド]タブをクリックして、インテントの[追加]ボタンをクリックして、インテント名をHeeIntentと入力します。[カスタムインテントを作成]ボタンをクリックして作成します。

s401

このインテントが反応するための言葉を登録します。サンプル発話に「へぇボタン」と入力して、Enterキーを押してください。

最後に必ずインテントの[モデルを保存]ボタンと[モデルをビルド]ボタンをそれぞれクリックします。

s402

4-2. APLを実装する

APLのレイアウトを組み込みます。[コードエディタ]タブをクリックしてから、左上の新規ドキュメントアイコンをクリックして作成してください。

ファイル名は「apl_hee.json」としました。

s403

コードは下記の通りです。

{
    "type": "APL",
    "version": "1.0",
    "theme": "dark",
    "import": [],
    "resources": [],
    "styles": {},
    "layouts": {
        "heeLayout": {
            "parameters": [
                {
                    "name": "imageUrl",
                    "type": "string"
                }
            ],
            "items": [
                {
                    "type": "Container",
                    "alignItems": "center",
                    "justifyContent": "center",
                    "height": "100vh",
                    "items": [
                        {
                            "type": "Image",
                            "scale": "best-fit",
                            "height": "50vh",
                            "width": "50vw",
                            "align": "center",
                            "source": "${imageUrl}"
                        }
                    ]
                }
            ]
        }
    },
    "mainTemplate": {
        "parameters": ["payload"],
        "items": [
            {
                "type": "Container",
                "items": [
                    {
                        "type": "Pager",
                        "width": "100vw",
                        "height": "100vh",
                        "id": "myPager",
                        "initialPage": 0,
                        "items": [
                            {
                                "type": "TouchWrapper",
                                "onPress": {
                                    "type": "SendEvent",
                                    "arguments": [
                                        "touch"
                                    ]
                                },
                                "item": {
                                    "type": "heeLayout",
                                    "imageUrl": "${payload.hello.hee_off}"
                                }
                            },
                            {
                                "type": "heeLayout",
                                "imageUrl": "${payload.hello.hee_on}"
                            }
                        ]
                    },
                    {
                        "type": "Container",
                        "height": "100vh",
                        "position":"absolute",
                        "alignItems": "center",
                        "justifyContent": "end",
                        "paddingBottom": "10vh",
                        "items": {
                            "type": "Text",
                            "width": "100vw",
                            "textAlign": "center",
                            "text": "${payload.hello.myCount}"
                        }                        
                    }
                ]
            }
        ]
    }
}

APLオーサリングツールでこのコードを貼り付けると以下のようになります。
heeLayoutとありますが、自分でオリジナルのレイアウトを適用することができます。

※実際にAPLオーサリングツールで試す場合はこちらのjsonファイルで試してください
https://raw.githubusercontent.com/gaomar/apl_handson/master/files/apl_hee.json

s404

typeのPagerで画面をスワイプするとへぇボタンのOFFとON画像が切り替わります。またOFF状態のへぇボタンをタッチできるようにTouchWrapperを使います。

4-3. プログラムを修正する

APLを適用するためにindex.jsファイルを編集します。コードエディタからindex.jsを開いてください。

コード量が多いので抜粋しています。全コードはこちらからダウンロードしてください。

https://raw.githubusercontent.com/gaomar/apl_handson/master/files/index.js

※自分で編集される際はaddRequestHandlersに関数を追加するのを忘れないようにしてください。

const HeeIntentHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'IntentRequest'
            && handlerInput.requestEnvelope.request.intent.name === 'HeeIntent';
    },
    handle(handlerInput) {
        const speechText = 'へぇ〜';
        return handlerInput.responseBuilder
            .speak(speechText)
            .addDirective({
                type : 'Alexa.Presentation.APL.RenderDocument',
                version: '1.0',
                token: "token",
                document: require('./apl_hee.json'),
                datasources: {
                    "hello": {
                        "hee_off": "https://github.com/gaomar/apl_handson/raw/master/files/hee_off.png",
                        "hee_on": "https://github.com/gaomar/apl_handson/raw/master/files/hee_on.png",
                        "myCount": 0
                    }
                }
            })            
            .getResponse();
    }
};
const TouchEventHandler = {
    canHandle(handlerInput) {
    return ((handlerInput.requestEnvelope.request.type === 'Alexa.Presentation.APL.UserEvent' &&
        (handlerInput.requestEnvelope.request.source.handler === 'Press' || 
        handlerInput.requestEnvelope.request.source.handler === 'onPress')));
    },
    handle(handlerInput) {
        // セッション情報の取得
        var myCount = 0;
        const attributes = handlerInput.attributesManager.getSessionAttributes();
        // 初期化
        if(!attributes.counter){ 
            myCount = 1;
        } else {
            myCount = attributes.counter;
            myCount++;
        }
        attributes.counter = myCount;
        handlerInput.attributesManager.setSessionAttributes(attributes);

        const speechText = 'へぇ〜';
        return handlerInput.responseBuilder
            .speak(speechText)
            .addDirective({
                type : 'Alexa.Presentation.APL.ExecuteCommands',
                token: "token",
                commands: [
                    {
                        type: "Sequential",
                        commands: [
                            {
                                "type": "SetPage",
                                "componentId": "myPager",
                                "position": "absolute",
                                "value": 1
                            },
                            {
                                type: "Idle",
                                delay: 500
                            },
                            {
                                "type": "SetPage",
                                "componentId": "myPager",
                                "position": "absolute",
                                "value": 0
                            }
                        ]
                    }
                ]
            }) 
            .addDirective({
                type : 'Alexa.Presentation.APL.RenderDocument',
                version: '1.0',
                token: "token",
                document: require('./apl_hee.json'),
                datasources: {
                    "hello": {
                        "hee_off": "https://github.com/gaomar/apl_handson/raw/master/files/hee_off.png",
                        "hee_on": "https://github.com/gaomar/apl_handson/raw/master/files/hee_on.png",
                        "myCount": myCount
                    }
                }
            })
            .getResponse();            
    }
};

コード解説

画面タッチ処理はAPL側のTouchWrapperでトリガーされます。

タッチされたら、index.js側のAlexa.Presentation.APL.UserEventが飛んでくるので、PressonPressのどちらかで受け取ります。

Alexa.Presentation.APL.ExecuteCommandsでAPLに対してコマンドを送っています。今回送っているコマンドは、シーケンス処理で、順番に処理を行っていくコマンドです。

SetPageのvalueで次のページを自動的に表示させています。Idleで500ミリ秒待ってから再度SetPageで一番最初のページを表示させています。

最後にカウントを加算するためにAPLを再度読み込み直しています。

完成

s405

早く終わった方は20へぇ到達で違う画像に切替えたり、0へぇに戻すというような処理を付け加えてみてはいかがでしょうか?

まとめ

今回のハンズオンではたくさんあるAPL機能の一部です!

他にもたくさんの機能やテクニックがあるので、これを機にチャレンジしてみてください!