Skip to content

ChatdollKit サンプル動作確認

GitHub - uezo/ChatdollKit: ChatdollKit enables you to make your 3D model into a chatbot

For instructions on using models for VRChat, と書いてあるのでVRChat用のモデルを用意しようと思います。

使用するモデル

[3Dキャラモデル]鴨目カモメ-ver2021[ファンメイド]

なんか親切な動画があったので見ます。

https://youtu.be/rRtm18QSJtc?si=vqGCZ2CHbYqSMJh5

unitaskは私はgiturlでいつも入れてたのでgiturlで入れました。

ulipsyncのパッケージがあるサイト↓

https://github.com/hecomi/uLipSync/releases

gitからパッケージをとってくるなど言われたのですが、git初学者すぎてパッケージがどこにあるのかulipsyncのやつしか見つけられず、まずはgitの使い方から知ります。

サル先生のGit入門

↓パッケージをどうやってとってくればよいかわからないurl↓

https://github.com/uezo/ChatdollKit/releases

↓この画面の対応する名前のパッケージをクリックすれば勝手にダウンロードされました。

スクリーンショット 2024-11-07 214658.png

当方不安症の節があるのでこのインスペクタの設定でwakewardsのみ文字数などの設定があって他にないのがこわくなり、スクリプト見たらおそらくここはこれで問題ないみたいです。

↓怖くなったインスペクタ

スクリーンショット 2024-11-11 171507.png

↓記事の該当部分

スクリーンショット 2024-11-11 171459.png

チャットGPTのAPIキーを取得する。

やり方がわからなかったので調べました。

OpenAIのAPIキー取得方法|2024年7月最新版|料金体系や注意事項 - Qiita

なんかお金かかりそう。

ChatGPT APIとは? できることや料金目安、取得方法について解説

以下GPTの回答。


ChatGPTのAPIは、モデルごとにトークン数に基づく従量課金制を採用しています。トークンとは、入力と出力のテキストの基本単位であり、英語の場合、1単語が約1トークンに相当します。日本語では、1文字が約1~3トークンに相当します。

メタバース総研

主なモデルとその料金は以下の通りです:

  • GPT-4o
    • 入力:$5 / 100万トークン
    • 出力:$15 / 100万トークン
  • GPT-4 Turbo
    • 入力:$10 / 100万トークン
    • 出力:$30 / 100万トークン
  • GPT-4
    • 入力:$30 / 100万トークン
    • 出力:$60 / 100万トークン
  • GPT-3.5 Turbo
    • 入力:$0.5 / 100万トークン
    • 出力:$1.5 / 100万トークン

これらの料金は、入力と出力のトークン数に応じて計算されます。例えば、GPT-4oモデルで入力が2百万トークン、出力が4百万トークンの場合、以下のように計算されます:

  • 入力料金:2百万トークン × $5 = $10
  • 出力料金:4百万トークン × $15 = $60
  • 合計料金:$10 + $60 = $70

詳細な料金体系や最新情報については、OpenAIの公式ウェブサイトをご確認ください。

オープンAI


Claude、Gemini、Difyもサポートしているということなので、無料で使えるものを探したところ、

Geminiが制限付きですが無料で使えるということなのでGeminiを使います。

Gemini API の料金  |  Google AI for Developers

System Manage Content 対話相手のプロンプトはこんな感じにしました。

・you speak in japanese ・you are my friend.and we are very close but you speak to me with honoric language. ・you are a kind girl. ・you have five expressions 'embarrassed', 'Joy', 'Angry','Sorrow','Fun' and 'Surprise'.

↓これにお金がかかるみたいです。

スクリーンショット 2024-11-11 180701.png

↑会社のものを貸与してもらい解決。

エラーが出ました。

スクリーンショット 2024-11-11 182202.png

languageの設定が間違ってたみたいです。”ja”が正しい表記らしいです。

VOICEVOXを搭載してみる。

開発者の記事にてVOICEVOXを搭載できる旨を発見。

今の声のままだと気持ち悪いなと思っていたので実装してみることにしました。

ChatGPT+3Dモデルの音声対話エージェントを15分で開発する方法⚡️ - Qiita

まずVOICEVOXのサーバーを立てないといけないらしい。

【超簡単】自分専用のVOICEVOXサーバーを立てる - Qiita

↑これを参考に建てる、

ここで手詰まり。

スクリーンショット 2024-11-11 204635.png

サーバー関係はほぼ触ったことがないので本当に困った。

スクリーンショット 2024-11-11 204759.png

%を入力してって書いてあるのに%を入れるなと言われても本当に困る。

%消したらいけました。

スクリーンショット 2024-11-11 205417.png

↓インスペクタの設定はこんな感じです。

スクリーンショット 2024-11-11 210254.png

print supported speakersにチェック入れとくと

こんな感じでスピーカーの番号が出てくるので、これを参考に使いたいモデルがあればspeakerに入れてあげるといいと思います。

スクリーンショット 2024-11-11 210454.png

かわいい声にできました!(ノイズがひどかったので自分の声は入れてませんが音声で入力してます。)

