はかせのラボ

私の頭の中を書いていく雑記ブログです

Alexa AlexaとUnityの連携

あいさつ

どうも、はかせです。
今回はAlexaとUnityの連携がようやく形になってきたので
その話です。

どういうものか

まずは動画をご覧ください。


(生声(/-\*) ハジュカチ…)

Alexaでスキルを起動し
「ポーズ」というとUnity側でPauseという文字が出るという
凄く単純な構成です。

実装

実装も極めて単純で
DynamoDBを介してやりとりしています。
f:id:hakase0274:20190826200337p:plain

AlexaでDynamoDBを更新
UnityがDBの更新を確認し内容によって処理を行う感じです。

構成の説明が終わったところで
コードの説明へ行きましょう。

Alexa側のコード

今更ですがAlexa側はAWSLambdaの関数を使っています。
ランタイム設定はPython3.6です。

import boto3
from boto3.dynamodb.conditions import Key, Attr
import json
import os

dynamodb = boto3.resource("dynamodb")
table    = dynamodb.Table('TestTable')

def Update(key,val,text):
    table.update_item(
        Key = {
            "ActionKey":key
        },
        UpdateExpression = "set KeyValue = :a",
        ExpressionAttributeValues = {
            ":a" : val
        },
        ReturnValues = "UPDATED_NEW"
        )
    return OneSpeech(text).build()

    
def Test_handler(event, context):
    """最初に呼び出される関数"""
    # リクエストの種類を取得
    request = event['request']
    request_type = request['type']
    
    # LaunchRequestは、特定のインテントを提供することなく、ユーザーがスキルを呼び出すときに送信される...
    # つまり、「アレクサ、ハローワールドを開いて」のようなメッセージ
    # 「アレクサ、ハローワールドで挨拶しろ」と言うとこれはインテントを含むので、IntentRequestになる
    if request_type == 'LaunchRequest':
        return welcome()

    # 何らかのインテントだった場合
    elif request_type == 'IntentRequest':
        intent_name = request['intent']['name']

        # 「ヘルプ」「どうすればいいの」「使い方を教えて」で呼ばれる、組み込みインテント
        if intent_name == 'AMAZON.HelpIntent':
            return welcome()

        # 「キャンセル」「取り消し」「やっぱりやめる」等で呼び出される。組み込みのインテント
        elif intent_name == 'AMAZON.CancelIntent' or intent_name == 'AMAZON.StopIntent':
            return bye()
            
        elif intent_name == 'AMAZON.PauseIntent':
            return Update("isPause",1,"ポーズ")
        
        elif intent_name == 'AMAZON.ResumeIntent':
            return Update("isPause",0,"ポーズ解除")

基本的に受け取ったインテントに応じた値を渡し
DynamoDBのupdate_itemを呼び出しているだけです。

ちなみに少しハマった点がありまして、
実はDynamoDBにも予約語があったようです。
予約語はDynamoDBの式に組み込んではいけないそうで
最初思いっきり「value」とか使ってて弾かれましたw

予約語一覧はこちらからどうぞ
docs.aws.amazon.com

Unity側のコード

こちらはUnity2019.1.1f1を使っています。

using System.Collections;
using UnityEngine;
using Amazon.DynamoDBv2.DataModel;
using Amazon.DynamoDBv2;
using System;
using Amazon;
using Amazon.CognitoIdentity;
using Amazon.Runtime;

[DynamoDBTable("TestTable"), Serializable]
public class BatchTest
{
    [DynamoDBHashKey]
    public string ActionKey;
    [DynamoDBProperty("KeyValue")]
    public bool KeyValue;
}

public class DynamoDBMonitor : MonoBehaviour
{
    //IDプールのID
    public string IdentityPoolId = "";
    //各種リージョンを入れる
    public string CognitoPoolRegion = RegionEndpoint.APNortheast1.SystemName;
    public string DynamoRegion = RegionEndpoint.APNortheast1.SystemName;
    //DynamoDB使うのに必要なものたち
    private IAmazonDynamoDB mDbclient;
    private DynamoDBContext mDbcontext;
    private AWSCredentials mCredentials;
    //バッチ処理を行ってくれるクラス Update以外はバッチング可能らしいので積極的に使うべし
    private BatchGet<BatchTest> mBatchTest;
    //何フレームごとに確認するか 
    //とりあえず毎秒だったらリクエスト数的にもラグ的にも許容かな・・・?
    public int WaitFrame = 60;
    public GameObject Panel;
    private bool isMonitoring = true;

    // Start is called before the first frame update
    void Start()
    {
        //AWSSDKを使う際のおまじない
        UnityInitializer.AttachToGameObject(this.gameObject);
        AWSConfigs.HttpClient = AWSConfigs.HttpClientOption.UnityWebRequest;
        mCredentials = new CognitoAWSCredentials(IdentityPoolId, RegionEndpoint.GetBySystemName(CognitoPoolRegion));
        mDbclient = new AmazonDynamoDBClient(mCredentials, RegionEndpoint.GetBySystemName(DynamoRegion));
        mDbcontext = new DynamoDBContext(mDbclient);
        mBatchTest = mDbcontext.CreateBatchGet<BatchTest>();
        mBatchTest.AddKey("isPause");
        StartCoroutine(MonitorCoroutine());
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space)) isMonitoring = false;
    }

    /// <summary>
    /// DBを確認(監視)するコルーチン
    /// </summary>
    /// <returns></returns>
    private IEnumerator MonitorCoroutine()
    {
        while (isMonitoring)
        {
            //指定フレーム待つ
            for (var i = 0; i < WaitFrame; i++) yield return null;
            //バッチ処理を実行
            mBatchTest.ExecuteAsync((res) =>
            {
                //エラー起こってたら内容表示
                if (res.Exception != null) print(res.Exception);
                //今回は一つしか値取ってこないからそれをそのまま使用
                Panel.SetActive(mBatchTest.Results[0].KeyValue);
            });
        }
        yield break;      
    }
}

Unity側では一定間隔ごとにDBのデータを確認し
その内容をゲーム内に反映させています。

一定間隔にしているのは
毎フレームDB読み出しを行うと
あっという間にリクエスト数が巨大になって
お金が消し飛ぶからですw
(あと純粋に通信走るから多少なりとも重い)

一定間隔の処理はコルーチンを採用しました。
理由は変にアセットを増やしたくなかったのと、
DB通信から反映までの処理を一か所に押し込んでしまいたかったからです。

そして前回までは読み出しにはLoadAsyncを使用していましたが、
今回はBatchGetという新しいものを使いました。

このBatchGetはその名の通りバッチ処理を行ってくれるクラスです。
DynamoDBはものすごくざっくり言ってしまえば
読み出し/書き込みのリクエスト数による従量課金なので
それをまとめて一回でやってくれるこいつは
使い勝手もよくお財布にもやさしいです。
(ただしUpdate処理はバッチ処理してくれない模様)

あとがき

今回はAlexaとUnityの連携でした。
私のタスクの大きなところの一つがようやく終わりが見えてきて
ほっと一息って感じです。

実際のところあまりAlexaとUnityの連携系の記事とかなくて
かなり色々手探りでやってきました。
(有料アセットなら一個あったんだけどね)

なのでなんとか形になりそうで
よかったぁっていう気持ちももちろんですが、
AlexaとUnityでなんかやりたいって方の
一種の参考になれたならうれしいなーと思います。
(そんな人そうそういないかもですが)

それでは今回はこの辺でノシ