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

Gemini×Unityで作るカフェ経営ゲーム第4回:メニュー調合と原価率計算UI

編集

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


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

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

コーヒー豆・抽出量・ミルク泡立て率の調整から、販売価格・原価率・味の評価を動的算出させよう!


第4回の実装を開始

「自分だけのオリジナルコーヒーメニューを作りたい!でも、調合パラメータに応じて原価率や客の評価が自動計算される経営ゲームの仕組みはどう作る?」

カフェ経営シミュレーションの売上と満足度を左右するのが、魅力的なドリンクメニューの開発機能です。抽出量や豆の選択、ミルクフォームの割合などのパラメータをUIで細かく調合し、リアルタイムに販売価格・原価率・味の評価スコアが連動するシステムを構築します。

第4回となる本記事では、クラフトUI(Slider / Toggle / Dropdown)の動的同期、経営評価アルゴリズムのC#自動生成、そして開発したレシピをScriptableObjectとして動的保存・ラインナップ追加する仕組みまでを完全解説します!

1. はじめに(第4回で実装するメニュー開発&クラフトシステム)

メニュー開発画面では、プレイヤーが豆のブレンドやシロップ量を微調整し、コストとクオリティのトレードオフをバランス調整する「経営的な選択」を提供します。

固定レシピ(自由度の低い構造)

あらかじめ用意されたメニュー(ブレンドコーヒー=400円)を選ぶだけで、オリジナルの試行錯誤やパラメータ調整の楽しさがない。

パラメータ連動クラフト(本講座の方式)

抽出量、豆のグレード、フォームミルク比率等をUIで自在に調合。原価率や味の評価、最適価格がリアルタイム計算される本格システム。

完成したメニューは『DrinkRecipeSO』としてデータ化され、今後の客AIの注文ラインナップや売上計算ロジックにそのまま流用できる柔軟な設計にします。

2. Step 1: メニュー開発パラメータと経営シミュレーション計算式の設計

まずはドリンクを構成する要素と、それらから「原価」「適正価格」「味の評価(★)」を導き出す計算式を設計します。

レシピ開発パラメータと評価算出アルゴリズム

パラメータ名 入力範囲 / 選択肢 原価・評価への影響ルール
1. 豆の種類(Bean Type) エチオピア / コロンビア / ブラジル エチオピア(高級:原価高・風味評価最高)、ブラジル(標準:原価安)
2. 抽出量(Shot Volume) 10ml 〜 60ml (Slider) 量に応じて豆コストが増加。35ml付近が黄金比率(味評価が最高化)
3. ミルクフォーム率(Milk Foam) ★重要 0% 〜 100% (Slider) ミルクコストが加算。フォーム率70%前後でラテとしての黄金比を達成
4. シロップ追加(Syrup) バニラ / キャラメル / ヘーゼルナッツ 1プッシュごとに原価+30円、若い客層からの評価スコアがアップ

初心者補足:原価率(Margin)の計算式とは?

`原価率(%) = (合計原価 ÷ 販売価格) × 100` で求められます。飲食経営では原価率20%〜30%前後が理想的とされており、原価率が高すぎると利益が出ず、低すぎると客の満足度が下がります。

\ 評価計算アルゴリズムをAIに自動生成させよう! /


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

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

設計した計算式に基づき、UIの操作に合わせて即座に原価率・推奨価格・味の評価スコアを算出してイベント発火する `RecipeDeveloper.cs` を生成させます。

コピペで使える!メニュー開発ロジック生成プロンプト

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

【要件】
1. ドリンク調合パラメータ:
   - beanType (int) : 0:エチオピア(原価120円), 1:コロンビア(原価90円), 2:ブラジル(原価70円)
   - espressoVolume (float) : 10〜60ml
   - milkFoamRatio (float) : 0〜100%
   - syrupPushes (int) : 0〜3プッシュ (1個あたり原価30円)
   - sellingPrice (int) : 設定販売価格 (初期値650円)
