digitalcontent.jp マンガ×図解解説メディア

Gemini API×Unityで作るカフェ経営ゲーム第6回:AI口コミとSNS自動生成

編集

まなびコミック編集部 監修


Unity×AI開発講座【第6回】

『Gemini & ChatGPT × Unity』で本格カフェ経営シミュレーションを作る!

LLM API(WebAPI)を直接連携し、ゲーム内状況に応じた動的口コミ&擬似SNS(InstaCafé)を構築しよう!


第6回の実装を開始

「あらかじめ固定された定型文の口コミじゃ物足りない…!プレイヤーの店舗レイアウトやメニュー、待ち時間に応じて人間のような生々しい口コミやSNS投稿が生成されたら面白そう!」

現代のAIゲーム開発における最大の強みは、**「生成AI(Gemini API / OpenAI API)」をゲームエンジン内に直接組み込める点**にあります。UnityからWebAPIを呼び出し、ゲーム内の状況(待ち時間の長さ、内装の映え度、ドリンクの価格)をプロンプトとして送信することで、毎プレイごとに異なるリアルな口コミや擬似SNS「InstaCafé」の投稿を自動生成できます。

第6回となる本記事では、APIキーの安全な保持方法、UnityWebRequestによるHTTP POST通信、JSONパース処理、async/awaitによる非同期通信、そして通信失敗時のフォールバック処理までを完全解説します!

1. はじめに(第6回で実装するLLM API連携&InstaCaféシステム)

ゲーム内に生成AI APIを組み込むことで、プレイヤーの個性に合わせた世界に1つだけの動的レビューやSNS投稿を無限に生み出すことが可能になります。

従来型の固定メッセージ(パターン化)

「美味しかった!」「待ち時間が長かった」など数パターンのテキストランダム表示。数時間プレイすると飽きが生じる。

LLM API直結の動的SNS(本講座の方式)

「ネオン看板の前で撮ったラテが激映え!」「待ち時間20分は最悪…」など、店内の内装や状況にリアル追従するAI口コミ。

本章をマスターすれば、Unityと外部Web APIを繋ぐ実践的なネットワークプログラム技術が身につき、経営ゲームのプレイ体験を飛躍的に向上させることができます。

2. Step 1: LLM API(Gemini API / OpenAI API)のキー取得と安全な管理設計

まずはGoogle AI Studio(Gemini API)またはOpenAI Platform(ChatGPT API)から通信用のAPIキーを発行し、Unityプロジェクト内で安全に保持する仕組みを作ります。

APIキー発行と安全保持の手順

ステップ 操作内容 セキュリティ上の注意点
1. APIキー取得 Google AI Studio等で【Get API Key】を発行 キーは秘密情報です。第三者に絶対に漏洩させないでください
2. ScriptableObject管理 ★重要 `APIConfigSO.cs` を作成し、インスペクターからキーを入力 C#コードに直接ベタ書き(ハードコーディング)するとGitHub等に漏洩するリスクを防止できます
3. .gitignore設定 `APIConfigSO.asset` を.gitignoreに追加して公開から除外 リポジトリへキーを含めない設定を徹底します

初心者補足:WebAPI / HTTP POSTとは?

WebAPIとは、インターネット越しに外部サーバー(Google/OpenAI)にデータ処理を依頼する仕組みです。HTTP POST通信を使ってプロンプト(質問)を送ると、AIが生成した回答テキストが返ってきます。

\ 通信スクリプトをAIに自動生成させよう! /


Step 2: LLMReviewGeneratorスクリプト生成へ

3. Step 2: AIプロンプトによる「LLMReviewGenerator.cs」通信スクリプトの自動生成

`UnityWebRequest` を使用し、ゲーム内状況(待ち時間・味の評価・内装映え度)をJSONプロンプト化してGemini APIへ送信する通信スクリプトを作成します。

コピペで使える!API通信コード生成プロンプト

