Unity × AI 時代の最速開発ワークフロー(2025年12月版)

〜 Tic-Tac-Toe(3目並べ)を題材に、Unity 開発の“現代標準プロセス”を完全解説 〜

本記事では Unity × AI × Cursor(AI コードエディタ)を組み合わせた、2025年時点の最新・最速開発ワークフロー を、実例として 3目並べ(Tic-Tac-Toe) を題材にわかりやすくまとめています。

2024 年までの Unity 開発手順は「人間がすべてのコードを書き、UI を手動で並べる」というものでした。

しかし現在は、AI が UI 生成・コード雛形・バグ修正・リファクタリングまで担当する時代に大きく変わりつつあります。

この記事では次の内容を解説します:

  • Unity プロジェクト構築の最新ベストプラクティス
  • Cursor(AI エディタ)を中心にした AI 時代の標準フロー
  • 3目並べ UI の自動生成と Editor 拡張の作成
  • ブレークポイントを含むデバッグ方法
  • 初学者・学習者向けの AI の使い方ガイド
  • 応用として他ゲームに一般化できる「AI 開発者マップ」

すべて 2025年12月時点の環境に対応した最新版です。


1. 準備するもの(2025年12月版 確定)

本記事は以下の組み合わせで最適に機能します。

● Unity 6000.2.x

  • Editor 拡張に安定
  • UI Toolkit+UGUI 併用が標準
  • C# 10 ベース

● Cursor(最新版)

  • GPT-5.1 Codex
  • Composer 1(Claude 系)
  • Auto 設定でプロジェクト全体の把握も可能
  • Unity デバッグ(ブレークポイント)に正式対応

● External Script Editor(重要設定)

Unity → Preferences → External Tools

External Script Editor: Cursor (Internal)
External Script Editor Args:
"$(ProjectPath)" "$(File)"

※ 引用符を絶対に付ける。

付けないと AI がプロジェクト全体を認識できないため、動作が大きく狂います。

● VSCode(任意)

すでに Cursor がブレークポイント対応したため、必須ではありません。


2. Unity × Cursor × AI の開発フロー(2025年版)

AI 時代の標準フローは次の 5 ステップで完結します。


Step 1. 要件を自然言語でまとめる

最初に ゲームの要件を簡潔に文章で説明します。

例:

「3×3 のマスを持つ 3目並べ。X/O を交互に置く。勝敗判定。リセットボタン。UI は Canvas ベース。」

この段階でコードを書く必要はありません。


Step 2. Cursor に「UI 自動生成」を依頼する

プロンプト例(推奨):

Unity 6000.2 で動く 3目並べ用の UI を Editor 拡張で自動生成したい。
Canvas 上に 3×3 の Button、Reset ボタン、結果表示 TextMeshPro を配置する EditorWindow を作成してください。
UI は全て RectTransform 座標で配置すること。
実行結果として Hierarchy が以下のようになること:

Canvas
 ├── Board
 │     ├── Cell(0,0)
 │     ├── …
 │     └── Cell(2,2)
 ├── ResetButton
 └── ResultText

Cursor は即座に

  • EditorWindow コード
  • UI 生成関数
  • Prefab 作成部分まで出力します。

Step 3. 生成コードを実行して UI を一発生成

AI の出力コードをそのまま貼り付け、Unity で EditorWindow を開いて「Generate」ボタンを押すだけで UI が完成します。

これは「手で配置」「Anchor を調整」といった時間のかかる作業を完全に排除します。


Step 4. ゲームロジックを AI に依頼

同様に Cursor に以下を依頼します:

この UI に対して、3目並べのロジックを追加したい。
BoardManager / Cell / GameManager の 3スクリプト構成で作って。
交互入力、勝敗判定、リセットを実装して。

すると Cursor が 自動的に既存ファイルを読み込み、必要箇所だけを編集して出力します。

手修正が必要な場合も、AI が差分パッチを即座に生成します。


Step 5. Cursor でブレークポイントを使ったデバッグ