2. 計算ロジック `RecalculateRecipe()`:
   - 合計原価 (Cost) = 豆原価 + (抽出量 * 1.5) + (ミルク率 * 1.2) + (シロップ * 30)
   - 原価率 (CostRatio) = (合計原価 / 販売価格) * 100
   - 味の評価 (TasteScore: 1.0〜5.0) = 抽出量35ml、ミルク率70%を基準とし、理想値からの離れ具合で減点算出。
3. リアルタイム通知用イベント:
   - System.Action<int, float, float> OnRecipeUpdated; // (原価, 原価率, 味評価)

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

using System;
using UnityEngine;

public class RecipeDeveloper : MonoBehaviour
{
    public string recipeName = "スペシャルカフェラテ";
    public int beanType = 0; // 0:エチオピア, 1:コロンビア, 2:ブラジル
    public float espressoVolume = 35f; // ml
    public float milkFoamRatio = 70f;  // %
    public int syrupPushes = 1;
    public int sellingPrice = 650;

    // 算出結果
    public int totalCost { get; private set; }
    public float costRatio { get; private set; }
    public float tasteScore { get; private set; }

    public event Action<int, float, float> OnRecipeUpdated;

    private void Start()
    {
        RecalculateRecipe();
    }

    public void RecalculateRecipe()
    {
        // 豆原価の判定
        int beanBaseCost = beanType switch
        {
            0 => 120, // エチオピア
            1 => 90,  // コロンビア
            2 => 70,  // ブラジル
            _ => 90
        };

        // 合計原価の計算
        totalCost = Mathf.RoundToInt(beanBaseCost + (espressoVolume * 1.5f) + (milkFoamRatio * 1.2f) + (syrupPushes * 30));

        // 原価率の計算
        costRatio = sellingPrice > 0 ? ((float)totalCost / sellingPrice) * 100f : 0f;

        // 味の評価スコア算出(理想値:抽出量35ml, ミルク70%)
        float espressoPenalty = Mathf.Abs(espressoVolume - 35f) * 0.05f;
        float milkPenalty = Mathf.Abs(milkFoamRatio - 70f) * 0.03f;
        tasteScore = Mathf.Clamp(5.0f - (espressoPenalty + milkPenalty), 1.0f, 5.0f);

        // イベント発火でUIに即座通知
        OnRecipeUpdated?.Invoke(totalCost, costRatio, tasteScore);
    }

    public void SetEspressoVolume(float val) { espressoVolume = val; RecalculateRecipe(); }
    public void SetMilkFoamRatio(float val) { milkFoamRatio = val; RecalculateRecipe(); }
    public void SetSellingPrice(int price) { sellingPrice = price; RecalculateRecipe(); }
    public void SetBeanType(int type) { beanType = type; RecalculateRecipe(); }
    public void SetSyrupPushes(int pushes) { syrupPushes = pushes; RecalculateRecipe(); }
}

4. Step 3: uGUI(スライダー・ドロップダウン・数値テキスト)の構築とリアルタイム同期

スライダーを動かした瞬間に原価率のメーターや★評価がリアルタイムに変動するクラフトUI画面を構築します。

UI要素のバインド手順

1

Slider / Dropdown の作成

【UI】➔【Slider】を2つ作成(抽出量・ミルク用)。【UI】➔【Dropdown – TMP】を作成(豆の種類選択用)。

2

onValueChanged イベントの紐付け

Sliderのインスペクター内【On Value Changed】に `RecipeDeveloper` の `SetEspressoVolume` や `SetMilkFoamRatio` を割り当てます。

RecipeUIController.cs スクリプト

using UnityEngine;
using TMPro;
using UnityEngine.UI;

public class RecipeUIController : MonoBehaviour
{
    [SerializeField] private RecipeDeveloper developer;
    [SerializeField] private TMP_Text costText;
    [SerializeField] private TMP_Text marginText;
    [SerializeField] private TMP_Text ratingText;
    [SerializeField] private Image marginGaugeBar;

