🐕
ノベルゲーム型MMO - NPC会話システム設計書
はじめに
- 生成AIで作ったゲーム設計をそのまま書きます。
- PostgreSQL内にワールドを構築したノベルゲーム型MMOを作ります。
- PostgreSQLだけでワールドを同期するので実装の簡易さを実現します。
- 実装はC#とPython、クライアントはUnityを使います。
ノベルゲーム型MMO - NPC会話システム設計書
概要
本設計書は、プレイヤーごとの個別記憶を持つNPC会話システムの実装計画を詳述する。システムは.NETをコアとしつつ、一部の高度な機能をPythonマイクロサービスで強化する、コストパフォーマンスに優れたハイブリッドアーキテクチャを採用する。
1. システムアーキテクチャ
1.1 全体構成
[Unity Client] <---> [.NET Core Backend] <---> [PostgreSQL]
|
v
[Python Microservices]
- 感情分析サービス
- 重要度スコアリングサービス
1.2 技術スタック
コンポーネント | 技術 |
---|---|
フロントエンド | Unity |
コアバックエンド | .NET Core |
データベース | PostgreSQL (Vector拡張) |
AI機能 | OpenAI API |
補助サービス | Python (FastAPI) |
通信プロトコル | JSON-RPC |
1.3 開発フェーズ
- フェーズ1: .NETコアシステム構築(3ヶ月)
- フェーズ2: 感情分析マイクロサービス導入(1.5ヶ月)
- フェーズ3: 重要度スコアリング強化(1.5ヶ月)
2. データモデル
2.1 主要テーブル
NPCマスターテーブル
CREATE TABLE npc_master (
npc_id SERIAL PRIMARY KEY,
npc_name VARCHAR(50) NOT NULL,
personality TEXT NOT NULL,
role VARCHAR(100) NOT NULL,
default_prompt TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
プレイヤーテーブル
CREATE TABLE player (
player_id SERIAL PRIMARY KEY,
player_name VARCHAR(50) NOT NULL,
last_login TIMESTAMP NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
NPC・プレイヤー関係テーブル
CREATE TABLE npc_player_relation (
relation_id SERIAL PRIMARY KEY,
npc_id INTEGER REFERENCES npc_master(npc_id),
player_id INTEGER REFERENCES player(player_id),
familiarity FLOAT NOT NULL DEFAULT 0.0,
relation_status VARCHAR(50) NOT NULL DEFAULT 'neutral',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
会話履歴テーブル
CREATE TABLE conversation_history (
conversation_id SERIAL PRIMARY KEY,
npc_id INTEGER REFERENCES npc_master(npc_id),
player_id INTEGER REFERENCES player(player_id),
player_input TEXT NOT NULL,
npc_response TEXT NOT NULL,
input_embedding VECTOR(1536) NOT NULL,
context_embedding VECTOR(1536) NOT NULL,
importance_score FLOAT NOT NULL DEFAULT 0.5,
emotion_state JSONB,
conversation_datetime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
記憶アーカイブテーブル
CREATE TABLE memory_archive (
archive_id SERIAL PRIMARY KEY,
npc_id INTEGER REFERENCES npc_master(npc_id),
player_id INTEGER REFERENCES player(player_id),
summary TEXT NOT NULL,
summary_embedding VECTOR(1536) NOT NULL,
start_date TIMESTAMP NOT NULL,
end_date TIMESTAMP NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
2.2 インデックス
CREATE INDEX conversation_embedding_idx ON conversation_history USING ivfflat (input_embedding vector_cosine_ops) WITH (lists = 100);
CREATE INDEX context_embedding_idx ON conversation_history USING ivfflat (context_embedding vector_cosine_ops) WITH (lists = 100);
CREATE INDEX summary_embedding_idx ON memory_archive USING ivfflat (summary_embedding vector_cosine_ops) WITH (lists = 100);
3. .NET コアシステム設計
3.1 主要コンポーネント
3.1.1 リポジトリレイヤー
- NPCRepository: NPCの基本情報とプレイヤーとの関係管理
- ConversationRepository: 会話履歴管理とベクトル検索
- MemoryRepository: 記憶のアーカイブと検索
3.1.2 サービスレイヤー
- DialogueService: 会話フロー制御と応答生成
- EmbeddingService: OpenAIを使用したテキスト埋め込み生成
- MemoryManagementService: 記憶の整理とアーカイブ
- AIPipelineService: AIサービスとの連携管理
3.1.3 コントローラーレイヤー
- ConversationController: 会話APIエンドポイント
- NPCController: NPC情報管理API
- MemoryController: 記憶管理API
3.2 主要クラス設計
// 会話サービスの基本設計
public class DialogueService : IDialogueService
{
private readonly IConversationRepository _conversationRepo;
private readonly IEmbeddingService _embeddingService;
private readonly IAIPipelineService _aiPipelineService;
public async Task<DialogueResponse> ProcessDialogue(DialogueRequest request)
{
// 1. 入力テキストのエンべディング生成
var inputEmbedding = await _embeddingService.CreateEmbedding(request.PlayerInput);
// 2. 関連記憶の検索
var relevantMemories = await _conversationRepo.FindRelevantMemories(
request.PlayerId,
request.NpcId,
inputEmbedding,
5);
// 3. プロンプト生成と応答取得
var response = await _aiPipelineService.GenerateResponse(
request.NpcId,
request.PlayerInput,
relevantMemories);
// 4. 新しい会話の記録
await _conversationRepo.SaveConversation(
request.PlayerId,
request.NpcId,
request.PlayerInput,
response.NpcResponse,
inputEmbedding,
response.ContextEmbedding,
response.ImportanceScore);
return response;
}
}
4. Python マイクロサービス設計
4.1 感情分析サービス
4.1.1 機能
- テキストからの感情状態抽出
- 8次元感情ベクトル生成(喜び、悲しみ、怒り、恐れ、驚き、嫌悪、信頼、期待)
- NPCの性格に基づく感情応答スコアリング
4.1.2 実装
# FastAPIを使用した感情分析サービス
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from transformers import pipeline
import numpy as np
app = FastAPI()
# 感情分析モデルの初期化
emotion_analyzer = pipeline("text-classification",
model="j-hartmann/emotion-english-distilroberta-base",
top_k=8)
class TextRequest(BaseModel):
text: str
npc_personality: str = ""
class EmotionResponse(BaseModel):
emotions: dict
dominant_emotion: str
intensity: float
response_modifier: dict
@app.post("/analyze_emotion", response_model=EmotionResponse)
async def analyze_emotion(request: TextRequest):
try:
# 感情分析の実行
emotion_results = emotion_analyzer(request.text)
# 結果の整形
emotions = {item["label"]: item["score"] for item in emotion_results[0]}
dominant_emotion = max(emotions.items(), key=lambda x: x[1])[0]
intensity = emotions[dominant_emotion]
# NPC性格に基づく応答修飾子の計算
response_modifier = calculate_response_modifier(emotions, request.npc_personality)
return EmotionResponse(
emotions=emotions,
dominant_emotion=dominant_emotion,
intensity=intensity,
response_modifier=response_modifier
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
def calculate_response_modifier(emotions, personality):
# NPCの性格に基づく応答修飾子を計算するロジック
# 実際の実装ではより複雑な性格マッピングを行う
modifiers = {}
# 例: 喜びが高い場合、フレンドリーなNPCはより親しみやすく応答
if "joy" in emotions and emotions["joy"] > 0.5:
if "friendly" in personality.lower():
modifiers["friendliness"] = min(1.0, emotions["joy"] * 1.5)
elif "stern" in personality.lower():
modifiers["friendliness"] = emotions["joy"] * 0.5
# その他の感情と性格の組み合わせに基づく修飾子
return modifiers
4.2 重要度スコアリングサービス
4.2.1 機能
- 会話内容の重要度評価
- 長期記憶価値の予測
- 記憶圧縮・要約の優先順位決定
4.2.2 実装
# 重要度スコアリングサービス
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sentence_transformers import SentenceTransformer
app = FastAPI()
# モデルの初期化
sentence_model = SentenceTransformer('all-MiniLM-L6-v2')
tfidf = TfidfVectorizer(max_features=1000)
class ImportanceRequest(BaseModel):
current_text: str
previous_conversations: list
npc_id: int
player_id: int
class ImportanceResponse(BaseModel):
importance_score: float
novelty_score: float
emotional_impact: float
information_density: float
narrative_relevance: float
@app.post("/calculate_importance", response_model=ImportanceResponse)
async def calculate_importance(request: ImportanceRequest):
try:
# 現在のテキストの埋め込み
current_embedding = sentence_model.encode(request.current_text)
# 情報密度の計算(TF-IDFベース)
all_texts = [request.current_text] + [conv for conv in request.previous_conversations]
tfidf_matrix = tfidf.fit_transform(all_texts)
information_density = np.sum(tfidf_matrix[0].toarray()) / len(request.current_text.split())
# 新規性スコアの計算
if request.previous_conversations:
previous_embeddings = sentence_model.encode(request.previous_conversations)
similarities = np.dot(previous_embeddings, current_embedding) / (
np.linalg.norm(previous_embeddings, axis=1) * np.linalg.norm(current_embedding)
)
novelty_score = 1.0 - np.max(similarities)
else:
novelty_score = 1.0
# 感情的インパクトの計算(別サービスを呼び出すか、簡易版を実装)
emotional_impact = calculate_emotional_impact(request.current_text)
# ナラティブ関連性(実際はゲーム状態やクエスト情報も考慮)
narrative_relevance = 0.7 # 仮の値
# 最終的な重要度スコア
importance_score = (
0.25 * novelty_score +
0.25 * emotional_impact +
0.25 * information_density +
0.25 * narrative_relevance
)
return ImportanceResponse(
importance_score=importance_score,
novelty_score=novelty_score,
emotional_impact=emotional_impact,
information_density=float(information_density),
narrative_relevance=narrative_relevance
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
def calculate_emotional_impact(text):
# 簡易版の感情インパクト計算
# 実際の実装では感情分析サービスと連携
emotion_words = {
"happy": 0.7, "sad": 0.8, "angry": 0.9, "surprised": 0.8,
"love": 0.9, "hate": 0.9, "fear": 0.8, "excited": 0.7
}
words = text.lower().split()
impact = 0.0
matches = 0
for word in words:
if word in emotion_words:
impact += emotion_words[word]
matches += 1
return impact / max(1, matches) if matches > 0 else 0.5
5. 会話処理フロー
5.1 基本会話フロー
- プレイヤーがUnityクライアントで入力
- Unity → .NET APIへJSON-RPCリクエスト送信
- .NET APIがOpenAI Embeddingで入力をベクトル化
- PostgreSQLで類似会話記憶の検索
- 必要に応じてPython感情分析サービス呼び出し
- プロンプト生成とOpenAI APIでの応答生成
- 必要に応じてPython重要度スコアリングサービス呼び出し
- 会話記録とメモリ更新
- クライアントへの応答返送
5.2 シーケンス図
Player Unity .NET API PostgreSQL Python OpenAI
| | | | | |
|--Input--->| | | | |
| |--Request-->| | | |
| | |--Vector--->| | |
| | |<--Mem------| | |
| | |----------------------->| |
| | |<-----------------------| |
| | |----------------------------->| |
| | |<-----------------------------| |
| | |----------------------->| |
| | |<-----------------------| |
| | |--Save----->| | |
| |<--Resp-----| | | |
|<--Display-| | | | |
6. プロンプト設計
6.1 基本プロンプトテンプレート
あなたは「{npc_name}」というノベルゲーム内のNPCです。
【性格】
{personality}
【役割】
{role}
【プレイヤーとの関係】
{relation_status}、親密度: {familiarity}/10
【感情状態】
{emotion_state}
【過去の関連する記憶】
{relevant_memories}
プレイヤー「{player_name}」からの入力:
{player_input}
以下の制約に従って応答してください:
1. {npc_name}の性格に一致する口調で話してください
2. 150文字以内で簡潔に答えてください
3. 感情状態を反映した応答をしてください
4. ゲーム内の設定に沿った内容だけを話してください
6.2 感情反映プロンプト拡張
【感情修飾子】
- 親密さレベル: {friendliness}
- 怒りレベル: {anger}
- 悲しみレベル: {sadness}
- 興奮レベル: {excitement}
これらの感情状態を考慮して、適切に反応してください。
例えば、親密さが高い場合はより友好的に、怒りが高い場合はよりぶっきらぼうに応答するなど。
7. メモリ管理メカニズム
7.1 記憶分類
- アクティブメモリ: 直近14日間の会話(完全保持)
- 短期メモリ: 15-60日間の会話(重要度に基づき選別)
- 長期メモリ: 61日以上前の会話(要約と重要記憶のみ保持)
7.2 記憶圧縮プロセス
- 日次バッチジョブで記憶分類を更新
- 短期→長期への移行時:
- 重要度スコアリングで重要会話を特定
- 関連会話をグループ化し要約を生成
- 要約とオリジナル会話のマッピングを保存
7.3 記憶の想起アルゴリズム
public async Task<List<ConversationMemory>> RecallMemories(int playerId, int npcId, Vector inputEmbedding, int limit)
{
// 1. アクティブメモリから直近の会話を取得(時系列)
var recentMemories = await _dbContext.ConversationHistory
.Where(c => c.PlayerId == playerId && c.NpcId == npcId)
.OrderByDescending(c => c.ConversationDatetime)
.Take(3)
.ToListAsync();
// 2. ベクトル類似度による関連記憶の検索
var similarMemories = await _dbContext.ConversationHistory
.Where(c => c.PlayerId == playerId && c.NpcId == npcId)
.OrderByDescending(m => m.InputEmbedding.CosineSimilarity(inputEmbedding))
.Take(limit - recentMemories.Count)
.ToListAsync();
// 3. 重要度スコアによる調整
var adjustedMemories = similarMemories
.OrderByDescending(m => m.ImportanceScore * 0.7 +
m.InputEmbedding.CosineSimilarity(inputEmbedding) * 0.3)
.Take(limit - recentMemories.Count)
.ToList();
// 4. アーカイブからの要約記憶検索
var summarizedMemories = await _dbContext.MemoryArchive
.Where(m => m.PlayerId == playerId && m.NpcId == npcId)
.OrderByDescending(m => m.SummaryEmbedding.CosineSimilarity(inputEmbedding))
.Take(1)
.ToListAsync();
// 5. 結果の結合と重複排除
var result = recentMemories
.Concat(adjustedMemories)
.Concat(summarizedMemories.Select(s => new ConversationMemory {
Content = $"[要約記憶: {s.StartDate} - {s.EndDate}] {s.Summary}"
}))
.DistinctBy(m => m.ConversationId)
.ToList();
return result;
}
8. パフォーマンスと拡張性
8.1 パフォーマンス目標
- 平均応答時間: 1.5秒以内
- 同時接続ユーザー: 1,000名
- NPCあたりの記憶容量: 最大10,000会話
8.2 スケーリング戦略
- .NETバックエンドのKubernetes水平スケーリング
- Pythonマイクロサービスの独立スケーリング
- PostgreSQLのリードレプリカ活用
8.3 キャッシング戦略
- よく使われるNPC情報のインメモリキャッシュ
- 頻繁に呼び出される感情分析結果のキャッシュ
- 重要度スコアの計算結果キャッシュ
9. セキュリティと運用
9.1 認証と認可
- JWTによるプレイヤー認証
- ロールベースの権限管理
9.2 監視体制
- Prometheusによる指標収集
- Grafanaによる可視化ダッシュボード
- ELKスタックによるログ分析
9.3 障害対応
- サーキットブレーカーパターンの実装
- フォールバックダイアログの準備
- 自動リトライ戦略
10. 実装ロードマップ
10.1 第1フェーズ(3ヶ月)
- .NETコアシステム構築
- 基本的なOpenAI連携
- 記憶管理の基礎実装
- Unityクライアント連携
10.2 第2フェーズ(1.5ヶ月)
- Python感情分析サービスの実装と統合
- 感情ベースの応答調整機能
- NPCキャラクターの個性強化
10.3 第3フェーズ(1.5ヶ月)
- 重要度スコアリングサービスの実装
- 記憶圧縮と要約の高度化
- パフォーマンス最適化
11. 運用コスト予測
11.1 開発コスト
- バックエンド開発者(.NET): 2名 × 6ヶ月
- AI/Python開発者: 1名 × 3ヶ月
- フロントエンド開発者(Unity): 1名 × 4ヶ月
- テスター: 1名 × 2ヶ月
11.2 インフラコスト(月額)
- サーバーホスティング: $500-1,000
- データベース: $200-400
- OpenAI API: $1,000-2,000(プレイヤー数に依存)
11.3 保守コスト(月額)
- 運用保守エンジニア: 1名($3,000-5,000)
- モニタリングサービス: $100-200
- セキュリティ監査: $300-500
付録: 推奨Pythonライブラリ
目的 | ライブラリ |
---|---|
APIフレームワーク | FastAPI |
感情分析 | Transformers, spaCy |
ベクトル処理 | NumPy, SciPy |
テキスト埋め込み | sentence-transformers |
自然言語処理 | NLTK, spaCy |
HTTP通信 | httpx, aiohttp |
Discussion