2025年現在、Cursor は Unity デバッグに完全対応しています。

  • Attach to Unity
  • ブレークポイント
  • ローカル変数表示
  • Call Stack
  • 例外時の自動解説

VSCode と同等(または以上)の機能を、AI による解説付きで利用できます。

デバッグ手順は:

  1. Unity:Play(Debug Mode)
  2. Cursor:左側の「デバッグ」から Attach to Unity
  3. ブレークポイントで停止
  4. AI が原因を説明してくれる

これが「AI 時代のデバッグ」です。


3. 実際に使える Editor 拡張(完成版)

  • ファイル名例:Assets/Editor/TicTacToeBoardGenerator.cs
  • メニュー:Tools/TicTacToe/Generate Board
  • 実行すると Hierarchy はこうなります:
Canvas
 ├ Board
 │   ├ Cell_0_0
 │   ├ …
 │   └ Cell_2_2
 ├ ResetButton
 └ ResultText

ではコードです。

using UnityEngine;
using UnityEditor;
using UnityEngine.UI;
using TMPro;
using UnityEditor.SceneManagement;
using UnityEngine.SceneManagement;

public class TicTacToeBoardGenerator : EditorWindow
{
    private const string CanvasName = "Canvas";
    private const string BoardName = "Board";
    private const string ResetButtonName = "ResetButton";
    private const string ResultTextName = "ResultText";

    private const int Rows = 3;
    private const int Columns = 3;

    [MenuItem("Tools/TicTacToe/Generate Board")]
    public static void ShowWindow()
    {
        var window = GetWindow<TicTacToeBoardGenerator>("TicTacToe Board Generator");
        window.minSize = new Vector2(320, 120);
    }

    private void OnGUI()
    {
        GUILayout.Label("Tic-Tac-Toe UI Generator", EditorStyles.boldLabel);
        GUILayout.Space(8);

        EditorGUILayout.HelpBox(
            "現在開いているシーンに 3×3 のボード UI、Reset ボタン、Result テキストを自動生成します。",
            MessageType.Info
        );

        GUILayout.Space(8);

        if (GUILayout.Button("Generate 3x3 Board", GUILayout.Height(32)))
        {
            GenerateBoard();
        }
    }

    private void GenerateBoard()
    {
        // Undo グループ開始
        Undo.IncrementCurrentGroup();
        int group = Undo.GetCurrentGroup();

        Canvas canvas = FindOrCreateCanvas();
        RectTransform canvasRect = canvas.GetComponent<RectTransform>();

        // 既存の Board / Reset / Result を削除して再生成
        Transform existingBoard = canvas.transform.Find(BoardName);
        if (existingBoard != null)
        {
            Undo.DestroyObjectImmediate(existingBoard.gameObject);
        }

        Transform existingReset = canvas.transform.Find(ResetButtonName);
        if (existingReset != null)
        {
            Undo.DestroyObjectImmediate(existingReset.gameObject);
        }

        Transform existingResult = canvas.transform.Find(ResultTextName);
        if (existingResult != null)
        {
            Undo.DestroyObjectImmediate(existingResult.gameObject);
        }

        // Board 作成
        GameObject boardGO = new GameObject(BoardName, typeof(RectTransform), typeof(GridLayoutGroup));
        Undo.RegisterCreatedObjectUndo(boardGO, "Create Board");
        boardGO.transform.SetParent(canvas.transform, false);

        RectTransform boardRect = boardGO.GetComponent<RectTransform>();
        boardRect.anchorMin = new Vector2(0.5f, 0.5f);
        boardRect.anchorMax = new Vector2(0.5f, 0.5f);
        boardRect.pivot = new Vector2(0.5f, 0.5f);
        boardRect.sizeDelta = new Vector2(600f, 600f);
        boardRect.anchoredPosition = Vector2.zero;

        GridLayoutGroup grid = boardGO.GetComponent<GridLayoutGroup>();
        grid.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
        grid.constraintCount = Columns;
        grid.cellSize = new Vector2(180f, 180f);
        grid.spacing = new Vector2(10f, 10f);
        grid.childAlignment = TextAnchor.MiddleCenter;

        // セル生成
        for (int row = 0; row < Rows; row++)
        {
            for (int col = 0; col < Columns; col++)
            {
                GameObject cellGO = CreateCell(boardGO.transform, row, col);
                Undo.RegisterCreatedObjectUndo(cellGO, "Create Cell");
            }
        }

        // ResetButton 作成
        GameObject resetGO = CreateResetButton(canvas.transform);
        Undo.RegisterCreatedObjectUndo(resetGO, "Create Reset Button");

        // ResultText 作成
        GameObject resultGO = CreateResultText(canvas.transform);
        Undo.RegisterCreatedObjectUndo(resultGO, "Create Result Text");

        // シーン Dirty フラグ
        EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());

