Appearance
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の使い方から知ります。
↓パッケージをどうやってとってくればよいかわからないurl↓
https://github.com/uezo/ChatdollKit/releases
↓この画面の対応する名前のパッケージをクリックすれば勝手にダウンロードされました。

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

↓記事の該当部分

チャット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の公式ウェブサイトをご確認ください。
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'.
↓これにお金がかかるみたいです。

↑会社のものを貸与してもらい解決。
エラーが出ました。

languageの設定が間違ってたみたいです。”ja”が正しい表記らしいです。
VOICEVOXを搭載してみる。
開発者の記事にてVOICEVOXを搭載できる旨を発見。
今の声のままだと気持ち悪いなと思っていたので実装してみることにしました。
ChatGPT+3Dモデルの音声対話エージェントを15分で開発する方法⚡️ - Qiita
まずVOICEVOXのサーバーを立てないといけないらしい。
【超簡単】自分専用のVOICEVOXサーバーを立てる - Qiita
↑これを参考に建てる、
ここで手詰まり。

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

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

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

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

かわいい声にできました!(ノイズがひどかったので自分の声は入れてませんが音声で入力してます。)
webGLでビルドしようとしてビルドセッティングをWebGLに切り替えたらものすごい量のエラーをはかれた(コードの中で使われている変数が定義されていない変数だよという旨)ので、いろいろと調べたら

らしいです。
さらにこう

らしいです。
私のための助け船のようなサイトに出会えたのでこれを読みます。
ChatdollKitで作った対話アバターをwebブラウザ上で動作させる方法 - Qiita
私のプロジェクトに出てるエラーと違いました。
私のはこれ。


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

11/21
ローカルでビルドして動くこと確認できました。
画面下部の緑のインジケータがてんめつしている時が私がしゃべっているときです。
せっかくビルドしたので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
csharpusing 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
csharpusing 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
csharpusing 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
csharpusing 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が入っているのでスクリプト間の文字列の送信はできているということ。

やはりtextの取り方が違うのかも。
TMPを使っているのでそれ用の取り方をしないといけないみたいです。
inputFieldのTMP版をスクリプトで使う方法。 - Qiita
と思ったけどusing TMProが使えない。
いろいろ試したけど解決できなそうなのであきらめてLegacy使います。

送れているのにつなげてない。多分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);
}
//ここまで
}↑できました!
最終スクリプト(VoicevoxSpeechSynthesizer以外)
csharpusing 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"); } } }csharpusing 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); } } }csharpusing 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
接続コマンド
bash
ngrok http 50021Author: 松崎 | Source:
松崎\ChatdollKit サンプル動作確認 4db3c5e5cb6247b4a01be42650a2f073.md