Unity 2022.3 (C#) で動作する「LLMReviewGenerator.cs」を作成してください。

【要件】
1. Gemini API(https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent)へ UnityWebRequest で HTTP POST 送信するクラス。
2. 入力パラメータ:
   - waitTime (float), tasteRating (float), decorAppeal (float), drinkName (string)
3. プロンプト指示文:
   「あなたはカフェを訪れた客です。以下の体験データに基づき、擬似SNS『InstaCafé』用の投稿文(30文字以内)とハッシュタグ、いいね数予測(100〜5000)を以下のJSONフォーマットのみで返答してください。
   フォーマット:{"username":"ユーザー名", "comment":"本文", "likeCount":1200}」
4. POSTデータのヘッダー設定(Content-Type: application/json)とJSONボディの組み立て。
5. 通信成功時、受信したJSONをパースしてイベント(OnReviewGenerated)を発火させてください。

生成された C# スクリプト:LLMReviewGenerator.cs

using System;
using System.Text;

using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;

[Serializable]
public class ReviewData
{
    public string username;
    public string comment;
    public int likeCount;
}

public class LLMReviewGenerator : MonoBehaviour
{
    [SerializeField] private string apiKey = "YOUR_GEMINI_API_KEY";
    private string apiUrl = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=";

    public event Action<ReviewData> OnReviewGenerated;

    public async Task RequestReviewAsync(float waitTime, float tasteRating, float decorAppeal, string drinkName)
    {
        string prompt = $"カフェ訪問データ:ドリンク={drinkName}, 待ち時間={waitTime}秒, 味評価=★{tasteRating}, 内装映え度={decorAppeal}。\n" +
                       "この体験に基づき、擬似SNS用の投稿データを次のJSON形式のみで作成してください。\n" +
                       "{\"username\":\"@ユーザー名\", \"comment\":\"感想コメント(30文字以内)\", \"likeCount\":いいね数(100-5000)}";

        string jsonRequestBody = $"{{\"contents\":[{{\"parts\":[{{\"text\":\"{prompt.Replace("\"", "\\\"").Replace("\n", "\\n")}\"}}]}}]}}";

        using (UnityWebRequest request = new UnityWebRequest(apiUrl + apiKey, "POST"))
        {
            byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonRequestBody);
            request.uploadHandler = new UploadHandlerRaw(bodyRaw);
            request.downloadHandler = new DownloadHandlerBuffer();
            request.SetRequestHeader("Content-Type", "application/json");

            var operation = request.SendWebRequest();
            while (!operation.isDone) await Task.Yield();

            if (request.result == UnityWebRequest.Result.Success)
            {
                string responseText = request.downloadHandler.text;
                ParseAndEmitReview(responseText);
            }
            else
            {
                Debug.LogWarning($"API通信エラー: {request.error}。フォールバック投稿を表示します。");
                EmitFallbackReview(drinkName, tasteRating);
            }
        }
    }

    private void ParseAndEmitReview(string jsonResponse)
    {
        try
        {
            // Geminiレスポンス内から生成テキストを取り出す(簡易抽出処理)
            int startIndex = jsonResponse.IndexOf("{\"username\"");
            int endIndex = jsonResponse.LastIndexOf("}") + 1;
            if (startIndex != -1 && endIndex > startIndex)
            {
                string cleanJson = jsonResponse.Substring(startIndex, endIndex - startIndex);
                ReviewData review = JsonUtility.FromJson<ReviewData>(cleanJson);
                OnReviewGenerated?.Invoke(review);
                return;
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"JSONパース例外: {e.Message}");
        }
        EmitFallbackReview("スペシャルラテ", 4.0f);
    }

    private void EmitFallbackReview(string drinkName, float rating)
    {
        ReviewData fallback = new ReviewData
        {
            username = "@CafeLover_Local",
            comment = $"{drinkName}美味しかった!お店の雰囲気も最高✨",
            likeCount = UnityEngine.Random.Range(300, 1500)
        };
        OnReviewGenerated?.Invoke(fallback);
    }
}

4. Step 3: APIレスポンス(JSON)のパースと「InstaCafé」UIへのリアルタイム反映

APIから返ってきたレビューデータを受け取り、ゲーム内の擬似SNS画面「InstaCafé」のフィードUI(ユーザー名・コメント・いいね数・グラフレベル)を動的に作成・更新します。