    private void OnEnable()
    {
        if (developer != null) developer.OnRecipeUpdated += UpdateUI;
    }

    private void OnDisable()
    {
        if (developer != null) developer.OnRecipeUpdated -= UpdateUI;
    }

    private void UpdateUI(int totalCost, float costRatio, float tasteScore)
    {
        costText.text = $"原価: ¥{totalCost}";
        marginText.text = $"原価率: {costRatio:F1}%";
        ratingText.text = $"味の評価: ★{tasteScore:F1}";

        // 原価率ゲージの色変化(30%以下なら緑、40%以上は赤警告)
        if (marginGaugeBar != null)
        {
            marginGaugeBar.fillAmount = costRatio / 100f;
            marginGaugeBar.color = costRatio > 40f ? Color.red : Color.green;
        }
    }
}

\ 作成したオリジナルドリンクを保存しよう! /


Step 4: レシピのScriptableObject保存へ

5. Step 4: オリジナルレシピの保存とScriptableObject自動動的生成

開発ボタンを押した際、現在調合中のデータを `DrinkRecipeSO` インスタンスとして動的に生成(`ScriptableObject.CreateInstance`)し、店舗のメニューリストへ追加する処理を実装します。

DrinkRecipeSO(レシピデータ構造)

ドリンク名、販売価格、原価、味評価、調合パラメータを保持するデータアセット。

実行時動的生成(CreateInstance)

ゲームプレイ中にプレイヤーが新開発したメニューをメモリ上で即座にデータ化してメニュー追加します。

DrinkRecipeSO.cs & レシピ保存処理

using UnityEngine;

[CreateAssetMenu(fileName = "NewDrinkRecipe", menuName = "Cafe/Drink Recipe")]
public class DrinkRecipeSO : ScriptableObject
{
    public string drinkName;
    public int price;
    public int cost;
    public float tasteRating;

    // 調合詳細データ
    public int beanType;
    public float espressoVolume;
    public float milkFoamRatio;
    public int syrupPushes;
}

// RecipeDeveloper内に追記する保存関数
public void SaveRecipeAsAsset()
{
    DrinkRecipeSO newRecipe = ScriptableObject.CreateInstance<DrinkRecipeSO>();
    newRecipe.drinkName = recipeName;
    newRecipe.price = sellingPrice;
    newRecipe.cost = totalCost;
    newRecipe.tasteRating = tasteScore;
    newRecipe.beanType = beanType;
    newRecipe.espressoVolume = espressoVolume;
    newRecipe.milkFoamRatio = milkFoamRatio;
    newRecipe.syrupPushes = syrupPushes;

    Debug.Log($"新メニュー「{newRecipe.drinkName}」(価格:¥{newRecipe.price} / 評価:★{newRecipe.tasteRating:F1})を登録しました!");
}

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

お疲れ様でした!第4回では、カフェシミュレーションの奥深さを決定づける「パラメータ連動クラフトUI」「原価・評価計算アルゴリズム」「ScriptableObject動的保存」を完成させました!

次回:第5回「第一人称アクション&ミニゲーム実装」へ!

第一人称(FPS)視点でエスプレッソマシンの圧力ゲージ修理やトングを使ったドーナツ陳列ミニゲームを実装します!


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

よくある質問 (FAQ)

Q. 開発したレシピをプロジェクトフォルダ(.assetファイル)として保存したい場合は?

Unityエディタ実行時であれば `#if UNITY_EDITOR` 内で `AssetDatabase.CreateAsset(newRecipe, “Assets/Recipes/new.asset”);` を実行することでアセットファイル化可能です。ビルド後の実機プレイ時はリスト管理またはJSON化して保存します。

Q. スライダーの数値を変更してもUIテキストが更新されません。

Sliderのインスペクター内【On Value Changed】に設定したメソッドが「Dynamic float(一番上の動的メソッド)」として選択されているか確認してください。

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

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

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

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