【AI×Unity】ChatGPT新モデルo1を検証!1回の回答でゲームを作れるのか?ChatGPT-4oとの対決の結果は…

AIゲームクリエイターズ

はじめに

2024年12月5日、OpenAIはChatGPTの最新モデル「ChatGPT o1」をリリースしました

ChatGPTo1は、深い思考と、高い推論能力を持っていると言われています。

そうなると気になるのが、ゲーム制作に使うとどうなるのか?ということ

そこで今回は、Unityのゲーム開発に活用した場合の、ChatGPTo1の実力を検証していきます。

元の動画

この記事は、下記の動画がベースになっています

詳細は動画をご覧ください。

今回の検証内容

新モデル o1 の実力を測るために、今回は従来モデルである「ChatGPT4o」との比較をしてみたいと思います。

何で比較をするかと言うと

「1回の回答だけでUnityでテトリスが作れるか対決」

で検証しようと思います!

今回インプットするプロンプト

Unityでテトリスを作りたいので、作り方を教えてください。
・完成までの、手順、設定、必要なオブジェクト、スクリプトとその中身、を余すことなく、一度の回答で教えてください。
・テトリミノはプレハブを使用せずスクリプト上で生成するようにしてください
・Unityプロジェクトの作成までは済んでいます(バージョン2024.3.45、2D-URP)

ChatGPT 4o の結果

ChatGPT4oの回答例

Unityでテトリスを作成する方法を、手順と必要な設定、スクリプトを具体的に説明します。以下は、最小限のテトリスを構築するためのガイドです。


完成までの手順

  1. プロジェクト設定
    • プロジェクト名: Tetris
    • テンプレート: 2D-URP
    • シーン作成: Assetsフォルダで右クリック → Create > Scene → シーン名をMainに変更し、開く。
  2. 画面設定
    • カメラの設定を調整:
      • Position: (0, 0, -10)
      • Background: 任意の背景色(黒やグレーがおすすめ)。
    • Orthographic Size: 10(プレイエリア全体が見えるように調整)。
  3. グリッド構築
    • テトリスのゲーム盤をスクリプト上で構築する。
  4. テトリミノ生成
    • スクリプトでテトリミノ(4つのブロックで構成される形状)を動的に生成。
  5. 移動と回転
    • プレイヤーがテトリミノを操作できるスクリプトを作成。
  6. ライン消去とスコア
    • ラインが揃った場合に消去するロジックを実装。
  7. ゲームオーバー処理
    • テトリミノが上部に到達した場合にゲームを終了。

必要なオブジェクト

  1. ゲームオブジェクト:
    • GameController: ゲームロジックを管理。
  2. テキストUI: スコア表示。

スクリプト

1. GameController.cs

ゲーム全体のロジックを管理します。

using UnityEngine;

public class GameController : MonoBehaviour
{
    public static int width = 10;  // グリッド幅
    public static int height = 20; // グリッド高さ
    public static Transform[,] grid = new Transform[width, height];

    void Start()
    {
        SpawnTetromino();
    }

    public void SpawnTetromino()
    {
        GameObject tetromino = new GameObject("Tetromino");
        Tetromino t = tetromino.AddComponent<Tetromino>();
        t.Initialize();
    }

    public static bool InsideGrid(Vector2 position)
    {
        return (position.x >= 0 && position.x < width && position.y >= 0);
    }

    public static void AddToGrid(Transform block)
    {
        Vector2 position = block.position.Round();
        grid[(int)position.x, (int)position.y] = block;
    }

    public static void CheckLines()
    {
        for (int y = height - 1; y >= 0; y--)
        {
            if (IsLineComplete(y))
            {
                DeleteLine(y);
                MoveLinesDown(y);
            }
        }
    }

    private static bool IsLineComplete(int y)
    {
        for (int x = 0; x < width; x++)
        {
            if (grid[x, y] == null)
                return false;
        }
        return true;
    }

    private static void DeleteLine(int y)
    {
        for (int x = 0; x < width; x++)
        {
            Destroy(grid[x, y].gameObject);
            grid[x, y] = null;
        }
    }

    private static void MoveLinesDown(int y)
    {
        for (int i = y; i < height; i++)
        {
            for (int x = 0; x < width; x++)
            {
                if (grid[x, i] != null)
                {
                    grid[x, i - 1] = grid[x, i];
                    grid[x, i] = null;
                    grid[x, i - 1].position += Vector3.down;
                }
            }
        }
    }
}

2. Tetromino.cs

テトリミノの生成と動作。

using UnityEngine;