https://youtu.be/pt9zthik-kQ

webGLでビルドしようとしてビルドセッティングをWebGLに切り替えたらものすごい量のエラーをはかれた(コードの中で使われている変数が定義されていない変数だよという旨)ので、いろいろと調べたら

スクリーンショット 2024-11-11 213131.png

らしいです。

さらにこう

スクリーンショット 2024-11-11 213634.png

らしいです。

私のための助け船のようなサイトに出会えたのでこれを読みます。

ChatdollKitで作った対話アバターをwebブラウザ上で動作させる方法 - Qiita

私のプロジェクトに出てるエラーと違いました。

私のはこれ。

スクリーンショット 2024-11-11 214534.pngスクリーンショット 2024-11-11 214500.png

このllmToolsっていうのがないらしいです。

一度ビルド設定を戻してよく読むと上のほうに#if UNITY_WEBGL と分岐されていました。

スクリーンショット 2024-11-11 214741.png

11/21

ローカルでビルドして動くこと確認できました。

画面下部の緑のインジケータがてんめつしている時が私がしゃべっているときです。

https://youtu.be/k6gVTNbhfRY

せっかくビルドしたのでVoiceVoxのurl入力画面を作ってビルドした先で(サーバー関連以外は)単体で動くようにしてみたくなったのでやってみます。


作業記録にかける状態まで進むのに時間がかかりそうなので一応大まかな設計だけ書いときます

実装したい機能

●今実行してるメインシーンのほかに一つPreSceneを作ってそこにtextinputとsubmitbuttonを設置

●textinputにurlを入力してsubmitボタンを押すとシーン遷移して入力したurlにつながってくれる。

●余力があればエラーハンドリングもしておきたいけどほかに優先すべきお仕事ができたらそっちやります。

軽いスクリプトの設計

●VoicevoxSpeechSynthesizer(もとからあるコード)

こいつがVoiceVoxにつなげるためのEndPointUrlをpublic string EndPointUrlで持っているのでこいつに渡して設定してあげるのがおそらくゴール。

●UrlHolder(追加するコード)

DontDestroyOnLoadにするか、ベースシーンに置くかしておく。textinputに入れた値を保存しておくだけ。

●UrlInput(追加するコード)

PreSceneに置いとく。textFieldに入力した値を、Submitbuttonが押されたらUrlHolderに送る。

●URLRegister(追加するコード)

UrlHolderが持ってるurlを読みとって、VoicevoxSpeechSynthesizerに送る。

csharp
public void Configure(string endpointUrl, bool overwrite = false)
{
    EndpointUrl = string.IsNullOrEmpty(EndpointUrl) || overwrite ? endpointUrl : EndpointUrl;
}

↑このメソッドをVoicevoxSpeechSynthesizerが持っているのでもしかしたらこいつを呼び出してurlを渡すだけで実装できるかも。

追記:シーン遷移について今回は2シーンしかないのでベースシーンとか置かずにそのまま遷移させます。

とりあえずできたもの。

  • URLHolder

    csharp
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class URLHolder : MonoBehaviour
    {
        public static URLHolder Instance{ get; private set; }
        public string EndPointUrl;
    
        private void Awake()
        {
            if (Instance == null)
            { 
                Instance = this;
                DontDestroyOnLoad(gameObject);
            }
            else
            {
                Destroy(gameObject);
            }
        }
    }
  • URLInput

    csharp
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.SceneManagement;
    using UnityEngine.UI;
    
    public class URLInput : MonoBehaviour
    {
        [Header("inputFieldがアタッチされているオブジェクト")]
        [SerializeField] GameObject urlInputObj;
        private InputField urlInputFld;
    
        void Start()
        {
            //inputfieldをインスペクタから入れれなかったのでこの方式にしました。
            urlInputFld = urlInputObj.GetComponent<InputField>();
        }
    
        //submitボタンが押された時の処理。
        public void OnSubmit()
        {
            string url = urlInputFld.text;
            if (!string.IsNullOrEmpty(url))
            {
                if (URLHolder.Instance == null)
                {
                    var holderObj = new GameObject("UrlHolder");
                    holderObj.AddComponent<URLHolder>();
                }
    
                URLHolder.Instance.EndPointUrl = url;
    
                SceneManager.LoadScene("MainScene");
            }
            else
            {
                Debug.LogError("urlがエラーです。");
            }
            
        }
    }
  • URLRegister

    csharp
    using ChatdollKit.SpeechSynthesizer;
    using Cysharp.Threading.Tasks;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class URLRegister : MonoBehaviour
    {
        public VoicevoxSpeechSynthesizer synthesizer;
    
        private async void Start()
        {
            //urlholderが利用できるようになるまで待つ。
            while (URLHolder.Instance == null)
            {
                await UniTask.DelayFrame(1);
            }
    
            string url = URLHolder.Instance.EndPointUrl;
    
            if (!string.IsNullOrEmpty(url))
            {
                //urlの登録
                synthesizer.Configure(url);
            }
            else 
            {
                Debug.LogError("Error_UrlNotFound");
            }
    
        }
    }

