Unity×AI開発講座【第5回】
『Gemini & ChatGPT × Unity』で本格カフェ経営シミュレーションを作る!
第一人称視点での手元アクション、圧力ゲージ修理ミニゲーム、ドーナツ陳列判定を実装しよう!
「経営シミュレーションに臨場感を足したい!でも、第一人称視点への切り替えや、手元で行うインタラクティブなミニゲームはどう作れば良い?」
カフェ経営ゲームのゲーム性をグッと高めるのが、プレイヤー自らが手元で行う「アクティビティ」です。機器故障時の圧力ゲージ修理ミニゲームや、トングを使ったドーナツ陳列精度判定を盛り込むことで、単なる数値管理にとどまらない没入感抜群の体験をプレイヤーに提供できます。
第5回となる本記事では、三人称視点(経営)と第一人称視点(手元アクション)の視点切り替え、タイミング判定修理ミニゲーム、トング陳列判定、そして故障時の煙・蒸気エフェクト(Particle System)制御までを完全解説します!
目次
1. はじめに(第5回で実装するプレイヤーアクション&ミニゲーム)
シミュレーションゲームにおいて、店舗の全体を見渡すマクロ視点と、プレイヤーが直接手元で作業するミクロ視点を使い分けることで、ゲームの緩急とメリハリが生まれます。
マクロ視点のみ(作業的になりやすい構造)
ボタンを押すだけで故障が自動治癒、陳列が自動完了。見守るだけになり、プレイヤーの直接的な「介入感」が薄れてしまう。
FPS手元アクション(本講座の方式)
故障中のマシンに近寄り[E]キーで第一人称視点へダイブ。タイミング合わせで修理し、トングでドーナツを配置する直感アクション。
本章では、視点遷移のカメラ制御から、ゲージタイミング判定ロジック、トングでの位置精度判定、視覚効果(煙・湯気)の連動までを一気に構築します。
2. Step 1: 三人称(クォータービュー)と第一人称(FPS手元操作)のカメラ切り替え設計
経営画面(Overview Camera)と、マシン修理・陳列時の手元画面(First Person Camera)をスムーズに切り替えるカメラマネージャーを設計します。
視点切り替えとカーソル制御の手順
| モード | カメラ位置 / 視点 | カーソル状態・操作方法 |
|---|---|---|
| 1. 店舗経営モード(Overview) | 店舗俯瞰(クォータービュー:Y:8, Z:-8) | `Cursor.lockState = CursorLockMode.None`(UIや家具を選択・配置) |
| 2. 手元操作モード(FPS/Action) ★重要 | 故障機器の直前(手元クローズアップ) | [E]キーで突入。ミニゲームUIのタイミング判定またはドラッグ操作を実行 |
初心者補足:カメラの優先度(Priority)切り替えとは?
Cinemachineなどのカメラアセットを使用する場合、各カメラの Priority(優先度)の値を変更するだけで、Unityが自動的に滑らかな補間(ブレンド移動)で視点を移動してくれます。
\ タイミング判定ミニゲームコードをAIに自動生成させよう! /
3. Step 2: AIプロンプトによる「MachineRepairMinigame.cs」スクリプトの自動生成
圧力ゲージの針が振り子のように往復運動し、緑色の適正領域(Target Zone)に重なった瞬間に[E]キーを押すタイミグ判定ミニゲームを構築します。
コピペで使える!修理ミニゲーム生成プロンプト
Unity 2022.3 (C#) で動作する「MachineRepairMinigame.cs」を作成してください。
【要件】
1. 圧力ゲージ(0〜100%)の針の値 `currentValue` が Sine波(Mathf.PingPong)で毎フレーム往復運動させてください。
2. 緑色の成功領域(targetMin: 60%, targetMax: 80%)を定義してください。
3. プレイヤーが [E] キーを押した際の判定:
- `currentValue` が targetMin 〜 targetMax の間にある場合 ➔ 成功イベント(OnRepairSuccess)を発火、煙パーティクルを停止し、蒸気エフェクトを一瞬再生。
- 範囲外の場合 ➔ 失敗イベント(OnRepairFailed)を発火、揺れ(Camera Shake)演出を起こし再度挑戦。
4. UI表示用に Slider(針の位置)と Image(ターゲット領域の色)の参照を設定してください。
生成された C# スクリプト:MachineRepairMinigame.cs
using UnityEngine;
using UnityEngine.UI;
public class MachineRepairMinigame : MonoBehaviour
{
[SerializeField] private Slider needleSlider;
[SerializeField] private ParticleSystem smokeParticle; // 故障中の煙
[SerializeField] private ParticleSystem steamParticle; // 修理成功時の蒸気
[SerializeField] private GameObject minigameUI;
public float needleSpeed = 2.0f;
public float targetMin = 0.6f; // 60%
public float targetMax = 0.8f; // 80%
private bool isPlaying = false;
private float currentVal = 0f;
public System.Action OnSuccess;
public void StartMinigame()
{
isPlaying = true;
minigameUI.SetActive(true);
if (smokeParticle != null) smokeParticle.Play();
}
private void Update()
{
if (!isPlaying) return;
// 針の往復運動(0.0 〜 1.0)
currentVal = Mathf.PingPong(Time.time * needleSpeed, 1.0f);
if (needleSlider != null) needleSlider.value = currentVal;
// タイミング判定ボタン入力
if (Input.GetKeyDown(KeyCode.E) || Input.GetKeyDown(KeyCode.Space))
{
CheckTiming();
}
}
private void CheckTiming()
{
if (currentVal >= targetMin && currentVal <= targetMax)
{
// 成功
isPlaying = false;
minigameUI.SetActive(false);
if (smokeParticle != null) smokeParticle.Stop();
if (steamParticle != null) steamParticle.Play();
Debug.Log("修理成功!機器が正常復旧しました。");
OnSuccess?.Invoke();
}
else
{
// 失敗
Debug.LogWarning("修理失敗!タイミングがズレています。");
}
}
}
4. Step 3: トング操作とドーナツケース陳列判定(DonutPlacementController.cs)の実装
プレイヤーがトングでドーナツを掴み、ショーケース内のトレイへ配置する手元アクションを実装します。位置が中央に近いほど高得点(Placement Accuracy)になります。
ドラッグ&レイキャスト移動
マウスカーソルの位置に3Dモデル(ドーナツ+トング)を追従移動させ、クリックで落下配置させます。
陳列精度(Placement Accuracy)計算
トレイの中心座標との距離(Vector3.Distance)を測定し、100%に近いほど店舗映えスコアがアップします。
DonutPlacementController.cs スクリプト
using UnityEngine;
public class DonutPlacementController : MonoBehaviour
{
[SerializeField] private Transform trayCenterTarget; // 理想の陳列中心
[SerializeField] private GameObject donutHoldingObject;
[SerializeField] private LayerMask showcaseLayer;
private bool isHolding = true;
private void Update()
{
if (!isHolding) return;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit, 10f, showcaseLayer))
{
donutHoldingObject.transform.position = hit.point + new Vector3(0, 0.05f, 0);
// クリックで配置決定
if (Input.GetMouseButtonDown(0))
{
PlaceDonut(hit.point);
}
}
}
private void PlaceDonut(Vector3 placePosition)
{
isHolding = false;
float distance = Vector3.Distance(placePosition, trayCenterTarget.position);
// 精度(Placement Accuracy)の算出(0.2m以内で100%)
float accuracy = Mathf.Clamp01(1.0f - (distance / 0.2f)) * 100f;
Debug.Log($"ドーナツ陳列完了!精度 (Placement Accuracy): {accuracy:F1}%");
// 店舗評価へ還元
if (accuracy > 80f)
{
GameManager.Instance?.SetRating(GameManager.Instance.currentRating + 0.1f);
}
}
}
\ パーティクルとSEで臨場感を極めよう! /
5. Step 4: パーティクルエフェクト(Particle System)と音響(SE)の演出制御
機器の故障時・修理完了時・コーヒー抽出時に、Unityの **Particle System** を起動・停止させる制御スクリプトを追加し、視覚的な臨場感を最大化します。
パーティクル制御スクリプトの組み込み
エスプレッソマシンのプレハブ配下に【Effects】➔【Particle System】を追加。名前を「SmokeParticle」にし、黒っぽい煙がモクモク昇るよう設定します。
`particle.Play();` でエフェクト開始、`particle.Stop();` で停止制御を行います。同時に `AudioSource.PlayOneShot(repairSE);` でカチャンという修理音を鳴らすとカンペキです。
6. まとめ & よくある質問 (FAQ)
第5回では、単なる経営数値の管理にとどまらない「FPS手元アクション」「タイミング判定修理ミニゲーム」「ドーナツ陳列精度判定」「パーティクル演出」を完成させました!
次回:第6回「Gemini / ChatGPT API連携!リアルタイム口コミ&InstaCafé」へ!
UnityからLLM API(WebAPI)を呼び出し、店内の評価・内装に応じたリアルな口コミや擬似SNS投稿(InstaCafé)を動的生成させます!
よくある質問 (FAQ)
Q. 手元操作モードに入った際、プレイヤーが歩き回ってカメラから外れてしまいます。
手元モード突入時にプレイヤー移動スクリプト(`CharacterController` や `NavMeshAgent`)の `.enabled = false;` に設定して移動物理をロックしてください。
Q. ミニゲームの針の動きを途中で速くしたり難易度を変えたい場合は?
`needleSpeed` の数値を変数化し、ゲームの進行度(店舗レベル等)に応じて `needleSpeed += 0.5f;` と加算することで難易度調整が可能です。