public class Tetromino : MonoBehaviour
{
    private Vector3 rotationPoint;
    private float fallTime = 1.0f;
    private float previousTime;

    public void Initialize()
    {
        // テトリミノ形状をスクリプトで生成
        CreateShape();
        transform.position = new Vector3(5, 20, 0);
    }

    void Update()
    {
        HandleInput();
        HandleFall();
    }

    private void HandleInput()
    {
        if (Input.GetKeyDown(KeyCode.LeftArrow))
        {
            transform.position += Vector3.left;
            if (!ValidMove())
                transform.position -= Vector3.left;
        }
        else if (Input.GetKeyDown(KeyCode.RightArrow))
        {
            transform.position += Vector3.right;
            if (!ValidMove())
                transform.position -= Vector3.right;
        }
        else if (Input.GetKeyDown(KeyCode.DownArrow))
        {
            transform.position += Vector3.down;
            if (!ValidMove())
            {
                transform.position -= Vector3.down;
                AddToGrid();
            }
        }
        else if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            transform.RotateAround(transform.TransformPoint(rotationPoint), Vector3.forward, 90);
            if (!ValidMove())
                transform.RotateAround(transform.TransformPoint(rotationPoint), Vector3.forward, -90);
        }
    }

    private void HandleFall()
    {
        if (Time.time - previousTime > fallTime)
        {
            transform.position += Vector3.down;
            if (!ValidMove())
            {
                transform.position -= Vector3.down;
                AddToGrid();
            }
            previousTime = Time.time;
        }
    }

    private void CreateShape()
    {
        // L字型テトリミノを例として生成
        GameObject block = GameObject.CreatePrimitive(PrimitiveType.Cube);
        block.transform.SetParent(transform);
        block.transform.localPosition = Vector3.zero;

        for (int i = 0; i < 3; i++)
        {
            GameObject newBlock = GameObject.CreatePrimitive(PrimitiveType.Cube);
            newBlock.transform.SetParent(transform);
            newBlock.transform.localPosition = new Vector3(i, 0, 0);
        }
    }

    private void AddToGrid()
    {
        foreach (Transform child in transform)
        {
            GameController.AddToGrid(child);
        }
        GameController.CheckLines();
        Destroy(gameObject);
    }

    private bool ValidMove()
    {
        foreach (Transform child in transform)
        {
            Vector2 pos = child.position.Round();
            if (!GameController.InsideGrid(pos))
                return false;

            if (GameController.grid[(int)pos.x, (int)pos.y] != null)
                return false;
        }
        return true;
    }
}

3. VectorExtensions.cs

位置を丸める拡張メソッド。

using UnityEngine;

public static class VectorExtensions
{
    public static Vector2 Round(this Vector2 vector)
    {
        return new Vector2(Mathf.Round(vector.x), Mathf.Round(vector.y));
    }
}

これで、シンプルなテトリスが動作します。

結果

スクリプトの時点でエラーが出てしまい「失敗」となりました。

ChatGPT o1(新モデル) の回答

ChatGPT o1 の回答例

以下に、Unityでテトリスを作るための詳細な手順、設定、必要なオブジェクト、そしてスクリプト例を示します。
特記事項:

  • Unityバージョンは2024.3.45 (2D-URP) と想定
  • プロジェクト作成は済みで空のシーンからスタートする想定
  • テトリミノはPrefabsを使用せず、スクリプト上でGameObjectとSpriteRenderer等を生成する方針
  • 必要な素材(スプライト等)は、Unity内で簡易的に用意するか、Solidカラーの四角スプライトを用いる想定
  • 完成イメージ:
    • 画面上部からテトリミノが出現し、下へ落ちていく
    • プレイヤーは左右移動、回転、落下加速を可能
    • ブロックがラインを埋めたら削除し、スコアを加算
    • テトリミノ生成、ゲームオーバー判定まで行う

以下に流れと詳細を示します。