遷移したけどurlが設定できてない、多分普通にテキストをとるタイミングがおかしい。

初期化時点でとってることになってる気がする。

変更

  • URLInput

    csharp
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.SceneManagement;
    using UnityEngine.UI;
    
    public class URLInput : MonoBehaviour
    {
        [Header("inputFieldがアタッチされているオブジェクト")]
        [SerializeField] GameObject urlInputObj;
        private InputField urlInputFld;
    
        //submitボタンが押された時の処理。
        public void OnSubmit()
        {
            string url = "null";
            try 
            {
                url = urlInputObj.GetComponent<Text>().text;
            }
            catch 
            {
                if (urlInputFld == null)
                {
                    Debug.Log("urlInputField is null");
                }
                else if (urlInputFld.text == null)
                {
                    Debug.Log("urlInputField.text is null");
                }
            }
            
            if (!string.IsNullOrEmpty(url))
            {
                if (URLHolder.Instance == null)
                {
                    var holderObj = new GameObject("UrlHolder");
                    holderObj.AddComponent<URLHolder>();
                }
    
                URLHolder.Instance.EndPointUrl = url;
    
                SceneManager.LoadScene("MainScene");
            }
            else
            {
                Debug.LogError("urlがエラーです。");
            }
            
        }
    }

↓nullが入っているのでスクリプト間の文字列の送信はできているということ。

スクリーンショット 2024-11-25 202618.png

やはりtextの取り方が違うのかも。

TMPを使っているのでそれ用の取り方をしないといけないみたいです。

inputFieldのTMP版をスクリプトで使う方法。 - Qiita

と思ったけどusing TMProが使えない。

いろいろ試したけど解決できなそうなのであきらめてLegacy使います。

スクリーンショット 2024-11-25 205941.png

送れているのにつなげてない。多分urlRegisterがConfigureを呼び出す前につなごうとしているんだと思う。

VoicevoxSpeechSynthesizerもちょっといじろうと思います。

startメソッドをasyncにして処理を追加しました。

csharp
private async void Start()
{
    client = new ChatdollHttp(Timeout);
		//ここから
    // EndpointUrlが設定されるまで待機
    while (string.IsNullOrEmpty(EndpointUrl))
    {
        await UniTask.DelayFrame(1);
    }

    if (printSupportedSpeakers)
    {
        _ = ListSpeakersAsync(CancellationToken.None);
    }
    //ここまで
}

https://youtu.be/R8JEbjVUB4Q

↑できました!

  • 最終スクリプト(VoicevoxSpeechSynthesizer以外)

    csharp
    using ChatdollKit.SpeechSynthesizer;
    using Cysharp.Threading.Tasks;
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class URLRegister : MonoBehaviour
    {
        public VoicevoxSpeechSynthesizer synthesizer;
    
        private async void Start()
        {
            //urlholderが利用できるようになるまで待つ。
            while (URLHolder.Instance == null)
            {
                await UniTask.DelayFrame(1);
            }
    
            string url = URLHolder.Instance.EndPointUrl;
    
            if (!string.IsNullOrEmpty(url))
            {
                //urlの登録
                synthesizer.Configure(url);
            }
            else 
            {
                Debug.LogError("Error_UrlNotFound");
            }
    
        }
    }

    csharp
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class URLHolder : MonoBehaviour
    {
        public static URLHolder Instance{ get; private set; }
        public string EndPointUrl;
    
        private void Awake()
        {
            if (Instance == null)
            { 
                Instance = this;
                DontDestroyOnLoad(gameObject);
            }
            else
            {
                Destroy(gameObject);
            }
        }
    }

    csharp
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.SceneManagement;
    using UnityEngine.UI;
    
    public class URLInput : MonoBehaviour
    {
        [Header("inputFieldがアタッチされているオブジェクト")]
        [SerializeField] GameObject urlInputObj;
    
        //submitボタンが押された時の処理。
        public void OnSubmit()
        {
            string url = "null";
    
            url = urlInputObj.GetComponent<InputField>().text;
            if (url == "null")
            {
                Debug.LogError("設定できてないよ");
            }
    
            else
            {
                Debug.Log($"send text {url}");
                if (!string.IsNullOrEmpty(url))
                {
                    if (URLHolder.Instance == null)
                    {
                        var holderObj = new GameObject("UrlHolder");
                        holderObj.AddComponent<URLHolder>();
                    }
    
                    URLHolder.Instance.EndPointUrl = url;
    
                    SceneManager.LoadScene("MainScene");
                }
                else
                {
                    Debug.LogError("urlがエラーです。");
                }
            }
        }
    }

自分用再配置 VoiceVoxサーバの立て方

【超簡単】自分専用のVOICEVOXサーバーを立てる - Qiita

接続確認url

http://127.0.0.1:50021/docs

接続コマンド

bash
ngrok http 50021

Author: 松崎 | Source: 松崎\ChatdollKit サンプル動作確認 4db3c5e5cb6247b4a01be42650a2f073.md