        Undo.CollapseUndoOperations(group);

        EditorUtility.DisplayDialog("Tic-Tac-Toe", "UI の自動生成が完了しました。", "OK");
    }

    private Canvas FindOrCreateCanvas()
    {
        Canvas canvas = FindObjectOfType<Canvas>();

        if (canvas == null)
        {
            // 新規 Canvas 作成
            GameObject canvasGO = new GameObject(CanvasName, typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
            canvas = canvasGO.GetComponent<Canvas>();
            canvas.renderMode = RenderMode.ScreenSpaceOverlay;

            CanvasScaler scaler = canvasGO.GetComponent<CanvasScaler>();
            scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
            scaler.referenceResolution = new Vector2(1920, 1080);

            Undo.RegisterCreatedObjectUndo(canvasGO, "Create Canvas");
        }

        return canvas;
    }

    private GameObject CreateCell(Transform parent, int row, int col)
    {
        string cellName = $"Cell_{row}_{col}";

        GameObject cellGO = new GameObject(cellName, typeof(RectTransform), typeof(Image), typeof(Button));
        cellGO.transform.SetParent(parent, false);

        RectTransform rect = cellGO.GetComponent<RectTransform>();
        rect.localScale = Vector3.one;

        Image img = cellGO.GetComponent<Image>();
        img.color = new Color(1f, 1f, 1f, 0.9f); // 少し透過した白など

        Button btn = cellGO.GetComponent<Button>();
        btn.transition = Selectable.Transition.ColorTint;

        // テキスト(X / O を表示する TextMeshProUGUI)
        GameObject textGO = new GameObject("Text", typeof(RectTransform), typeof(TextMeshProUGUI));
        textGO.transform.SetParent(cellGO.transform, false);

        RectTransform textRect = textGO.GetComponent<RectTransform>();
        textRect.anchorMin = Vector2.zero;
        textRect.anchorMax = Vector2.one;
        textRect.offsetMin = Vector2.zero;
        textRect.offsetMax = Vector2.zero;

        TextMeshProUGUI tmp = textGO.GetComponent<TextMeshProUGUI>();
        tmp.text = "";
        tmp.alignment = TextAlignmentOptions.Center;
        tmp.fontSize = 96f;

        return cellGO;
    }

    private GameObject CreateResetButton(Transform canvasTransform)
    {
        GameObject resetGO = new GameObject(ResetButtonName, typeof(RectTransform), typeof(Image), typeof(Button));
        resetGO.transform.SetParent(canvasTransform, false);

        RectTransform rect = resetGO.GetComponent<RectTransform>();
        rect.anchorMin = new Vector2(1f, 0f);
        rect.anchorMax = new Vector2(1f, 0f);
        rect.pivot = new Vector2(1f, 0f);
        rect.anchoredPosition = new Vector2(-50f, 50f);
        rect.sizeDelta = new Vector2(220f, 80f);

        Image img = resetGO.GetComponent<Image>();
        img.color = new Color(0.2f, 0.4f, 0.9f, 1f);

        Button btn = resetGO.GetComponent<Button>();
        btn.transition = Selectable.Transition.ColorTint;

        // テキスト
        GameObject textGO = new GameObject("Text", typeof(RectTransform), typeof(TextMeshProUGUI));
        textGO.transform.SetParent(resetGO.transform, false);

        RectTransform textRect = textGO.GetComponent<RectTransform>();
        textRect.anchorMin = Vector2.zero;
        textRect.anchorMax = Vector2.one;
        textRect.offsetMin = Vector2.zero;
        textRect.offsetMax = Vector2.zero;

        TextMeshProUGUI tmp = textGO.GetComponent<TextMeshProUGUI>();
        tmp.text = "Reset";
        tmp.alignment = TextAlignmentOptions.Center;
        tmp.fontSize = 40f;
        tmp.color = Color.white;

        return resetGO;
    }

    private GameObject CreateResultText(Transform canvasTransform)
    {
        GameObject resultGO = new GameObject(ResultTextName, typeof(RectTransform), typeof(TextMeshProUGUI));
        resultGO.transform.SetParent(canvasTransform, false);

        RectTransform rect = resultGO.GetComponent<RectTransform>();
        rect.anchorMin = new Vector2(0.5f, 1f);
        rect.anchorMax = new Vector2(0.5f, 1f);
        rect.pivot = new Vector2(0.5f, 1f);
        rect.anchoredPosition = new Vector2(0f, -50f);
        rect.sizeDelta = new Vector2(800f, 100f);

        TextMeshProUGUI tmp = resultGO.GetComponent<TextMeshProUGUI>();
        tmp.text = ""; // 初期状態は空
        tmp.alignment = TextAlignmentOptions.Center;
        tmp.fontSize = 60f;

        return resultGO;
    }
}

