初音ミクが、残高を教えてくれたり振込の手続きをしてくれる銀行アプリのプロトタイプを作ってみた


<br>

GMOあおぞらネット銀行のsunabarというサンドボックス環境を使って、初音ミクの銀行アプリのプロトタイプを作ってみました。
なお、sunabarを利用するためにはGMOあおぞらネット銀行の口座が必要です。（口座開設は面倒な書類のやりとりは無く、ネットだけで完結します。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/GMO%E3%81%82%E3%81%8A%E3%81%9E%E3%82%89%E3%83%8D%E3%83%83%E3%83%88%E9%8A%80%E8%A1%8C?src=hash&amp;ref_src=twsrc%5Etfw">#GMOあおぞらネット銀行</a> さんには <a href="https://twitter.com/hashtag/sunabar?src=hash&amp;ref_src=twsrc%5Etfw">#sunabar</a> というAPIサンドボックス環境があるので、試しに初音ミクの銀行アプリのプロトタイプを作ってみた。<br>こんな銀行アプリあったら売れるかしら？ <a href="https://t.co/JIl9tGDx4d">pic.twitter.com/JIl9tGDx4d</a></p>&mdash; たつや (@tatsuya1970) <a href="https://twitter.com/tatsuya1970/status/1444633314929774597?ref_src=twsrc%5Etfw">October 3, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>



##動作環境
OS: Windows10

使用ソフト：Unity
　・Text Mesh Pro をインストール
　　※Window > Package Manager
　・JSON Object アセットをインストール
　　https://assetstore.unity.com/packages/tools/input-management/json-object-710?locale=ja-JP

必要アカウント
・sunabar（あおぞら銀行のAPI環境）


##sunabar について

API開発者ポータルでアカウント作成
https://api.gmo-aozora.com/ganb/developer/api-docs/#/STOP0101

GMOあおぞら銀行の口座開設
https://gmo-aozora.com/

銀行口座を作成すると、
サンドボックス環境であるsunabarポータルへのログインが可能になります。

ログインID,パスワードは、リアル口座画面から確認できます。
「お客さま情報（申込・設定）」タブ 　＞ 　「開発者向け」
※パスワードは定期的に変更されています。

sunabarポータルへのログイン
https://portal.sunabar.gmo-aozora.com/