InstaCafé UI の構築とバインド手順

1

SNSフィードパネルの準備

【UI】➔【Panel】を作成し、名前を「InstaCafePanel」にします。内部に「UsernameText」「CommentText」「LikeCountText」のTextMeshProオブジェクトを配置します。

2

フォロワー数の自動加算ロジック

「いいね数」に比例して店舗のフォロワー数(Follower Increase)が増加する計算ロジック(`followers += likeCount / 10;`)を組み込みます。

InstaCafeUI.cs スクリプト

using UnityEngine;
using TMPro;

public class InstaCafeUI : MonoBehaviour
{
    [SerializeField] private LLMReviewGenerator generator;
    [SerializeField] private TMP_Text usernameText;
    [SerializeField] private TMP_Text commentText;
    [SerializeField] private TMP_Text likeCountText;
    [SerializeField] private TMP_Text followerCountText;

    private int totalFollowers = 1200;

    private void OnEnable()
    {
        if (generator != null) generator.OnReviewGenerated += DisplayNewPost;
    }

    private void OnDisable()
    {
        if (generator != null) generator.OnReviewGenerated -= DisplayNewPost;
    }

    private void DisplayNewPost(ReviewData review)
    {
        usernameText.text = review.username;
        commentText.text = review.comment;
        likeCountText.text = $"{review.likeCount:N0} Likes";

        // フォロワー数の増加更新
        int newFollowers = review.likeCount / 5;
        totalFollowers += newFollowers;
        if (followerCountText != null)
        {
            followerCountText.text = $"フォロワー数: {totalFollowers:N0}人 ( +{newFollowers}/日 )";
        }

        Debug.Log($"[InstaCafé] 新着投稿: {review.username} 「{review.comment}」");
    }
}

\ ゲーム描画を止めない「非同期通信」を極めよう! /


Step 4: エラー処理&非同期化へ

5. Step 4: API通信の非同期化(async/await)とタイムアウト・エラーハンドリング

外部API通信は応答に1〜3秒程度の時間がかかります。ゲームのフレームレート(FPS)を下げたりフリーズさせたりしないよう、`async/await` による非同期通信とフォールバック処理(通信失敗時の予備メッセージ表示)を徹底します。

バックグラウンド非同期処理(async/await)

APIからのレスポンスを待つ間も客AIやタイマーは止まらず60FPSで快適に動作し続けます。

フォールバック処理(Fallback)

オフライン時やAPI通信エラー(404/500等)が発生しても、ゲームが止まらず自動で定型文投稿に切り替わります。

6. まとめ & よくある質問 (FAQ)

お疲れ様でした!第6回では、最先端の生成AI(Gemini API / ChatGPT API)をUnity内に組み込み、リアルタイムな口コミや擬似SNS「InstaCafé」を全自動生成する次世代のゲームシステムを完成させました!

次回:第7回「エリアマップ・競合分析&マルチエージェント最適化」へ!

エリアマップ画面での競合分析(ライバル店の弱点・シェア算出)と、大量の客AI(50体以上)を軽量に動かすオブジェクトプール最適化を解説します!


第7回記事を読む(準備中)

よくある質問 (FAQ)

Q. API通信の利用料金が高くなる心配はありませんか?

Gemini APIの `gemini-1.5-flash` モデルには個人開発に十分な「無料枠(Free Tier)」が用意されています。また、毎フレーム送るのではなく「客が退店した瞬間」など限定的に呼び出すことでコストをほぼ抑えられます。

Q. APIから意図しないテキストや不適切な言葉が返ってくるのを防ぐには?

プロンプト内に「不適切な表現は除外してください」と明記することに加え、Gemini API側のセーフティ設定(Safety Settings)を【BLOCK_MEDIUM_AND_ABOVE】以上に指定することで防止できます。

サクッと読めて超わかりやすい!

【無料】マンガで全部わかる!

図解&ストーリー形式だから活字が苦手でもスルスル理解!全話を一覧からチェックしよう

マンガ全話一覧を見る(無料)
マンガ全話一覧へ 次話を読む