RPG で村人に話しかけて、三度目に同じ台詞が返ってきた瞬間、世界が急に書き割りに見えてしまう。会話の分岐を何本用意しても、この底が抜ける感覚はなかなか消えません。
NPC がプレイヤーの問いに自分の言葉で答え、ゲーム世界の状況を踏まえ、性格を保ったまま話す。Gemini API を Unity に繋ぐと、そこに手が届きます。実際に会話できる NPC を組み上げるまでを追いかけます。
Gemini API の基礎
Gemini は Google が開発した生成 AI モデルです。会話型の AI との大きな違いは、以下の点です:
- 高速な応答:レイテンシーが低く、ゲーム内での使用に適している
- マルチモーダル対応:テキストだけでなく、画像入力もサポート(NPCの顔表情認識など拡張可能)
- コスト効率:API の呼び出しが比較的低コスト
- キャラクター性の保持:システムプロンプトにより、NPC のキャラクター設定を一貫性を持って表現可能
Gemini API は REST API として提供されており、Unity の UnityWebRequest で簡単に統合できます。
セットアップ:Gemini API の導入
ステップ 1: Google Cloud コンソールでプロジェクト設定
- Google Cloud Console にアクセス
- 新しいプロジェクトを作成(例:「UnityGameAI」)
- Gemini API を有効化
- API キーを生成(認証情報 → API キー → 新規作成)
ステップ 2: Unity プロジェクトで API キーを安全に管理
API キーは絶対に GitHub や公開リポジトリに含めてはいけません。Unity では ScriptableObject を使うか、環境変数で管理します:
using UnityEngine;
using System;
[CreateAssetMenu(menuName = "AI/Gemini Config")]
public class GeminiConfig : ScriptableObject
{
[SerializeField]
private string apiKey;
public string ApiKey => apiKey;
}セキュリティ重要事項:本番環境では、ゲーム内で API キーを直接保持するのではなく、バックエンドサーバーを経由して API を呼び出すようにしてください。
ステップ 3: Gemini API クライアントの実装
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using System.Text;
using Newtonsoft.Json;
public class GeminiAPIClient : MonoBehaviour
{
[SerializeField]
private GeminiConfig config;
private const string API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent";
[System.Serializable]
public class GeminiRequest
{
public Content[] contents;
}
[System.Serializable]
public class Content
{
public Role role;
public Part[] parts;
}
[System.Serializable]
public class Part
{
public string text;
}
[System.Serializable]
public enum Role
{
user,
model
}
[System.Serializable]
public class GeminiResponse
{
public Candidate[] candidates;
}
[System.Serializable]
public class Candidate
{
public Content content;
}
public IEnumerator SendMessage(string userMessage, System.Action<string> onResponse)
{
var request = new GeminiRequest
{
contents = new[]
{
new Content
{
role = Role.user,
parts = new[] { new Part { text = userMessage } }
}
}
};
string jsonData = JsonConvert.SerializeObject(request);
byte[] postData = Encoding.UTF8.GetBytes(jsonData);
string url = $"{API_URL}?key={config.ApiKey}";
var webRequest = new UnityWebRequest(url, "POST");
webRequest.uploadHandler = new UploadHandlerRaw(postData);
webRequest.downloadHandler = new DownloadHandlerBuffer();
webRequest.SetRequestHeader("Content-Type", "application/json");
yield return webRequest.SendWebRequest();
if (webRequest.result == UnityWebRequest.Result.Success)
{
var response = JsonConvert.DeserializeObject<GeminiResponse>(webRequest.downloadHandler.text);
string responseText = response.candidates[0].content.parts[0].text;
onResponse?.Invoke(responseText);
}
else
{
Debug.LogError($"Gemini API Error: {webRequest.error}");
onResponse?.Invoke("エラーが発生しました");
}
}
}NPC 会話システムの実装
ステップ 1: NPC キャラクターベースクラスの設計
using UnityEngine;
using System.Collections.Generic;
public class NPCCharacter : MonoBehaviour
{
[SerializeField]
private string npcName;
[SerializeField]
private string characterPrompt; // キャラクター設定
[SerializeField]
private GeminiAPIClient apiClient;
[SerializeField]
private GameObject dialogueUI; // UI パネル
private List<(string role, string message)> conversationHistory;
private void Start()
{
conversationHistory = new List<(string, string)>();
}
public void StartConversation()
{
conversationHistory.Clear();
dialogueUI.SetActive(true);
}
public void SendMessage(string userMessage)
{
// 会話履歴に追加
conversationHistory.Add(("user", userMessage));
// API 呼び出し用のコンテキスト構築
string contextMessage = BuildConversationContext(userMessage);
StartCoroutine(apiClient.SendMessage(contextMessage, OnResponseReceived));
}
private string BuildConversationContext(string userMessage)
{
// キャラクター設定 + 会話履歴 + 新しいメッセージを組み立て
var sb = new System.Text.StringBuilder();
sb.AppendLine(characterPrompt);
sb.AppendLine("\n【会話履歴】");
foreach (var (role, message) in conversationHistory)
{
sb.AppendLine($"{(role == "user" ? "プレイヤー" : npcName)}:{message}");
}
return sb.ToString();
}
private void OnResponseReceived(string response)
{
// NPC の応答を会話履歴に追加
conversationHistory.Add(("npc", response));
// UI に表示
DisplayDialogue(response);
}
private void DisplayDialogue(string text)
{
// DialogueUI スクリプトで表示処理を実行
var dialoguePanel = dialogueUI.GetComponent<DialoguePanel>();
dialoguePanel.ShowMessage(npcName, text);
}
}ステップ 2: キャラクター設定(システムプロンプト)の設計
Gemini API の力を最大限に発揮するには、詳細で一貫性のあるキャラクター設定が重要です:
あなたは「フロディア」という老賢者の魔法使いです。
年齢は150歳で、東の塔に住んでいます。
性格:穏やか、思慮深い、時々ユーモアを交える。
口癖:「ふむふむ」「わかるかね」
このファンタジー世界では:
- 魔法は四大元素(火・水・風・土)で構成される
- 人間とエルフは共存しているが、時々緊張がある
- 黒い魔法は禁止されている
会話のルール:
1. 常に日本語で、キャラクターらしく話してください
2. ゲーム世界の知識を使って応答してください
3. プレイヤーの質問に丁寧に答えてください
4. 長すぎない応答(1~3文程度)をしてください
例)
プレイヤー:「炎の魔法を習いたいのですが」
フロディア:「ふむふむ、良い心がけじゃな。炎の魔法は火元素の力を扱うもの。まずは基礎を学ぶ必要があるわかるかね?わしが教えてやろう。」
このような詳細なプロンプトを設定することで、NPC が単なる情報応答機ではなく、ゲーム世界の登場人物として機能するようになります。
ステップ 3: 会話履歴の管理と最適化
長い会話になると、API に送信するトークン数が増加し、コストと レイテンシーが増加します。会話履歴を適切に管理することが重要です:
private const int MAX_HISTORY_PAIRS = 5; // 最大 5 ターン分保持
private string BuildConversationContext(string userMessage)
{
var sb = new System.Text.StringBuilder();
sb.AppendLine(characterPrompt);
sb.AppendLine("\n【会話履歴】");
// 古い履歴を削除し、最新の N 個のみ保持
int startIndex = Mathf.Max(0, conversationHistory.Count - MAX_HISTORY_PAIRS * 2);
for (int i = startIndex; i < conversationHistory.Count; i++)
{
var (role, message) = conversationHistory[i];
sb.AppendLine($"{(role == "user" ? "プレイヤー" : npcName)}:{message}");
}
sb.AppendLine($"プレイヤー:{userMessage}");
return sb.ToString();
}実践的な活用例
例 1: 店主 NPC による動的会話
public class MerchantNPC : NPCCharacter
{
[SerializeField]
private List<ItemData> inventory;
protected override string BuildCharacterPrompt()
{
var sb = new System.Text.StringBuilder();
sb.AppendLine("あなたは「マルコ」という商人です。");
sb.AppendLine("性格:商売好き、机敏、時々ぼったくりを試みる");
sb.AppendLine("\n現在の在庫:");
foreach (var item in inventory)
{
sb.AppendLine($"- {item.name}:{item.price} ゴールド");
}
sb.AppendLine("\nプレイヤーの質問に応じて、商品を勧めてください。");
return sb.ToString();
}
}例 2: クエスト NPC による状況理解
あなたは「エライア」という騎士団長です。
現在、ゴブリン族が北の森で勢力を増しており、それが大きな問題になっています。
もしプレイヤーが:
- 「クエストをください」と言ったら、ゴブリン退治クエストを提案
- 「既にゴブリンを倒しました」と言ったら、感謝し、報酬を提示
- 「ゴブリンについて知ってますか?」と聞いたら、ゲーム世界での背景を説明
常にこのコンテキストを保ちながら、プレイヤーのリクエストに応答してください。
レスポンス処理と UI 統合
Gemini API からの応答を Unity の UI に表示するためには、以下の処理が必要です:
public class DialoguePanel : MonoBehaviour
{
[SerializeField]
private Text characterNameText;
[SerializeField]
private Text dialogueText;
[SerializeField]
private CanvasGroup canvasGroup;
[SerializeField]
private Button continueButton;
private void Start()
{
continueButton.onClick.AddListener(HideDialogue);
canvasGroup.alpha = 0f;
}
public void ShowMessage(string characterName, string message)
{
characterNameText.text = characterName;
// テキストを段階的に表示(タイプライター風エフェクト)
StartCoroutine(TypeMessage(message));
}
private IEnumerator TypeMessage(string message)
{
canvasGroup.alpha = 1f;
dialogueText.text = "";
foreach (char c in message)
{
dialogueText.text += c;
yield return new WaitForSeconds(0.05f); // 1 文字あたり 50ms
}
continueButton.interactable = true;
}
private void HideDialogue()
{
StartCoroutine(FadeOut());
}
private IEnumerator FadeOut()
{
while (canvasGroup.alpha > 0)
{
canvasGroup.alpha -= Time.deltaTime;
yield return null;
}
}
}プロンプト設計のベストプラクティス
1. キャラクター属性を明確に記述
あなたは「セラ」というエルフの魔法使いです。
年齢:250歳(エルフにしては若い)
見た目:銀色の長髪、蒼い瞳
性格:好奇心旺盛、時々短気、自信に満ちている
話し方:敬語は使わない、「~だね」「~かな」という語尾を多用
2. 禁止事項を明示
以下のことは絶対にしないでください:
- プレイヤーに対し、攻撃的または不適切な言葉を使う
- ゲーム世界外の話題(実世界のニュースなど)に言及する
- プレイヤーが入力したメッセージの内容をそのまま繰り返す
- 3 文以上の長い応答をする(ゲーム内のテンポが落ちるため)
3. 応答形式を指定
応答時は、以下の形式で返してください:
【感情】:笑顔 / 真顔 / 怒り顔 / 悲しみ顔 など
【応答】:実際の台詞
例:
【感情】:笑顔
【応答】:おお、やっと来たか!待ってたぞ。
このような形式指定により、NPC のアニメーション制御も自動化できます。
API コスト最適化
Gemini API の利用は従量課金です。ゲーム開発では、以下の工夫によりコストを抑制できます:
1. 会話履歴の圧縮
会話が長くなると、API に送信するトークン数が増加します:
private void CompressConversationHistory()
{
if (conversationHistory.Count > MAX_HISTORY_PAIRS * 2)
{
// 古いエントリを削除
conversationHistory.RemoveRange(0, 2);
}
}2. キャッシング
同じクエリへの応答は、キャッシュから返すようにします:
private Dictionary<string, string> responseCache = new();
public IEnumerator SendMessage(string userMessage, System.Action<string> onResponse)
{
if (responseCache.TryGetValue(userMessage, out string cached))
{
onResponse?.Invoke(cached);
yield break;
}
// API 呼び出し...
}3. ローカルフォールバック
インターネット接続がない場合や API が利用不可の場合のため、シンプルな応答テンプレートを用意:
private string GetFallbackResponse(string userMessage)
{
if (userMessage.Contains("こんにちは"))
return "やあ、元気にしてるか?";
else if (userMessage.Contains("クエスト"))
return "クエストについてなら、掲示板を確認してくれ。";
else
return "そうだね…(何か考えている)";
}マルチモーダル対応:顔表情認識への応用
Gemini API はテキストだけでなく、画像入力もサポートしています。例えば、NPC の顔をゲーム内で生成し、プレイヤーの表情に反応させることも可能です:
public IEnumerator SendMessageWithImage(Texture2D image, string userMessage, System.Action<string> onResponse)
{
// 画像を Base64 にエンコード
byte[] imageData = image.EncodeToPNG();
string base64Image = System.Convert.ToBase64String(imageData);
// API リクエストに画像を含める
// (詳細な実装は省略)
}これによって、プレイヤーの表情とゲーム状況から、NPC が自動的に最適な応答を生成することが可能です。
ネットワークとセキュリティの考慮事項
1. 通信の暗号化
// HTTPS を使用(UnityWebRequest は自動的に HTTPS をサポート)
webRequest.url = "https://generativelanguage.googleapis.com/...";2. API キーの保護
本番環境では、クライアント側で API キーを直接保持しないようにします:
// ❌ 危険
string apiKey = "直接埋め込まれたキー";
// ✅ 推奨
// バックエンドサーバーを経由してリクエスト
StartCoroutine(CallBackendNPCAPI(userMessage, onResponse));トラブルシューティング
よくある問題と解決策
Q: API の応答が遅い
- A: 会話履歴を制限する、キャッシング機能を導入する
Q: NPC の応答がキャラクターと合致していない
- A: システムプロンプトをより詳細に、具体例を多く含めて修正
Q: API キーが漏洩した
- A: Google Cloud Console で即座に無効化し、新しいキーを生成
まとめ
Gemini API を Unity に統合することで、以下のメリットが実現します:
- 動的な NPC 会話:スクリプトされた台詞の枠を超えた自由な会話が可能
- ゲーム世界への没入感向上:NPC がゲーム世界の文脈を理解した応答をする
- 開発効率の向上:膨大な会話分岐をスクリプト化する必要がなくなる
- プレイヤーの選択肢拡大:任意の質問に対応できるため、プレイヤーがより自由に行動できる
- リプレイ価値の向上:毎回異なる会話内容により、ゲーム体験が多様化
これは、ゲーム開発における AI 活用の一つの典型例です。Gemini API を上手く活用することで、プレイヤーにとって本当に「知性のある NPC」との交流がゲーム内で実現できるのです。
本記事で紹介した基本的なセットアップと簡単な NPC 会話の実装に加えて、本記事の続きとなるプレミアム記事では、マルチモーダル AI を用いた NPC 顔表情の自動生成、ストリーミング応答による高速な対話実装、複雑なキャラクター会話フローの完全コード集、およびプロダクション環境でのスケーリング戦略を詳しく解説しています。