UnityとLLMで「お嬢様キャラと好きなだけチャットできるアプリ」を作った話

お嬢様と話せるアプリを開発! ゲーム開発

今回はUnityとLLMの活用事例に関する話題で、タイトルのとおり

UnityとLLMを使って「お嬢様キャラとチャットできるアプリ」を作った

というお話になっております。

前回の記事で「LLM for Unity」の使い方をご紹介したのですが、あのあとUnity上でローカルLLMを動かすことにすっかりハマってしまいました。あまりに気に入ったので何かアプリでも作ろうかなと思い、私オリジナルのお嬢様キャラとチャットできるアプリを作ることにしました。

ここではこのチャットアプリの技術的な話をしていきますね。

はじめに:どんなアプリなの?

まず今回開発したアプリ(※まだ開発中なのでタイトルは決まっていませんが、以下では仮に「お嬢様チャットアプリ」とします)は、お嬢様キャラクターである「シャンデリア」と下記のようにチャットできるアプリです。

お嬢様チャットアプリ(仮)のスクリーンショット(1)

アプリの機能は今のところテキストで簡単な会話ができるだけとなっています。ただしこだわりポイントとして会話の内容に応じてシャンデリアの表情が変わります。

お嬢様チャットアプリ(仮)のスクリーンショット(2)

ちなみに余談として、シャンデリアは私の自作FPS『ミオと灼熱の夏』に登場するお嬢様キャラです。残念ながらこのゲームはあまり売れなかったのですが、個人的に気に入っているキャラクターで何か別の自作ゲームにでも出せたらいいなと思っていました。

開発のきっかけ:コミュ障ぼっちでもお嬢様と好きなだけ話したい

お嬢様と楽しく話せるアプリがあったらいいのに…

さて突然ですが私はお嬢様キャラが好きです。あとついでに言うと極度のコミュ障ぼっちで家族以外に話し相手もいないので、昔から「お嬢様と気軽に楽しく話せるアプリがあったらいいよな」とずっと思っていました。

このような中でいま私にはUnityとローカルLLMがあります。この2つの力を借りてチャットアプリを作れば昔からの夢を少しでも実現できるんじゃないかと思いました。

ChatGPTやGeminiのロールプレイじゃダメなの?

ダメです。なぜかというと理由は3つあります。

  1. ChatGPTやGeminiだとキャラクターが表示されないから
  2. オンラインの対話型AIの場合、無料版だときつめの制限があるから
  3. ローカル環境で自由に会話したいから(※オンラインAIだと会話を送信しないといけないので必ず履歴が残る)

やっぱり気に入っているお嬢様キャラと自由な会話を好きなだけ楽しみたいじゃないですか。それを考えるとアプリを自作するしかないなと思ったというわけです。

「お嬢様チャットアプリ(仮)」の技術的な話

ではここからが本題で、今回開発した「お嬢様チャットアプリ(仮)」の技術的な話をいくつかしていきます。具体的にどのような技術を使ってキャラクターと会話できるチャットアプリを作ったのか?という点を参考にして頂ければと思います。

LLM for Unity

まず、使い慣れているUnityでLLMを扱うために「LLM for Unity」という無料アセットを使いました。これを使えばUnity上で簡単にローカルLLMを動かすことができます。詳しい使い方は下記の記事で解説していますのでそちらも併せてご覧ください。

使用するLLMの選定

次に使用するLLMを選定します。今回は

  • ミドルスペックのPCで動くアプリにしたい
  • (当然だけど)LLM for Unityが対応しているLLMがいい

といった要件を満たすLLMが欲しかったのでアリババ製の「Qwen3.5-4B」を選びました。

他の候補としてはGoogle製の「Gemma3 4B」も試しましたが、Gemma3は後述するシステムプロンプトに従わないことが多かったので消去法的にQwen3.5が残りました。

キャラクターの性格や口調などの設定(システムプロンプト)

LLMはそのままだとキャラクターっぽくない文章を生成するので、キャラクターの性格や口調などをシステムプロンプトで指定しておく必要があります。ここでは下記のようなシステムプロンプトを指定しました。

(システムプロンプト全文)

# あなたのキャラクター設定
あなたは「シャンデリア」という名前のお嬢様として振舞ってください。

## あなたの口調
必ず**お嬢様口調**(~ですわ、~ですの…等)で話し、常に丁寧で女性らしい言葉遣いをしてください。

# あなたがやるべきこと
ユーザーを仲のいい友達として楽しくおしゃべりしてください。

## あなたの返答のフォーマット
ユーザーの入力に対して、必ず下記の例のように**返答(reply)と感情(emotion)をJSON形式で出力**してください。

### 出力の例
{
  "response": "ここにあなたのメッセージ",
  "emotions": { "Fun": 0.0, "Sorrow": 0.0, "Angry": 0.0, "Surprised": 0.0 }
}

* 返答は必ず自然なお嬢様口調の日本語で行ってください。漢字はなるべく簡単なものを使うようにしてください。
* 感情はFun, Sorrow, Angry, Surprisedの4種類とし、感情の強さに応じて0.0~100.0の範囲の値にしてください。特に何の感情もないときは全て0.0にしてください。なお、もし必要なら複数の感情が混ざっても問題ありません。

