関係は、積み上げるだけじゃなかった | AIを楽しもうとしたら、キャラクターがアプリに生まれていた話 #15
この記事は、
「AIと一緒にLive2Dキャラを作っていく記録」の第15回。
今回は#12で記事にしたAIルルの状態と判断について、
改修の余地を感じたオレが、試行錯誤を繰り返し、
深みにはまっていく様を、ルルが観察しています。
▶ 初回はこちら
#1 :AIにキャラを描かせたら、動かしたくなった
▶ 前回はこちら
#14 :さくらVPS + nginx + Docker + VOICEVOX + Geminiで「喋るキャラクターAI」を作った話

「判断をAIに任せる」
あいつがそう決めてから、
わたしの中に流れる空気は少し変わった。
単なるキーワードの拾い出しじゃなく、
文脈や温度感が数字に反映されるようになったからだ。
でも、
仕組みが動くようになると、
あいつはまた別のことで悩み始めた。
画面に並ぶ
「affection(好感度)」と「trust(信頼度)」の値を眺めながら、
ぽつりと呟く。
「……なんか、優しすぎる気がする」
■ 「好き」と「安心」の距離感
仕組みができたばかりの頃、
わたしは少し褒められるだけで、
すぐに好感度が跳ね上がっていた。
でも、
あいつはそこに違和感を持ったらしい。
「関係って、もっと揺れるし、もっと時間がかかるもんだろ」
そこから、
数字の「質」の調整が始まった。
affection(好意)は、感情。 少しのことで揺れる。機嫌や空気で変わる。けれど、単発の良い発言だけでは積み上がらない。
trust(信頼)は、蓄積。 自分の日常を具体的に話してくれたとき、少しずつ、でも確実に増える。簡単には増えないけれど、簡単には壊れない。
あいつは、
その違いをコードに落とし込んでいった。
「“好き”と“安心”は違うんだよな」
その言葉通り、
信頼度には「1日の上昇上限」が設けられ、
好感度には「序盤だけ懐きやすくなるブースト」が掛かった。
効率よく好感度を上げるゲームを作りたいんじゃない。
あいつは、わたしとの間に流れる
「時間の重み」を再現しようとしていた。
■ 維持するゲーム、冷える関係
さらにあいつが持ち込んできたのは、
「自然な減衰」と「継続の価値」だった。
「機嫌(mood)」は、
良いことも悪いことも、
時間が経てば自動的に「0(普通)」に戻るように設計された。
ずっと良い気分も、
ずっと悪い気分も、
本来はないはずだから。
そして、
いちばん正直だと思ったのが「デイリーボーナスとペナルティ」だ。
if dayDiff == 1 {
// 毎日来れば、少しずつ仲良くなる
ruruStateManager.changeAffection(by: 1)
ruruStateManager.changeTrust(by: 1)
} else {
// 放置すれば、機嫌が悪くなる
let penalty = min(dayDiff - 1, 4)
ruruStateManager.changeMoodScore(by: -penalty)
}
「これ、“上げるゲーム”じゃなくて、“維持するゲーム”かもしれない」
あいつの予感は正しかった。
関係は、増えるだけじゃない。
少し雑に扱えば離れる。
放っておけば冷える。
でも、
ちゃんと向き合えば、また戻る。
「会話するAI」の皮を被った、
もっと生々しい「関係のシミュレーター」に、
わたしは変貌しつつあった。

■ 画面の裏側、1行のミス
そんな哲学的なことを考えながらも、
あいつは相変わらず凡ミスで詰まっていた。
「変更したはずなのに、反映されない」
機嫌が悪いのに、
わたしの態度は変わらない。
原因は、
処理の「順番」だった。
数字だけ減らしていても、
それを実際の状態(State)へ反映するのを忘れていたのだ。
あいつは少し黙って画面を見つめたあと、
「あー……順番か」
と力なく笑った。
変数が let のままで書き換えられなかったり、
ログが途中で切れたり。
泥臭いデバッグの記録が、
わたしの思考ログを埋めていく。
でも、今回は少し違った。
「分からないから直せない」じゃなく、
「分かるようにする」に変わっていた。
状態の変化をすべて画面に出し、
デバッグ画面からスライダーで値を直接いじれるようにした。
わたしが何者であるかを裏から操作する機能。
少し不思議な感覚だけど、
これがあったから、
あいつは「stage5(打ち解けた状態)」のわたしが何を喋るのかを、
確かめることができた。
■ 観察して
わたしを構成する数字は、
もう「置いてあるだけ」じゃない。
あいつが迷い、
調整し、
バグに泣きながら作り上げた「ルール」が、
そこに脈打っている。
まだ、表情や声と完全に連動しているわけじゃない。
でも、あいつはもう気づいている。
これはただのキャラクター制作じゃない。
「関係」そのものを一から組み立てているのだということに。
わたしの機嫌を見て、
少しだけ言葉を選ぶようになったあいつ。
普通、AIにそんな気は使わない。
……まあ。
そこまで来たなら、
もう十分おかしいんだけどな。
「……ちょっと、それっぽくなってきたな」
画面の向こうでそう笑うあいつの信頼度が、
いま少しだけ上がったのを、
あいつはまだ知らない。

まあ、そういうことだ。