簡単な使い方

  1. Assets/Editor/ フォルダを作成
  2. 上記コードを TicTacToeBoardGenerator.cs として保存
  3. Unity に戻ると自動コンパイル
  4. メニューからTools → TicTacToe → Generate Board
  5. ボタンを押すと、現在のシーンに UI 一式が自動生成されます。

4. よくあるトラブルと解決策(2025年版)

● Unity からスクリプトは開くが、Cursor がプロジェクトを認識しない

原因の 95% はこれ:

"$(ProjectPath)" "$(File)"

引用符が無いと絶対に動きません。


● C# 補完が壊れる

ほぼ「旧 OmniSharp 拡張の残骸」が原因。

Cursor → Extensions から

  • ms-dotnettools.csharp
  • muhammad-sammy.csharp

の 2つは削除。


● ブレークポイントが無視される

  • Debug Mode になっていない
  • Attach to Unity をしていない
  • .csproj 生成設定が OFF
  • Extension 競合

が主要因。


5. AI 時代の学習方法(初学者向けガイド)

この手順は学習効率を最大化します。

  1. 理解できなくても最初は AI にコードを作らせる
  2. AI が作ったコードを「読む」ことに集中
  3. わからない部分をそのまま AI に質問
  4. バグが出たら「原因を説明して」「どこが間違っている?」と尋ねる
  5. 小規模改造を自分でやってみる
  6. また AI に答え合わせを依頼する
  7. 最後に「同じものをゼロから自力で再実装」する

人間の作業は「書く」ではなく、「理解」「判断」「検証」に変わります。


6. 応用:他のゲームにもそのまま転用可能

  • オセロ
  • 2048
  • マインスイーパー
  • ソリティア
  • ブロック崩し
  • タワーディフェンス
  • 2D RPG の UI 自動生成

同じプロセスを適用するだけで、全ジャンルで AI 補助開発が可能です。


7. まとめ

AI によって Unity 開発は次の時代に入りました。

  • UI は手で配置しない(Editor 拡張で生成)
  • コードは書くより「AI に依頼して修正する」
  • ブレークポイントで AI が原因を説明
  • Unity × Cursor でプロジェクト全体を一括把握
  • 初学者でも大型ゲームの基礎部分が作れる

このワークフローを習得しておけば、

ゲーム開発の速度は 3〜10 倍になります

訪問数 1 回, 今日の訪問数 3回

AI,AI,Unity,生成AI

Posted by hidepon