【Live2D + Unity + ChatGPT】CatGPT作ったよ


猫のChatGPT
名付けて、CatGPT　を作りました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ChatGPTのAPIを使ってCatGPT を作ったにゃん <a href="https://t.co/SGVLss7ZZK">pic.twitter.com/SGVLss7ZZK</a></p>&mdash; たつや (@tatsuya1970) <a href="https://twitter.com/tatsuya1970/status/1644629676516864000?ref_src=twsrc%5Etfw">April 8, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## OpenAI

OpenAIのAPIキーを取得する。
こちらを参照
https://qiita.com/tatsuya1970/items/c04cb7b6d07c4cf51007#3-openai

## Live2D

まずは、UnityHubで "2D" のプロジェクトを新規作成します。
[![Image from Gyazo](https://i.gyazo.com/cb45201eca5f8a0ee5a59611f9c51627.png)](https://gyazo.com/cb45201eca5f8a0ee5a59611f9c51627)

Unityが立ち上がったら、
2次元のイラストを自由に動かすことができるLive2DのSDKである「Cubism SDK for Unity」をプロジェクトにインポートします。

ダウンロードはこちらから
https://www.live2d.com/download/cubism-sdk/download-unity/

こんなエラーが出ますが、多分無視して大丈夫と思います。
```
ArgumentNullException: Value cannot be null.
Parameter name: shader
```

Live2Dのウェブサイトからキャラクターをダウンロードします、
https://www.live2d.com/download/sample-data/

初音ミクなどありますが、
今回はCatGPTなので「黒猫のひじき」をダウンロード。

そして、ダウンロードしたキャラクターのフォルダごとUnity　の　Project にそのままドラッグします。

Prefab　が生成されるので、Sceneに適当に置きます。

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


<br>

## Unityの設定

### UI

こんな画面にします。
[![Image from Gyazo](https://i.gyazo.com/4396b877f2286c6b968a466f28ca96e0.png)](https://gyazo.com/4396b877f2286c6b968a466f28ca96e0)

UIを設置します。
[![Image from Gyazo](https://i.gyazo.com/0aae0374369fcf8710497fca0ce83c9a.png)](https://gyazo.com/0aae0374369fcf8710497fca0ce83c9a)

あとで自分に分かるように名前を変えました。
[![Image from Gyazo](https://i.gyazo.com/df706ea8315ebfdd9f3a42992fa5cf57.png)](https://gyazo.com/df706ea8315ebfdd9f3a42992fa5cf57)


### アニメーション

「黒猫のひじき」の8個のモーション全部を　黄色のノード（Idle）　に繋げる。
モーションの名前や左側のParameterの名前は1からの8数字にした。
（今回はランダムでモーションを選ぶためモーションの名前が数字の方がコーディングしやすい）
　
[![Image from Gyazo](https://i.gyazo.com/6a605ceb162686c7bf514e23cbac8725.png)](https://gyazo.com/6a605ceb162686c7bf514e23cbac8725)

Idle と各モーションの Inspector は
Idle -> 各モーション

Condition を　true に設定
[![Image from Gyazo](https://i.gyazo.com/017f91d472a6804526bdb1fbcee46621.png)](https://gyazo.com/017f91d472a6804526bdb1fbcee46621)

モーション　-> Idle はデフォルトのまま



## スクリプト


汚いコードですが晒します。

今どきは、UniTaskやCoroutineを使わずasync/await　とかを使うのでしょうけど・・・

まず、JSON Object アセットをインストールします。
https://assetstore.unity.com/packages/tools/input-management/json-object-710?locale=ja-JP

で、以下のスクリプトを入力


``` answer.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using System;
using Defective.JSON;
using TMPro;

public class answer : MonoBehaviour
{
    private Animator animator;
    public Button button;          // OKボタン
    public GameObject inputField;  //ユーザー入力するフィールド

    [SerializeField] 
    TextMeshProUGUI responseText;

    //OpenAIのAPIにリクエストするとき必要
    [Serializable]
    public class body
    {
        public string model;  
        public Transfers[] messages;
    }

    [Serializable]
    public class Transfers
    {
        public string role;
        public string content;
    }

    public void OnClick()
    {
        animator = GameObject.Find("hijiki").GetComponent<Animator>();

        //ユーザーが投稿した内容を取得
        string mes = inputField.GetComponent<Text>().text;

        var url = "https://api.openai.com/v1/chat/completions";

        // ボットの性格を設定
        string chara = "あなたは猫ですが人間の言葉も理解できます。語尾に「にゃ」をつけるなど猫っぽい話し方をしてください。";

        string itemJson 
            = "{ \"model\": \"gpt-3.5-turbo\"," +
            "\"messages\": [" +
            "{ \"role\": \"system\", \"content\": \"" + chara +"\"}," +
            "{ \"role\": \"user\", \"content\": \"" + mes + "\"}]}";

        body item = JsonUtility.FromJson<body>(itemJson);

        string serialisedItemJson = JsonUtility.ToJson(item);
        // Debug.Log("serialisedItemJson " + serialisedItemJson);

        StartCoroutine(post(url, serialisedItemJson));
    }


    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");
        // OpenAi のAPIキー
        request.SetRequestHeader("Authorization", "Bearer OpenAIのAPIキー");

        yield return request.SendWebRequest();

        // 通信エラーチェック
        if (request.isNetworkError)
        {
            Debug.Log(request.error);
            animator.SetBool("2", true); //機嫌が悪いアニメーション
            yield return new WaitForSeconds(0.8f);
            animator.SetBool("2", false);
        }
        else
        {
            Debug.Log(request.responseCode);
            if (request.responseCode == 200 || request.responseCode == 201)
            {
                string text = request.downloadHandler.text;
                Debug.Log(text);
                JSONObject json = new JSONObject(text);
                JSONObject choices = json.GetField("choices");
                JSONObject message = choices[0].GetField("message");

                string content = message.GetField("content").ToString();
                Debug.Log(content);           
                string motion = UnityEngine.Random.Range(1,9).ToString();  //モーションをランダムで選ぶ
                animator.SetBool(motion, true);
                responseText.text =  content;
                yield return new WaitForSeconds(3.0f);
                animator.SetBool(motion, false);            
            }
            else
            {
                responseText.text = request.responseCode + "エラーだよ！！\nどこかが間違ってるよ。";
                Debug.Log("エラー");
                animator.SetBool("2", true); //機嫌が悪いモーション
                yield return new WaitForSeconds(0.8f);
                animator.SetBool("2", false);
                yield return new WaitForSeconds(3.0f);
           }
       }
   }
}

```


#### （コード説明）　猫のキャラクター設定

上のコードの中間部分の変数charaで指定してます。
該当部分を抽出しますと、

```
string chara = "あなたは猫ですが人間の言葉も理解できます。語尾に「にゃ」をつけるなど猫っぽい話し方をしてください。";

string itemJson 
    = "{ \"model\": \"gpt-3.5-turbo\"," +
      "\"messages\": [" +
      "{ \"role\": \"system\", \"content\": \"" + chara +"\"}," +
      "{ \"role\": \"user\", \"content\": \"" + mes + "\"}]}";
```

<br>
なお、今回は記憶を持たせてないので、しりとりとか連続性のある話はできません。
記憶といっても、今までの会話の内容（JSON）を Add していくのだと思うのですが、もっと他にいいやり方もありそうだし、次回の課題にします。

<br>

### Inspector　の設定

最後に Button のInspector を以下のように設定。

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


以上です。

<br><br>

※本作品のキャラクターには株式会社Live2Dの著作物であるサンプルデータが株式会社Live2Dの定める規約に従って用いられています。本作品は制作者の完全な自己の裁量で制作されています。
<br>