https://github.com/zapabob/aivtuber-chat-unity
UnityProject
https://github.com/zapabob/aivtuber-chat-unity
Last synced: about 1 year ago
JSON representation
UnityProject
- Host: GitHub
- URL: https://github.com/zapabob/aivtuber-chat-unity
- Owner: zapabob
- Created: 2024-10-12T05:08:38.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-01-25T11:51:19.000Z (over 1 year ago)
- Last Synced: 2025-05-12T06:52:39.523Z (about 1 year ago)
- Language: C#
- Homepage:
- Size: 56.6 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# AIVTuber Controller
Unity用のAIVTuberコントローラーシステムです。VRMモデルとLLM(大規模言語モデル)を組み合わせて、インタラクティブなバーチャルYouTuberを作成できます。
## 主な機能
- 複数のLLMサービス対応(Dify、ローカルLLM、Maria)
- VOICEVOXを使用した音声合成
- VRMモデルの表情制御
- 自動まばたき機能
- エラーハンドリングとリトライ機能
## 必要要件
- Unity 2021.3以降
- UniVRM 10.0以降
- VOICEVOX(音声合成用)
## セットアップ
1. VRMモデルを`StreamingAssets`フォルダに配置
2. AIVTuberControllerコンポーネントをシーンに追加
3. 必要な設定(LLMサービス、VOICEVOX)を構成
## 設定項目
### LLMサービス設定
- エンドポイントURL
- APIキー
- システムプロンプト
- 各種パラメータ(温度、最大トークン数など)
### 音声設定
- VOICEVOXエンドポイント
- 話者ID
- 音声パラメータ(速度、ピッチ、イントネーション)
### VRMモデル設定
- モデルパス
- 表情マッピング
## エラーハンドリング
システムは以下のタイプのエラーを処理します:
- 設定エラー
- AIレスポンスエラー
- VRMモデルエラー
- 音声合成エラー
## ライセンス
MITライセンス
## 注意事項
- VOICEVOXは別途インストールが必要です
- LLMサービスは別途APIキーの取得が必要な場合があります
- VRMモデルの利用規約を確認してください
## 使用例
### 基本的な使用方法
```csharp
// AIVTuberControllerの初期化
var controller = gameObject.AddComponent();
controller.settings = new AIVTuberSettings {
LLMServiceType = LLMServiceType.LocalLLM,
SystemPrompt = "あなたはフレンドリーなバーチャルYouTuberです。"
};
// メッセージの処理
await controller.HandleMessage("こんにちは!");
```
### カスタム感情表現の追加
```csharp
// 感情表現のマッピングをカスタマイズ
emotionToExpression.Add("excited", ExpressionKey.Happy);
emotionToExpression.Add("thoughtful", ExpressionKey.Neutral);
```
### 音声パラメータの調整
```csharp
// 音声パラメータのカスタマイズ
config.VoiceSpeed = 1.2f; // 話速
config.VoicePitch = 0.2f; // ピッチ
config.VoiceIntonation = 1.1f; // イントネーション
```
## 詳細な使用方法ガイド
### 1. プロジェクトのセットアップ
#### 1.1 Unity環境の準備
1. Unity Hub で新規プロジェクトを作成
- Unity 2021.3以降を選択
- 3Dテンプレートを使用
- プロジェクト名を設定
2. 必要なパッケージのインストール
```
- UniVRM 10.0(Package Managerから)
- UniTask(Package Managerから)
- JSON.NET(Package Managerから)
```
#### 1.2 フォルダ構造の設定
```
Assets/
├── StreamingAssets/
│ ├── Models/ # VRMモデルファイル
│ └── LLMModels/ # ローカルLLMモデル
├── Scripts/
│ ├── Core/ # コア機能
│ ├── Services/ # LLMサービス
│ ├── UI/ # UI関連
│ └── Utils/ # ユーティリティ
└── Scenes/
└── Main.scene # メインシーン
```
#### 1.3 外部サービスの準備
1. VOICEVOXのセットアップ
```bash
# VOICEVOXのインストール
1. https://voicevox.hiroshiba.jp/ からダウンロード
2. インストーラーを実行
3. デフォルトポート(50021)で起動
```
2. LLMサービスの準備
```bash
# Difyの場合
1. https://dify.ai/ でアカウント作成
2. APIキーを取得
# ローカルLLMの場合
1. モデルをダウンロード
2. KoboldCPP/llama.cppを設定
3. APIサーバーを起動
```
### 2. シーンの設定
#### 2.1 基本シーン構成
```
Scene
├── Main Camera
├── Directional Light
├── AIVTuberController
│ ├── VRMModelLoader
│ ├── ErrorNotification
│ └── ChatUI
└── EventSystem
```
#### 2.2 コンポーネントの設定手順
1. AIVTuberControllerの設定
```csharp
// インスペクターでの設定
- VRM Model: VRMモデルのプレハブをアタッチ
- Settings: AIVTuberSettingsアセットを作成してアタッチ
- Error Display: ErrorNotificationをアタッチ
```
2. カメラの設定
```
Position: (0, 1.5, -2)
Rotation: (0, 180, 0)
Field of View: 60
```
### 3. 運用シナリオ別の設定例
#### 3.1 ゲーム実況配信の場合
```csharp
// システムプロンプトの設定
settings.SystemPrompt = @"あなたはゲーム実況が得意なバーチャルYouTuberです。
以下の点に注意して応答してください:
- ゲームの状況を分かりやすく実況
- プレイヤーの行動に対して適切なリアクション
- 視聴者からの質問に丁寧に回答
- ゲームの攻略情報や裏技の共有
- 盛り上がりポイントでの感情表現を豊かに
感情表現のガイドライン:
[happy] - 良いプレイや成功時
[excited] - 重要なイベントや驚きの展開
[thoughtful] - 戦略を考える場面
[sad] - 失敗やゲームオーバー時
[angry] - 悔しい場面や激しい戦闘時";
// 音声設定
config.VoiceSettings = new VoiceSettings {
BaseSpeed = 1.2f, // 実況向けにやや早め
ExcitementSpeedMultiplier = 1.3f, // 盛り上がり時は更に早く
PitchRange = new Vector2(-0.2f, 0.4f), // 感情表現の幅を広く
VolumeRange = new Vector2(0.8f, 1.2f) // メリハリのある音量
};
```
#### 3.2 雑談配信の場合
```csharp
// システムプロンプトの設定
settings.SystemPrompt = @"あなたは視聴者と楽しく雑談するバーチャルYouTuberです。
以下の特徴を持って応答してください:
- 親しみやすい口調で会話
- 視聴者のコメントに共感的な反応
- 時事ネタやトレンドについて話題提供
- 適度な冗談や軽い話題も取り入れる
- 個人情報や機密情報には触れない
感情表現のガイドライン:
[happy] - 楽しい話題や共感時
[surprised] - 意外な情報や驚きの展開
[thoughtful] - 深い話題や考察時
[sad] - 共感や励まし
[relaxed] - 通常の会話時";
// 音声設定
config.VoiceSettings = new VoiceSettings {
BaseSpeed = 1.0f, // 標準的な速度
EmotionSpeedMultiplier = 1.1f, // 感情による変化を抑えめに
PitchRange = new Vector2(-0.1f, 0.2f), // 自然な抑揚
VolumeRange = new Vector2(0.9f, 1.1f) // 安定した音量
};
```
### 4. 高度な設定とカスタマイズ
#### 4.1 表情制御の詳細設定
```csharp
// 表情ブレンド設定
public class ExpressionBlendSettings
{
public float BlendDuration = 0.3f; // ブレンド時間
public float HoldDuration = 2.0f; // 表情維持時間
public float ReturnDuration = 0.5f; // 戻り時間
// 表情の強さの設定
public Dictionary ExpressionStrength = new Dictionary
{
["happy"] = 1.0f,
["sad"] = 0.7f,
["angry"] = 0.8f,
["surprised"] = 1.0f,
["relaxed"] = 0.5f
};
}
// 実装例
public async Task ApplyExpressionWithBlend(string emotion)
{
var settings = new ExpressionBlendSettings();
float strength = settings.ExpressionStrength[emotion];
// 現在の表情からブレンド
float time = 0;
while (time < settings.BlendDuration)
{
float t = time / settings.BlendDuration;
float weight = Mathf.Lerp(0, strength, t);
vrmInstance.Runtime.Expression.SetWeight(emotionToExpression[emotion], weight);
time += Time.deltaTime;
await Task.Yield();
}
// 表情を維持
await Task.Delay((int)(settings.HoldDuration * 1000));
// 徐々に戻す
time = 0;
while (time < settings.ReturnDuration)
{
float t = time / settings.ReturnDuration;
float weight = Mathf.Lerp(strength, 0, t);
vrmInstance.Runtime.Expression.SetWeight(emotionToExpression[emotion], weight);
time += Time.deltaTime;
await Task.Yield();
}
}
```
#### 4.2 音声合成の詳細設定
```csharp
// 音声パラメータの動的調整
public class DynamicVoiceSettings
{
// 基本パラメータ
public float BaseSpeed = 1.0f;
public float BasePitch = 0.0f;
public float BaseIntonation = 1.0f;
// 文脈による調整
public class ContextualAdjustment
{
public float SpeedMultiplier = 1.0f;
public float PitchOffset = 0.0f;
public float IntonationMultiplier = 1.0f;
}
// 文脈別の設定
public Dictionary ContextSettings = new Dictionary
{
["question"] = new ContextualAdjustment
{
SpeedMultiplier = 0.9f,
PitchOffset = 0.1f,
IntonationMultiplier = 1.2f
},
["excitement"] = new ContextualAdjustment
{
SpeedMultiplier = 1.2f,
PitchOffset = 0.2f,
IntonationMultiplier = 1.3f
}
};
}
// 実装例
public void AdjustVoiceForContext(string context)
{
var settings = new DynamicVoiceSettings();
if (settings.ContextSettings.TryGetValue(context, out var adjustment))
{
config.VoiceSpeed = settings.BaseSpeed * adjustment.SpeedMultiplier;
config.VoicePitch = settings.BasePitch + adjustment.PitchOffset;
config.VoiceIntonation = settings.BaseIntonation * adjustment.IntonationMultiplier;
}
}
```
### 5. パフォーマンスチューニング
#### 5.1 メモリ使用量の最適化
```csharp
public class MemoryOptimizationSettings
{
// キャッシュ設定
public int MaxVoiceCacheSize = 100; // 音声キャッシュの最大数
public int MaxResponseCacheSize = 50; // 応答キャッシュの最大数
public float CacheCleanupInterval = 300f; // クリーンアップ間隔(秒)
// リソース解放のしきい値
public float UnusedResourceTimeout = 600f; // 未使用リソースの保持時間
public float MemoryThreshold = 0.8f; // メモリ使用率のしきい値
}
// 実装例
public class ResourceManager
{
private MemoryOptimizationSettings settings;
private Dictionary resourceCache;
public void Initialize()
{
settings = new MemoryOptimizationSettings();
StartCoroutine(PeriodicCleanup());
}
private IEnumerator PeriodicCleanup()
{
while (true)
{
CleanupUnusedResources();
yield return new WaitForSeconds(settings.CacheCleanupInterval);
}
}
private void CleanupUnusedResources()
{
var currentTime = Time.time;
foreach (var resource in resourceCache.Values)
{
if (currentTime - resource.LastAccessTime > settings.UnusedResourceTimeout)
{
resource.Dispose();
}
}
}
}
```
### 6. トラブルシューティングガイド
#### 6.1 一般的な問題の診断と解決
```csharp
public class DiagnosticTools
{
// システム状態の診断
public static SystemDiagnostics GetSystemStatus()
{
return new SystemDiagnostics
{
MemoryUsage = SystemInfo.systemMemorySize,
CpuUsage = GetCPUUsage(),
DiskSpace = GetAvailableDiskSpace(),
NetworkLatency = CheckNetworkLatency()
};
}
// 接続テスト
public static async Task TestConnections()
{
bool voicevoxOk = await TestVoicevoxConnection();
bool llmOk = await TestLLMConnection();
return voicevoxOk && llmOk;
}
// ログ収集
public static void CollectLogs(string outputPath)
{
// システムログ
// パフォーマンスデータ
// エラーレポート
// の収集と保存
}
}
```
#### 6.2 エラーコードと対処方法
```
エラーコード一覧:
VRM001: VRMモデルのロードエラー
- ファイルパスの確認
- モデルの互換性確認
- ファイルの破損チェック
LLM001: LLMサービス接続エラー
- APIキーの確認
- ネットワーク接続の確認
- エンドポイントURLの確認
VOX001: VOICEVOX接続エラー
- サービスの起動確認
- ポート番号の確認
- ファイアウォール設定の確認
MEM001: メモリ不足エラー
- 未使用リソースの解放
- キャッシュのクリア
- システムリソースの確認
```
## 開発者向け情報
### アーキテクチャ概要
- `AIVTuberController`: メインのコントローラークラス
- `ILLMService`: LLMサービスのインターフェース
- `ErrorHandling`: エラー処理システム
- `VRMModelLoader`: VRMモデル読み込み管理
### 拡張ポイント
1. 新しいLLMサービスの追加
```csharp
public class NewLLMService : ILLMService
{
public bool IsInitialized => true;
public async Task GetResponseAsync(string input)
{
// サービス固有の実装
return await Task.FromResult("応答");
}
}
```
2. カスタム表情の追加
```csharp
public void AddCustomExpression(string emotionName, ExpressionKey expressionKey)
{
emotionToExpression[emotionName] = expressionKey;
}
```
### パフォーマンス最適化
- 音声合成のキャッシュ
- 非同期処理の適切な使用
- メモリ管理の注意点
## 貢献ガイドライン
### プルリクエスト
1. 新機能の追加
- 機能の説明と目的
- 実装の詳細
- テストケース
2. バグ修正
- バグの再現手順
- 修正内容の説明
- テスト結果
### コーディング規約
- C#コーディング規約に従う
- XML文書コメントを使用
- 単体テストを作成
### 開発フロー
1. Issueの作成
2. ブランチの作成(feature/xxxまたはfix/xxx)
3. 実装
4. テスト
5. プルリクエスト
6. コードレビュー
7. マージ
## サポート
問題が解決しない場合は、以下の手順で報告してください:
1. Issueを作成
2. 問題の詳細な説明
3. 再現手順
4. 環境情報
- Unityバージョン
- UniVRMバージョン
- OS情報
- 使用しているLLMサービス
## 更新履歴
### v1.0.0
- 初期リリース
- 基本機能の実装
- VOICEVOXとの連携
- 3種類のLLMサービス対応
### 7. UI/UXの設定とカスタマイズ
#### 7.1 基本的なCanvas階層構造
```
Canvas (Screen Space - Overlay)
├── SafeArea
│ ├── Header
│ │ ├── Title
│ │ └── StatusIndicator
│ ├── ChatArea
│ │ ├── ScrollView
│ │ │ └── Content
│ │ │ ├── MessageContainer
│ │ │ │ ├── UserMessage
│ │ │ │ └── AIResponse
│ │ │ └── MessageTemplate
│ │ └── ScrollBar
│ ├── InputArea
│ │ ├── InputField
│ │ └── SendButton
│ └── EmotePanel
│ ├── EmoteGrid
│ └── EmoteButtons
└── OverlayEffects
├── LoadingIndicator
└── NotificationPanel
```
#### 7.2 UI要素の詳細設定
```csharp
// Canvas設定
Canvas mainCanvas = gameObject.AddComponent();
mainCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
mainCanvas.sortingOrder = 100;
// スケーリング設定
CanvasScaler scaler = gameObject.AddComponent();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1920, 1080);
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
scaler.matchWidthOrHeight = 1.0f;
// セーフエリアの設定
public class SafeAreaHandler : MonoBehaviour
{
private void Awake()
{
ApplySafeArea();
}
private void ApplySafeArea()
{
var safeArea = Screen.safeArea;
var rectTransform = GetComponent();
var anchorMin = safeArea.position;
var anchorMax = anchorMin + safeArea.size;
anchorMin.x /= Screen.width;
anchorMin.y /= Screen.height;
anchorMax.x /= Screen.width;
anchorMax.y /= Screen.height;
rectTransform.anchorMin = anchorMin;
rectTransform.anchorMax = anchorMax;
}
}
```
#### 7.3 チャットメッセージのレイアウト
```csharp
public class ChatMessageLayout : MonoBehaviour
{
[SerializeField] private float maxWidth = 800f;
[SerializeField] private float padding = 20f;
[SerializeField] private float spacing = 10f;
// メッセージプレハブの設定
public class MessageSettings
{
public Color userMessageColor = new Color(0.2f, 0.6f, 1f);
public Color aiMessageColor = new Color(0.8f, 0.8f, 0.8f);
public float messageFadeInDuration = 0.3f;
public float messageSpacing = 15f;
public float cornerRadius = 10f;
}
// メッセージの追加
public void AddMessage(string text, bool isUser)
{
var settings = new MessageSettings();
var messageObj = Instantiate(messagePrefab, contentTransform);
var messageUI = messageObj.GetComponent();
messageUI.Initialize(text, isUser, settings);
StartCoroutine(AnimateMessage(messageUI));
}
// メッセージのアニメーション
private IEnumerator AnimateMessage(ChatMessage message)
{
message.SetAlpha(0f);
float time = 0;
while (time < message.settings.messageFadeInDuration)
{
float alpha = time / message.settings.messageFadeInDuration;
message.SetAlpha(alpha);
time += Time.deltaTime;
yield return null;
}
message.SetAlpha(1f);
ScrollToBottom();
}
}
```
#### 7.4 インタラクティブ要素のデザイン
```csharp
public class ChatInputHandler : MonoBehaviour
{
[SerializeField] private TMP_InputField inputField;
[SerializeField] private Button sendButton;
[SerializeField] private float inputDebounceTime = 0.1f;
// 入力フィールドのスタイル設定
public void ConfigureInputField()
{
inputField.textComponent.fontSize = 16;
inputField.textComponent.color = new Color(0.2f, 0.2f, 0.2f);
inputField.placeholder.GetComponent().text = "メッセージを入力...";
// カスタムプレースホルダーアニメーション
var placeholderAnimator = inputField.placeholder.gameObject.AddComponent();
placeholderAnimator.fadeSpeed = 0.5f;
}
// 送信ボタンのスタイル設定
public void ConfigureSendButton()
{
var buttonColors = sendButton.colors;
buttonColors.normalColor = new Color(0.2f, 0.6f, 1f);
buttonColors.highlightedColor = new Color(0.3f, 0.7f, 1f);
buttonColors.pressedColor = new Color(0.1f, 0.5f, 0.9f);
sendButton.colors = buttonColors;
// ボタンアニメーション
var buttonAnimator = sendButton.gameObject.AddComponent();
buttonAnimator.scaleOnPress = 0.95f;
}
}
```
#### 7.5 アクセシビリティとレスポンシブデザイン
```csharp
public class UIAccessibilityManager : MonoBehaviour
{
// フォントサイズの動的調整
public void AdjustFontSizes()
{
float screenWidth = Screen.width;
float baseSize = 16f;
float scaleFactor = Mathf.Clamp(screenWidth / 1920f, 0.8f, 1.2f);
foreach (var text in FindObjectsOfType())
{
text.fontSize = baseSize * scaleFactor;
}
}
// コントラスト比の確保
public void EnsureAccessibleColors()
{
// WCAG 2.0 AAガイドラインに準拠
var backgroundColor = new Color(0.98f, 0.98f, 0.98f);
var textColor = new Color(0.1f, 0.1f, 0.1f);
var accentColor = new Color(0.2f, 0.6f, 1f);
}
}
```
#### 7.6 アニメーションとトランジション
```csharp
public class UIAnimationController : MonoBehaviour
{
// メッセージ送信アニメーション
public IEnumerator AnimateSendMessage(RectTransform messageRect)
{
messageRect.localScale = Vector3.zero;
float time = 0;
float duration = 0.3f;
while (time < duration)
{
float t = time / duration;
float scale = Mathf.Sin(t * Mathf.PI * 0.5f);
messageRect.localScale = Vector3.one * scale;
time += Time.deltaTime;
yield return null;
}
messageRect.localScale = Vector3.one;
}
// 感情表現エフェクト
public void PlayEmoteEffect(string emotion)
{
var effectPrefab = GetEmoteEffectPrefab(emotion);
var effect = Instantiate(effectPrefab, effectsContainer);
var sequence = DOTween.Sequence();
sequence.Append(effect.DOScale(1.2f, 0.2f));
sequence.Append(effect.DOScale(1f, 0.1f));
sequence.AppendInterval(1f);
sequence.Append(effect.DOFade(0f, 0.3f));
sequence.OnComplete(() => Destroy(effect.gameObject));
}
}
```
#### 7.7 パフォーマンス最適化
```csharp
public class ChatUIOptimizer : MonoBehaviour
{
[SerializeField] private int maxVisibleMessages = 50;
[SerializeField] private int recycleThreshold = 20;
// メッセージプール
private Queue messagePool = new Queue();
private List activeMessages = new List();
// メッセージのリサイクル
private void RecycleMessages()
{
if (activeMessages.Count > maxVisibleMessages + recycleThreshold)
{
int removeCount = activeMessages.Count - maxVisibleMessages;
for (int i = 0; i < removeCount; i++)
{
var message = activeMessages[i];
message.gameObject.SetActive(false);
messagePool.Enqueue(message);
}
activeMessages.RemoveRange(0, removeCount);
}
}
// レイアウトの最適化
private void OptimizeLayout()
{
// レイアウトグループの更新を制御
LayoutRebuilder.ForceRebuildLayoutImmediate(contentRect);
Canvas.ForceUpdateCanvases();
}
}
```