全体的な流れ

  1. シーン準備
    • カメラ設定:2Dカメラを用意し、テトリスフィールド全体が写るようにする。
    • URPの場合は特に不要だが、バックグラウンドを黒や任意の色に設定。
  2. ゲームフィールド設計
    • テトリスの基本的なフィールドサイズは横10マス、縦20マス程度。
    • これをGridシステム(2次元配列)で管理する。
    • 画面上に見える座標を(0,0)を左下とするか中央とするかで処理が変わるが、ここでは左下が(0,0)とし、x方向に10マス、y方向に20マスとする。
    • 各ブロックは1×1マスの正方形スプライトを使用。
  3. テトリミノの定義
    • T字、I字、O字、L字、J字、S字、Z字の7種のテトリミノを定義する。
    • 各テトリミノは4つのブロックで構成され、その形状を相対的な座標で定義する。
    • 例:Tミノ:[(0,0), (1,0), (2,0), (1,1)] のような形で配列で保持。
  4. テトリミノの生成
    • スクリプト上でGameObject生成し、そこに複数のSpriteRenderer付きの子オブジェクトをAddするなどして構成。
    • Prefabは使わず、コード上でInstantiate、AddComponent、SpriteRendererアタッチ、Sprite割り当てを行う。
    • スプライトは単純な1×1サイズの四角形を用意する。URPであれば2D用のSprite Shapeなども使えるが、ここでは簡易的に1色のSquareスプライトを用意して Assets > Create > Sprites > Square で作成し、インスペクターで適宜サイズ/Pixel per Unitを設定。
  5. ゲーム制御ロジック
    • GameManagerスクリプトを作成し、シーンにGameManagerオブジェクトを置く。そこに以下の機能を持たせる:
      • フィールド(10×20)の2次元配列管理
      • 現在操作中のテトリミノと次回のテトリミノ管理
      • ライン消去、スコア計算
      • ゲームオーバー判定
    • Tetrominoスクリプトを作り、生成や移動、回転処理を担当する。
  6. 入力処理
    • 左右キーで左右移動
    • 上キーで回転
    • 下キーで落下スピードアップ
  7. アップデート処理
    • 一定時間毎(例えば1秒に1マス落下)にテトリミノを1マス下へ移動
    • 移動不可能な場合はフィールドに固定してラインチェック
    • ラインが埋まっているなら削除、スコア加算
    • 新たなテトリミノ生成
  8. ゲームオーバー条件
    • 新しいテトリミノを生成できない(初期位置がすでに埋まっている)場合、ゲームオーバー

必要オブジェクト構成例

  • Hierarchy
    • Main Camera (Orthographicカメラ、size等調整)
    • GameManager (Empty GameObject, GameManagerスクリプトアタッチ)
    • [任意] UI用Canvas、TextMeshProなどでScore表示

設定例

  • カメラ
    • Orthographic Sizeを10程度にして、x方向が10マス、y方向が20マス入るように調整。
    • Positionを(5,10,-10)ぐらいにして、ちょうどフィールドを真ん中に映す。
      (x=5は0~10の真ん中、y=10は0~20の真ん中としている。)
  • Sprite
    • Squareスプライトを用意し、Pixel per Unitを100ぐらいにしておく。
    • シンプルにカラーを付けるならMaterialでBasic2DShader+色を設定、もしくはSpriteRendererのColorで変更。

スクリプト例

GameManager.cs

using UnityEngine;
using System.Collections.Generic;

public class GameManager : MonoBehaviour
{
    public static GameManager Instance;

    public int width = 10;
    public int height = 20;

    // フィールド上の各マスに存在するブロックの参照を保持
    // nullなら空、GameObjectがあればそのブロックが存在
    public Transform[,] grid;

    // スコア
    public int score = 0;

    // テトリミノ定義
    // 各テトリミノは4つの相対的な座標
    private List<Vector2Int[]> tetrominoes = new List<Vector2Int[]>
    {
        // I
        new Vector2Int[] {new Vector2Int(0,0), new Vector2Int(1,0), new Vector2Int(2,0), new Vector2Int(3,0)},
        // O
        new Vector2Int[] {new Vector2Int(0,0), new Vector2Int(1,0), new Vector2Int(0,1), new Vector2Int(1,1)},
        // T
        new Vector2Int[] {new Vector2Int(0,0), new Vector2Int(1,0), new Vector2Int(2,0), new Vector2Int(1,1)},
        // S
        new Vector2Int[] {new Vector2Int(0,0), new Vector2Int(1,0), new Vector2Int(1,1), new Vector2Int(2,1)},
        // Z
        new Vector2Int[] {new Vector2Int(0,1), new Vector2Int(1,1), new Vector2Int(1,0), new Vector2Int(2,0)},
        // J
        new Vector2Int[] {new Vector2Int(0,0), new Vector2Int(0,1), new Vector2Int(1,0), new Vector2Int(2,0)},
        // L
        new Vector2Int[] {new Vector2Int(0,0), new Vector2Int(1,0), new Vector2Int(2,0), new Vector2Int(2,1)},
    };

    // 現在操作中のテトリミノ
    private Tetromino currentTetromino;

    private float fallTime = 1.0f; // 1秒ごとに落下
    private float fallTimer = 0f;