こんな画面です。
[![Image from Gyazo](https://i.gyazo.com/1b6ca1a4635c9ae7d6f75252c0b3b750.png)](https://gyazo.com/1b6ca1a4635c9ae7d6f75252c0b3b750)

自分のサンドボックス口座は、
左側をクリック

![スクリーンショット 2021-09-08 18.50.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291592/03d37ce0-83de-f4fb-f76d-ffe2fee4bdf7.png)

すでに個人・法人の２つの口座ができてます。
[![Image from Gyazo](https://i.gyazo.com/18f69f887121f72da488b98e082c4c66.png)](https://gyazo.com/18f69f887121f72da488b98e082c4c66)

ログインすると
本物のGMOあおぞら銀行のインターネットバンキングと同じUIです。
開発中に本物かsunabarか混乱したことあるほど。
なお、この画像では残高1万円ですが、デフォルトでは残高はゼロです。
[![Image from Gyazo](https://i.gyazo.com/14c7425f63eb1852b68c14b87ecdbdbe.png)](https://gyazo.com/14c7425f63eb1852b68c14b87ecdbdbe)

では、どうやって入金するかというと、先ほどの画面の右側から入金できます。
仮想的なセブン銀行のATMという設定です。
[![Image from Gyazo](https://i.gyazo.com/ad2f03be904a69acb9d37c2f2235a8f6.png)](https://gyazo.com/ad2f03be904a69acb9d37c2f2235a8f6)




## Unity
###Live2DのキャラをUnity へ

まずは、Unityで "2D" のプロジェクトを新規作成し、
2次元のイラストを自由に動かすことができるLive2DのSDKである「Cubism SDK for Unity」をプロジェクトにインポートします。
https://www.live2d.com/download/cubism-sdk/download-unity/


こんなエラーでたけど

```
ArgumentNullException: Value cannot be null.
Parameter name: shader
```

ほっといても、よさげ

<br>
Live2Dのウェブサイトのサンプルデータを見に行きます。
https://www.live2d.com/download/sample-data/

こんなに種類が豊富です。
[![Image from Gyazo](https://i.gyazo.com/b602b2b14a024bf7c652e32dc26c200e.png)](https://gyazo.com/b602b2b14a024bf7c652e32dc26c200e)

<br>
今回は初音ミクを選択

ダウンロードした初音ミクを
Unity　の　Project のどっかにフォルダをそのままドラッグします。

Prefab　が生成されるので、Sceneに適当においてみます。
[![Image from Gyazo](https://i.gyazo.com/0d6e3522f2c281330b3fd14023b520c2.png)](https://gyazo.com/0d6e3522f2c281330b3fd14023b520c2)


###アニメーション設定

笑顔、怒り、悲しみという３感情を設定。
通常の返答は「笑顔」、残高少ないのに出金しようとしたら「怒り」、間違った操作をしたら「悲しみ」というふうに設定。

左側のParameters も忘れずに
[![Image from Gyazo](https://i.gyazo.com/b0f47f252629fbf766382fe6ec402d27.png)](https://gyazo.com/b0f47f252629fbf766382fe6ec402d27)

Idleの設定
[![Image from Gyazo](https://i.gyazo.com/634d23b6c3ef1bc1f6ec83ab6627d585.png)](https://gyazo.com/634d23b6c3ef1bc1f6ec83ab6627d585)

表情の設定
ｓｍｉｌｅを例にする
[![Image from Gyazo](https://i.gyazo.com/97859e69f82a02d9fc1b34afe91635e0.png)](https://gyazo.com/97859e69f82a02d9fc1b34afe91635e0)

Idle -> Smile
Has Exit Time のチェックをはずす
Conditions
[![Image from Gyazo](https://i.gyazo.com/50be35e8b64333a92010ea074f8ede78.png)](https://gyazo.com/50be35e8b64333a92010ea074f8ede78)

Smile -> Idle
Has Exit Time のチェックをする。
Conditions は何も入れない
[![Image from Gyazo](https://i.gyazo.com/7e40478b05c646d82d0e14fbf46d15a8.png)](https://gyazo.com/7e40478b05c646d82d0e14fbf46d15a8)


###UI設定

この画像のように残高照会ボタン、振込ボタン、メッセージ欄をつくる
[![Image from Gyazo](https://i.gyazo.com/c3bf82c0a07ba13d1ae455c32c6ac65f.png)](https://gyazo.com/c3bf82c0a07ba13d1ae455c32c6ac65f)


#### 残高照会ボタン、振込ボタン
クリックしたらスクリプトanswer.cs を呼び出すために
onClick の設定とスクリプトanswer.csをアタッチする
[![Image from Gyazo](https://i.gyazo.com/95b716cfd8e02933fbc41a782f6a800b.png)](https://gyazo.com/95b716cfd8e02933fbc41a782f6a800b)

スクリプトanswer.csはこちら

```answer.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using System;
using TMPro;　//UIテキストにハイパーリンク表示

public class answer : MonoBehaviour
{
    private Animator animator;
    public GameObject TransferInputObject;
    public Button button;

    public GameObject BankCodeInputField;
    public GameObject BranchCodeInputField;
    public GameObject AccountNumberInputField;
    public GameObject NameInputField;
    public GameObject TransferAmountInputField;

    [SerializeField]
    private TextMeshProUGUI LinkText;

    [Serializable]
    public class body
    {
        public string accountId;
        public string transferDesignatedDate;
        public Transfers[] transfers;    
    }

    [Serializable]
    public class Transfers
    {
        public string transferAmount;
        public string beneficiaryBankCode;
        public string beneficiaryBranchCode;
        public string accountTypeCode;
        public string accountNumber;
        public string beneficiaryName;
    }

    public void OnClick()
    {
        animator = GameObject.Find("miku").GetComponent<Animator>();
        string order = this.gameObject.name;

        //残高照会ボタンが押された場合
        if (order == "BalanceButton")
        {         
            string url = "https://api.sunabar.gmo-aozora.com/personal/v1/accounts/balances";

            StartCoroutine(get(url, order));
        }

        //振込ボタンが押された場合
        else if (order == "TransferButton")
        {         
            TransferInputObject.SetActive(true);
        }

        //振込OKボタンが押された場合
        else if (order == "TransferOkButton") {
            string transferAmount = TransferAmountInputField.GetComponent<Text>().text;
            string bankCode = BankCodeInputField.GetComponent<Text>().text;
            string branchCode = BranchCodeInputField.GetComponent<Text>().text;
            string accountNumber = AccountNumberInputField.GetComponent<Text>().text;
            string name = NameInputField.GetComponent<Text>().text;
           
            var url = "https://api.sunabar.gmo-aozora.com/personal/v1/transfer/request";

            string date = DateTime.Now.ToString("yyyy-MM-dd");

            string itemJson = "{ \"accountId\":\"301010003162\", \"transferDesignatedDate\":\"" + date + "\", " +
                "\"transfers\":[{ \"transferAmount\" :\""+ transferAmount + "\", \"beneficiaryBankCode\": \""+ bankCode + "\" , \"beneficiaryBranchCode\": \"" + branchCode + "\", " +
                "\"accountTypeCode\": \"1\", \"accountNumber\": \""+ accountNumber + "\", \"beneficiaryName\": \"" + name + "\" }] }";

            body item = JsonUtility.FromJson<body>(itemJson);
          
            string serialisedItemJson = JsonUtility.ToJson(item);
            Debug.Log("serialisedItemJson " + serialisedItemJson);

            StartCoroutine(post(url, serialisedItemJson));
        }
    }

    private IEnumerator get(string url, string order)
    {
        UnityWebRequest request = UnityWebRequest.Get(url);
        request.SetRequestHeader("x-access-token", "GMOあおぞらネット銀行のトークン");
        yield return request.SendWebRequest();

        // 通信エラーチェック
        if (request.isNetworkError)
        {       
            Debug.Log(request.error);
        }
        else
        {
            Debug.Log(request.responseCode);
            if (request.responseCode == 200)
            {
                // UTF8文字列として取得する
                string text = request.downloadHandler.text;
                Debug.Log(text);
                JSONObject json = new JSONObject(text);

                if (order == "BalanceButton")
                {    
                    JSONObject balances = json.GetField("balances");                    
                    string balance = balances[0].GetField("balance").str;
                    int balanceInt = int.Parse(balance);
                    
                    if (balanceInt < 10000)
                    {
                        animator.SetBool("angry", true);
                        string mes = "今の残高は" + balanceInt.ToString("N0") + "円しかないよー\n無駄使いしたらダメだよ";
                        GameObject MesObj = GameObject.Find("MessageText");
                        MesObj.GetComponent<Text>().text = mes;

                        //アニメーションが終わる時間にもとにもどす
                        yield return new WaitForSeconds(0.8f);
                        animator.SetBool("angry", false);
                    }
                    else
                    {
                        animator.SetBool("smile", true);
                        string mes = "今の残高は" + balanceInt.ToString("N0") + "円だよー";
                        GameObject MesObj = GameObject.Find("MessageText");
                        MesObj.GetComponent<Text>().text = mes;

                        //アニメーションが終わる時間にもとにもどす
                        yield return new WaitForSeconds(0.8f);
                        animator.SetBool("smile", false);
                    }
                }
            }
        }
    }

    private IEnumerator post(string url,string body)
    {
        var request = new UnityWebRequest(url, "POST");
        byte[] postData = System.Text.Encoding.UTF8.GetBytes(body);

        request.uploadHandler = new UploadHandlerRaw(postData);
        request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
        request.SetRequestHeader("Content-Type", "application/json");
        request.SetRequestHeader("x-access-token", "GMOあおぞらネット銀行のトークン");

        yield return request.SendWebRequest();
       
        // 通信エラーチェック
        if (request.isNetworkError)
        {
            Debug.Log(request.error);
            animator.SetBool("sad", true);
            yield return new WaitForSeconds(0.8f);
            animator.SetBool("sad", false);
        }
        else
        {           
            Debug.Log(request.responseCode);
            if (request.responseCode == 200 || request.responseCode == 201)
            ｛               
                TransferInputObject.SetActive(false);
                animator.SetBool("smile", true);
                GameObject MesObj = GameObject.Find("MessageText");
                MesObj.GetComponent<Text>().text = "受け付けました。\nあとはこのリンク先から振込承認してね";
                GameObject LinkObj = GameObject.Find("LinkText_TMP");
                string LinkURL = "https://bank.sunabar.gmo-aozora.com/bank/notices/important";
                string mes = "<link=\"" + LinkURL + "\">" + LinkURL + "</link>";
                LinkText.text = mes;
                yield return new WaitForSeconds(3.0f);
                animator.SetBool("smile", false);
            }
            else
            {
                animator.SetBool("angry", true);
                GameObject MesObj = GameObject.Find("MessageText");
                MesObj.GetComponent<Text>().text = request.responseCode+ "エラーだよ！！\nどこかが間違ってるよ。";

                yield return new WaitForSeconds(0.8f);
                animator.SetBool("angry", false);
                yield return new WaitForSeconds(3.0f);
                MesObj.GetComponent<Text>().text = "";
            }
        }
    }
}



```








これで残高照会ボタンで残高を教えてくれる。
[![Image from Gyazo](https://i.gyazo.com/91341eaf2992093ecc8bc323ac0d1b47.png)](https://gyazo.com/91341eaf2992093ecc8bc323ac0d1b47)


振込ボタンが押されたら表示する振込情報のＵＩを作っておく

[![Image from Gyazo](https://i.gyazo.com/cde0530e58e2ad9423253a4be242c1fe.png)](https://gyazo.com/cde0530e58e2ad9423253a4be242c1fe)

これは通常は非表示にしておく
[![Image from Gyazo](https://i.gyazo.com/2b10471ade9bce3e288451b0f775781a.png)](https://gyazo.com/2b10471ade9bce3e288451b0f775781a)

####Message 欄
[![Image from Gyazo](https://i.gyazo.com/e3d7405649d9bf8344adf9beb19297a8.png)](https://gyazo.com/e3d7405649d9bf8344adf9beb19297a8)

振込を受付後に　振込承認の画面に誘導するため、ハイパーリンクを表示する必要がある。
冒頭の準備でTextMesh Pro　をインストールしたのはこのため
[![Image from Gyazo](https://i.gyazo.com/705e1b63f5cc618a64bed9d99329cf9e.png)](https://gyazo.com/705e1b63f5cc618a64bed9d99329cf9e)

スクリプト HyperlinkUI.cs をアタッチする
[![Image from Gyazo](https://i.gyazo.com/d913da1a2dd32fd39b94c3d7eb7463d9.png)](https://gyazo.com/d913da1a2dd32fd39b94c3d7eb7463d9)


このサイトのスクリプトを拝借した
https://baba-s.hatenablog.com/entry/2019/09/03/075000

```HyperlinkUI.cs
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;

[RequireComponent(typeof(TextMeshProUGUI))]
public class HyperlinkUI : MonoBehaviour, IPointerClickHandler
{
    public void OnPointerClick(PointerEventData e)
    {
        var text = GetComponent<TextMeshProUGUI>();
        var pos = Input.mousePosition;
        var canvas = text.canvas;
        var camera = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera;
        var index = TMP_TextUtilities.FindIntersectingLink(text, pos, camera);

        if (index == -1) return;

        var linkInfo = text.textInfo.linkInfo[index];
        var url = linkInfo.GetLinkID();

        Application.OpenURL(url);
    }
}
```



<br><br>
以上、自分好みの銀行アプリを作ることは技術的には可能です。

<br><br>






##参考サイト
https://note.com/ume_white/n/nd3c4498d7a5e

https://blog.applibot.co.jp/2016/07/20/unity-webrequest/

https://qiita.com/sea_mountain/items/6513b330983ffa003959

https://beyondjapan.com/blog/2020/05/unitywebrequest/

https://baba-s.hatenablog.com/entry/2019/09/03/075000