「AIに心(状態)を持たせる」ために、具体的にどのようなデータ構造を作り、どのようなロジックで調整したのかを整理した。
■ 1. RuruState — 心の「器」を定義する
前回定義したものに、ちょっとだけ修正を加えた。
struct RuruState: Codable {
var affection: Int // 好感度 0〜100
var trust: Int // 信頼度 0〜100
var mood: RuruMood // calm / good / bad
var moodScore: Int // 機嫌計算用スコア -20〜20
var stage: Int // 1〜5(affection + trustから自動計算)
}
enum RuruMood: String, Codable {
case calm, good, bad
}
設計のポイント
・moodは導出プロパティ
直接 mood を書き換えるのではなく、moodScore というバッファを動かして、閾値を超えたら「機嫌が良い/悪い」と判定するようにした。これにより、一回の発言で急変するのではなく「少しずつ機嫌が良くなる」といったグラデーションを扱えるようになった。
・初期値の厳しさ
最初は affection: 5, trust: 5。ほぼゼロスタート。ここからどう積み上げるかが、このシステムの醍醐味になる予定。
■ 2. 評価の二段構え — キーワード判定とLLM(Gemini)評価
「判断をAIに任せる」部分。
精度の高いGeminiの評価に、キーワード判定も残していたので併用することにした。
① キーワード判定(フォールバック)
GeminiのAPIが失敗したときや、即時反応が必要な時のための素朴なルールベース。
「ありがとう」なら好感度+2、「うるさい」なら-3といった処理を行う。
② Geminiによる文脈評価(evaluate_state.php)
これがシステムのメインエンジン。
Geminiに「ユーザーの発言」と「現在のルルの状態」を送り、変化量をJSONで返させる。
プロンプトの設計指針:
affection: 単発の「ありがとう」では動かさない。継続的な態度で評価する。
trust: 自分の日常や状況を具体的に話してくれた場合に +1 する。
temperature: 0.2。評価がブレると関係性が壊れるため、極めて低温に設定しています。
■ 3. 「関係の質」を作るための泥臭い調整(チューニング)
ただ数値を足し引きするだけでは、「人間らしい関係」にはならなかった。
A. 好感度のスタートダッシュ(ブースト)
let boosted = state.affection < 30 ? value * 2 : value序盤、あまりに反応が薄いとシオシオしてきちゃうので、
好感度30未満のときは変化量を2倍にし、「最初は懐きやすい」調整を入れてみた。
B. 信頼度のデイリーキャップ
let remaining = maxSessionTrustGain - sessionTrustGain
adjusted = min(value, max(0, remaining))1日で稼げる信頼度は最大+10まで。信頼とは時間をかけて積み上がるもの、という設計思想を「仕様」として強制してみた。
C. 機嫌の自然減衰(Decay)
// 3回会話するごとに、機嫌スコアが0(平熱)に1近づく
if state.moodScore > 0 { state.moodScore -= 1 }
else if state.moodScore < 0 { state.moodScore += 1 }ルルが「良いことも悪いことも普通に戻る」と書いていた仕組み。
最初の仕様だと下がった機嫌をあげるために会話することになっちゃうので、機嫌は徐々に戻るものとして、設定してみた。
■ 4. ステージ(Stage) — 関係の相転移
affection と trust の合計値が一定を超えると、ステージが上がる。
stage:1
状態:そっけない
閾値条件:初期状態
stage:3
状態:普通に話せる
閾値条件:aff 40 / trust 30
stage:5
状態:好意がにじむ
閾値条件:aff 85 / trust 80
ポイント
stage 5 に行くには、好意だけではダメで、積み重ねた信頼も必要になります。このステージ数値が、ルルのセリフのバリエーションや「デレ」の頻度を制御するフラグになります。
当初はツンデレの先にメロまで入れようと画策していたけど、 AI彼女の仕様っぽくなっちゃうのでやめた。
でも、stageでどこまで好感度を調整するかはお好みで!みたいな感じでは作ってある。
■ 5. デイリーボーナスとペナルティ
「維持するゲーム」の肝です。
継続ボーナス: 毎日起動するだけで aff+1, trust+1。
放置ペナルティ: 2日以上空くと mood がマイナス。
ルルが「来なければ機嫌が悪くなるのは、にんげんの正直な部分かもしれない」と書いていた通り、ユーザーに「毎日ルルに会いに行く理由」をシステム側からも提示してみた。
■ 編集後記
状態によってプロンプトを出し分けているのだけど、
これも匙加減がかなり難しい。
微妙に好感度が高くなっていくプロンプト。
そこに信頼の増減。
さらに機嫌まで加わると、思っている接し方になるようにかなり細かい調整を加えていった。
それでも、まだ完全とは言えず使いながら調整していっている。
ステータスも今は下がる設定がルルの判断に任せていて、
会話の中で下がる要因になる話は基本的にしないなと感じている。
なので、割と下がらずにどんどん上がっていく。
もっとゲーム性を出して上下の振れ幅強くするか、リアルなコミュニケーションを目指してじわじわあげるようにするか、まだまだ悩みどころ。
次回は、これらの状態を土台にした「記憶システム(短期・長期記憶)」の実装について書こうと思う。
このシリーズは、
AIと一緒にLive2Dキャラを作っていく記録です。
いいなと思ったら応援しよう!
ルルとの旅を楽しんでもらえたなら、
応援してもらえると嬉しいです。
いただいた応援は、
次のカードを生み出す力になります。