    void Awake()
    {
        if (Instance == null) Instance = this;
        grid = new Transform[width, height];
    }

    void Start()
    {
        SpawnNewTetromino();
    }

    void Update()
    {
        fallTimer += Time.deltaTime;
        if (fallTimer >= fallTime)
        {
            fallTimer = 0f;
            if (currentTetromino != null)
                currentTetromino.MoveDown();
        }

        HandleInput();
    }

    void HandleInput()
    {
        if (currentTetromino == null) return;

        if (Input.GetKeyDown(KeyCode.LeftArrow))
        {
            currentTetromino.MoveHorizontal(-1);
        }
        else if (Input.GetKeyDown(KeyCode.RightArrow))
        {
            currentTetromino.MoveHorizontal(1);
        }

        if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            currentTetromino.Rotate();
        }

        if (Input.GetKey(KeyCode.DownArrow))
        {
            if (Time.timeScale > 0)
                currentTetromino.MoveDown();
        }
    }

    public void SpawnNewTetromino()
    {
        Vector2Int[] shape = tetrominoes[Random.Range(0, tetrominoes.Count)];

        // 初期スポーン位置: フィールド上部中央
        int spawnX = width / 2 - 1;
        int spawnY = height - 2; 
        // 高さに余裕をもたせて2マス上など

        // そこにテトリミノを生成
        GameObject tetroObj = new GameObject("Tetromino");
        Tetromino tetro = tetroObj.AddComponent<Tetromino>();
        tetro.Initialize(shape, spawnX, spawnY);
        currentTetromino = tetro;

        // スポーン直後にすでに衝突する場合はゲームオーバー
        if (!tetro.IsValidPosition())
        {
            Debug.Log("Game Over");
            Time.timeScale = 0f;
        }
    }

    public bool IsInsideGrid(int x, int y)
    {
        return (x >= 0 && x < width && y >= 0 && y < height);
    }

    public void AddToGrid(Transform block)
    {
        Vector2 pos = block.position;
        int x = Mathf.RoundToInt(pos.x);
        int y = Mathf.RoundToInt(pos.y);
        grid[x, y] = block;
    }

    public void ClearLines()
    {
        for (int y = 0; y < height; y++)
        {
            if (IsLineFull(y))
            {
                DeleteLine(y);
                MoveDownLinesAbove(y);
                y--;
            }
        }
    }

    bool IsLineFull(int y)
    {
        for (int x = 0; x < width; x++)
        {
            if (grid[x, y] == null)
                return false;
        }
        return true;
    }

    void DeleteLine(int y)
    {
        for (int x = 0; x < width; x++)
        {
            Destroy(grid[x, y].gameObject);
            grid[x, y] = null;
        }
        score += 100; // 1ラインで100点など
        Debug.Log("Score: " + score);
    }

    void MoveDownLinesAbove(int lineY)
    {
        for (int y = lineY + 1; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                if (grid[x, y] != null)
                {
                    grid[x, y - 1] = grid[x, y];
                    grid[x, y] = null;
                    grid[x, y - 1].position += Vector3.down;
                }
            }
        }
    }

    public bool CheckPosition(Vector2Int[] blocks, int offsetX, int offsetY)
    {
        foreach (var cell in blocks)
        {
            int x = cell.x + offsetX;
            int y = cell.y + offsetY;
            if (!IsInsideGrid(x, y))
                return false;
            if (grid[x, y] != null)
                return false;
        }
        return true;
    }

    public void TetrominoPlaced()
    {
        currentTetromino = null;
        ClearLines();
        SpawnNewTetromino();
    }
}

Tetromino.cs

using UnityEngine;

public class Tetromino : MonoBehaviour
{
    private Vector2Int[] cells; // テトリミノの構成ブロック位置(ローカル座標)
    private int posX, posY; // テトリミノ原点(左下基準)
    private Color color;

    // 初期化
    public void Initialize(Vector2Int[] shape, int startX, int startY)
    {
        cells = new Vector2Int[shape.Length];
        for (int i = 0; i < shape.Length; i++)
        {
            cells[i] = shape[i];
        }
        posX = startX;
        posY = startY;

        color = Random.ColorHSV(); // ランダムな色

        // ブロック生成
        for (int i = 0; i < cells.Length; i++)
        {
            GameObject block = new GameObject("Block");
            block.transform.parent = this.transform;
            block.transform.localPosition = new Vector3(cells[i].x, cells[i].y, 0);

            var sr = block.AddComponent<SpriteRenderer>();
            sr.sprite = Resources.GetBuiltinResource<Sprite>("UI/Skin/Background.psd"); 
            // ↑これはUI背景用Sprite。適宜"Square"スプライトを用意してそれを参照可能なら置き換え。
            // Unity内にSquareスプライトを準備し、Assets内に"Square"という名前で置いた場合は以下でもOK:
            // sr.sprite = AssetDatabase.LoadAssetAtPath<Sprite>("Assets/Square.png");
            // ここは環境に合わせて差し替えが必要。  
            sr.color = color;
        }

        UpdatePosition();
    }