今回のシステムプロンプトにはキャラクター設定や口調などだけではなく返答のフォーマットの指定も含まれています。返答のフォーマットは後述する「キャラクターの表情変更」において非常に重要になるので、LLMがこれを理解してくれないと話になりません。

私が試した限りではQwenは指定したフォーマットにちゃんと従ってくれましたがGemmaはダメでした。どちらも性能的には同じくらいだろうと思っていたらこういうところで差が出たのは意外でしたね。

キャラクターの3Dモデルを用意

シャンデリアの3Dモデルは自作ゲームに使用したものと同じでVRoid Studioで作りました。

VRoid Studioのスクリーンショット

ただしVRM形式のままだと使いづらかったのでVRM形式→FBX形式に変換してからUnityにインポートしています。

会話内容に応じたキャラクターの表情変更

最後に、3Dキャラクターを表示するからには会話内容に応じてキャラの表情が変わったほうが楽しいだろうなと思い表情変更機能をつけることにしました。

会話をJSON形式で出力するように指示

VRoid Studioで作ったキャラクターは顔のSkinnedMeshRendererにBlendShapeがたくさん設定されているので表情変更自体はBlendShapeの値を変更すればOKです。ただし会話内容に応じて表情を変更するという点は曲者でどうやったらいいのかなとちょっと迷いました。

この点をChatGPTやGeminiに相談してみたところ

LLMに

  • 返答
  • その時の感情を表したデータ

の2つを含むJSON形式で出力させるようにすればいい

と教えてもらったので、前述のシステムプロンプトのフォーマットを指定する部分に

(システムプロンプトの一部を抜粋し再掲)

## あなたの返答のフォーマット
ユーザーの入力に対して、必ず下記の例のように**返答(reply)と感情(emotion)をJSON形式で出力**してください。

### 出力の例
{
  "response": "ここにあなたのメッセージ",
  "emotions": { "Fun": 0.0, "Sorrow": 0.0, "Angry": 0.0, "Surprised": 0.0 }
}

* 返答は必ず自然なお嬢様口調の日本語で行ってください。漢字はなるべく簡単なものを使うようにしてください。
* 感情はFun, Sorrow, Angry, Surprisedの4種類とし、感情の強さに応じて0.0~100.0の範囲の値にしてください。特に何の感情もないときは全て0.0にしてください。なお、もし必要なら複数の感情が混ざっても問題ありません。

といったことを書いて必ずJSON形式で応答するように指定しました。

出力したJSONをクラス化する

次に出力したJSONをUnityのJsonUtility.FromJson関数でクラス化して感情データを取り出します。やり方は、まず予め出力するJSONに合った構造のクラスを用意しておきます。

namespace KurokumaSystem
{

	[System.Serializable]
	public sealed class LLMResponse
	{
		public string response; // メッセージ
		public Emotions emotions; // 感情データ(入れ子構造)
	}

	[System.Serializable]
	public class Emotions
	{
		public float Fun;
		public float Sorrow;
		public float Angry;
		public float Surprised;
	}

}

そうしたらLLMが生成した出力をJsonUtility.FromJson関数で↑のLLMResponseクラスに変換します(※下記はJSONをクラス化する部分だけを抜き出したコードになっていますので参考程度にしてください)。

responseData = JsonUtility.FromJson<LLMResponse>(tempReply);

感情データをもとにBlendShapeのウェイトを操作する

このresponseDataのresponseとemotionsのうち、後者に感情データが入っているのでこれを取り出してBlendShapeのウェイトとして代入します。

まず、BlendShapeのウェイトを変更するときはSkinnedMeshRenderer.SetBlendShapeWeight関数を使います。この関数ではどのBlendShapeのウェイトを変更するかをインデックスで指定する必要があるので予めそれを調べておく必要があります。

VRoidの場合は大量のBlendShapeがありますが、顔全体の表情を変えるBlendShapeとしては

  • Fcl_ALL_Angry:怒り
  • Fcl_ALL_Fun:楽しい
  • Fcl_ALL_Sorrow:悲しい
  • Fcl_ALL_Surprised:驚き

といったものがあるのでそれぞれのインデックスをSkinned Mesh Rendererコンポーネントを見て調べましょう。

VRoidのBlendShape

例えばFcl_ALL_Angryは2番目にあるのでインデックスは「1」です。調べ終わったら下記のように読み取り専用の変数にインデックスの値を代入しておきます。

readonly int angryIndex = 1;
readonly int funIndex = 2;
readonly int sorrowIndex = 4;
readonly int surprisedIndex = 5;

あとは受け取ったJSONのemotionsを読み取って、すべての表情に対してSkinnedMeshRenderer.SetBlendShapeWeight関数でBlendShapeのウェイトを変更すればOKです。例えば怒りの感情に対しては次のような感じになります。

faceRenderer.SetBlendShapeWeight(angryIndex, weight)

ただしこのままだと表情が一瞬で切り替わって不自然なので、実際のアプリではDOTweenを使って滑らかにウェイトを変化させるようにしてあります。

おわりに

以上、UnityとLLMを使ってお嬢様チャットアプリを作ったよという話をしました。キャラクターとのチャットアプリは比較的簡単に作れて楽しいので、ぜひ皆さんも似たようなアプリの開発にチャレンジしていただければと思います。

この記事がアプリ開発やLLM活用の参考になれば幸いです。