はじめに
4月26日から開発を始めて、5月25日までの1か月で「LLM × VRM」で動くデスクトップマスコットエンジンを作ってみた話です。
GitHubリポジトリはこれ
動機
DifyでAIエージェントを作ったし、それを組み合わせてなんか「いい感じ」のアプリ作れねーかな?と思ったのがきっかけ。
で、AIエージェントを擬人化したキャラ(VRM)を作ってみたんですよ。
そしたら、「このキャラが喋って動いたらマジのエージェントじゃね?」って思って、勢いで作り始めました。
方向感はuDesktopMascotにインスパイアされました。
ちょうど「開発するかー」って気分のときに、「人のやつ改造した方が楽か?」→「探してみるか」→「おっ、これ良さそうじゃん!」って感じで出会いました。(ちょうどSteam版がリリースされたタイミングでした)
こちらも良作なのでぜひ。VRM以外の形式にも対応してるし、音声も流せたりします。
ちなみに、VRM1.0のロード部分は丸パクリさせてもらった。(VRM0.xやFBXとかのロード部分は削る感じ)
そんな感じで当初はuDesktopMascotを改造しようと思ったんすけど、機能が多くてUnity初心者の自分には改造がしんどかったので、結局は新規開発しました。(てかUnity触るの初めてだったし)
急がば回れってやつですね。
これは余談ですが、UIの配置や考え方はまるっきり違う感じなのでそこは棲み分けできそうな感じがします。
開発テーマ
「そこで生きているキャラクター」
これが今回のクソデカテーマ。
あくまで“それっぽく”動くことを目指して作っています。
LLMとVRMの組み合わせって、なんかもう最高じゃないですか?
UIの方向性
-
ウィンドウの透過処理
- Windows APIを直接叩くのは面倒だったので、UniWindowControllerを使う
-
チャットUI
- 本当は別ウィンドウで出したかったけど、Unityの仕様上ディスプレイの数しかウィンドウを作れない
- 同一ウィンドウ内に仮想ウィンドウを作って実現した。TextMesh ProなどのUnity標準機能を活用)
- 本当は別ウィンドウで出したかったけど、Unityの仕様上ディスプレイの数しかウィンドウを作れない
-
VRMとチャットウィンドウの位置
- VRMの吹き出しっぽくチャットウィンドウを常時表示。
- ウィンドウの最小化機能も今後欲しいかもしれない。
Unityのパイプライン
VRM使うし、とりあえず 3D Universal Render Pipeline(URP) でいこう、という感じで採用した。特に深い意味はない
メイン機能
-
LLMチャット機能
- ローカルLLM(対応済み)
- リモートLLM(Dify APIでの対応を予定)
-
LLMによる表情モーフィング
- ローカルLLM(対応済み)
- リモートLLM(こちらもDify API予定)
-
VRMとのインタラクション
- 撫でる・つつく・キャラが「振られた」ときのリアクション
- 「振られた」は実装済み
- 撫でる・つつく動作は複数パターン用意できるとよさそう
サブ機能
-
LLM設定、システムプロンプト、VRMモデルの外部ファイル差し替え機能
-
LLMパラメータのカスタマイズ
-
Temperature
,Top-K
,Top-P
など
-
-
VRMの表示調整機能
- スケール調整(推奨:0.5〜1.8)
- トーンマッピングや外力(SpringBone)設定
-
チャット窓のRGBA背景・透過度調整
-
瞬き抑制機能
- 特定の表情が一定以上の重みのとき、瞬きをオフにする
- 目を細める系の表情(Happyとか)だと、通常の瞬きが「目を閉じすぎ」になる問題を回避する機能です
- 糸目キャラのためにデフォルト瞬き禁止で、特定の表情(怒り、驚き)で目を見開く系のムーブするときだけ瞬きオンにもできます
-
キャラが「振られた」ときの感知強度を調整可能です
そのほか細かい機能もいろいろありますが、ひとまずこんな感じで。
紹介
Qiitaはgifの容量的にアウトだったので、GitHubで見てもらえたらなと。
使っているところ(GIF)
好きなキャラのVRMを突っ込んで、システムプロンプトを調整するだけで、かなり「いい感じ」に動いてくれます。
多少プロンプトのチューニングは必要ですが、そこは慣れです。
苦労ポイント
- 非矩形ウィンドウを作る(ウィンドウを透過する)処理
-
ScrollViewの挙動が謎すぎて死んだ
→ パラメータを一日中ポチポチ調整して、なんとか解決 -
VRMの頭の横にチャットUIをちょうど良く配置する機能
-
VRMスケール変更時の調整
- 透明化されたUnityウィンドウに、いい感じに収まるように
- チャットUIのスケーリングに影響しないよう動的に配置計算
-
ウィンドウ移動時にSpringBoneに外力を与えて「それっぽく」動かす
-
タイプライター風の文字出力機能
- LLMからStreamingでテキストが出力される際に、順次描画されることで自然な表示に
- LLMのメッセージとシステムメッセージ出力の違和感をなくすための地味な調整
- 非同期メソッドじゃないとUIが固まる、という初歩の罠にもハマった
まとめ
デスクトップマスコットエンジン、作るのめっちゃ楽しい。
本当はRustで書きたかったけど、今回は目的優先でUnityにしました。
Unity、最初は取っつきにくかったけど、慣れてくるとまぁまぁ良いですね。