    public void UpdatePosition()
    {
        this.transform.position = new Vector3(posX, posY, 0);
    }

    public void MoveHorizontal(int direction)
    {
        if (CanMove(cells, posX + direction, posY))
        {
            posX += direction;
            UpdatePosition();
        }
    }

    public void MoveDown()
    {
        if (CanMove(cells, posX, posY - 1))
        {
            posY -= 1;
            UpdatePosition();
        }
        else
        {
            // 下に動けない=固定
            LockDown();
        }
    }

    public void Rotate()
    {
        // 90度回転: (x,y) -> (y, -x)
        Vector2Int[] rotated = new Vector2Int[cells.Length];
        for (int i = 0; i < cells.Length; i++)
        {
            int x = cells[i].x;
            int y = cells[i].y;
            rotated[i] = new Vector2Int(y, -x);
        }

        if (CanMove(rotated, posX, posY))
        {
            cells = rotated;
            // 位置更新
            // 子オブジェクトの位置更新
            for (int i = 0; i < transform.childCount; i++)
            {
                transform.GetChild(i).localPosition = new Vector3(cells[i].x, cells[i].y, 0);
            }
        }
    }

    bool CanMove(Vector2Int[] shape, int offsetX, int offsetY)
    {
        return GameManager.Instance.CheckPosition(shape, offsetX, offsetY);
    }

    public bool IsValidPosition()
    {
        return CanMove(cells, posX, posY);
    }

    void LockDown()
    {
        // フィールドに登録
        for (int i = 0; i < cells.Length; i++)
        {
            int x = posX + cells[i].x;
            int y = posY + cells[i].y;
            Transform block = transform.GetChild(i);
            GameManager.Instance.AddToGrid(block);
        }

        // 自分(テトリミノオブジェクト)はブロックをすべて外した後破棄
        // ただし今はブロックの親オブジェクトとして存在しているので、自分は空になる
        // Grid側にブロックを登録してしまったので、このTetrominoオブジェクトは不要
        // ただしGrid側はblock.transform = ...で親を持ち続けているので注意が必要
        // 今回AddToGrid時には親付け直していないので、
        // Destroyする前にブロックの親をnullにしたいが、AddToGridで親を変えていないので大丈夫。
        for (int i = transform.childCount - 1; i >= 0; i--)
        {
            transform.GetChild(i).parent = null;
        }
        Destroy(gameObject);

        GameManager.Instance.TetrominoPlaced();
    }
}

Sprite参照に関して
Tetromino.cs内でスプライトを設定する箇所には実際にはSpriteを参照する必要があります。
Resources.Load<Sprite>("Square")などを使うために、Assets/Resourcesフォルダを作り、その中に”Square.png”スプライトを入れておくとResources.Load<Sprite>("Square")で取得可能です。
例えば以下に変更できます。

var sr = block.AddComponent<SpriteRenderer>();
Sprite blockSprite = Resources.Load<Sprite>("Square"); // Resourcesフォルダ内の"Square.png"
sr.sprite = blockSprite;
sr.color = color;

このようにResourcesフォルダを活用します。

改善・補足

  • 回転軸を微調整したい場合、テトリミノのローカル原点を調整するなどが必要。
  • スコアUI表示にはCanvasを作り、TextMeshProUI.Textを置いて、GameManagerから参照してUpdateすることでスコア表示可能。
  • ゲームスピードなどのバランス調整はfallTimeを変更したり、ライン消去数でスピードアップするなど拡張できる。

まとめ

上記の手順とスクリプト例に従えば、

  • GameManagerがフィールドやテトリミノを管理
  • Tetrominoが自分自身のブロックを生成し、移動・回転・固定処理
  • GameManagerは毎フレーム落下処理、入力処理、ライン消去判定
    といったテトリスの基本的な流れを実現できます。

これで、Prefabsを使わずにスクリプト上でテトリミノを生成する簡易的なテトリスが完成します。

結果

テトリスとして最低限の状態ではありますが、ちゃんと動く「テトリス」が作れました!

ということで「成功」です!

コメント

タイトルとURLをコピーしました