見出し画像

[Unity] Claude3にローグライクを作らせようとした話。

最近話題のClaude3にunityというゲームエンジンでローグライクを作らせようとしてみました。

きっかけはたまたま視聴したYouTubeのこの動画で
Claude3がアンチスレやYouTubeのコメント欄とかを解像度高めで生成してたのをみて、

Claude3やばいな!めっちゃ賢いな!

これならローグライクも作れるんじゃね?

と思ったため、見切り発車でローグライクをClaude3に作らせてみることにしました。


この動画から、最初に長文の資料を入れてから質問をするとClaudeの性能が上がると分かったので


まずはローグライクの開発手順や設計を入れてから、作ってもらうことにします。


今回は、この記事を参考にして、Claude3にローグライクの開発手順や設計の資料を入れました。


さぁ早速ローグライクを作ってくれ!

あなたはゲームエンジンのunityの専門家である。あなたはこの開発手順を元にローグライクゲームを作ることにした。この開発手順を参考に実際にローグライクを作成せよ

はい、この開発手順を参考にしてUnityでローグライクゲームを作成することができます。以下のようにステップを踏んで開発を進めていきましょう。

**Step 1. ゲームのコンセプト決定**

- Unityプロジェクトを新規作成します。
- 2Dゲームか3Dゲームかを決めます(ここでは2Dとします)。
- ローグライクゲームとしての特徴やゲームシステムを検討します。

**Step 2. Hello World! (開発環境の構築)**

- Unityで2Dゲームを作成する際の設定を行います。
- C#を使ってゲームロジックを実装することにします。

**Step 3. プレイヤーの表示とキー入力**

- プレイヤーのスプライトを作成/インポートします。
- プレイヤーの移動を実装します(InputSystemを使うとキー入力が簡単)。
- メッセージウィンドウを表示するUIを作ります。

**Step 4. マップとマップオブジェクト**

- タイルマップを使ってマップを作成します。
- 壁のタイルを設定し、プレイヤーと壁の当たり判定を実装します。
- マップデータの管理クラスを作ります。

**Step 5. 保存と読み込み**

- ゲームデータを保存・読み込みするシステムを作ります(PlayerPrefsやJSONなど)。

**Step 6. モンスターとターン制**

- モンスタースプライトを作成し、AIによる移動を実装します。
- ターン制の処理をシーケンサーで制御します。

**Step 7. 戦闘システム**

- キャラクターのステータスとダメージ計算を実装します。

**Step 8. データファイル**

- キャラクターのステータスデータやメッセージデータをJSONやCSVで外部化します。

**Step 9. アイテム**

- アイテムのスプライトを作成します。
- アイテムの取得、使用、装備などの処理を実装します。
- インベントリUIを作ります。

**Step 10. 魔法と特殊攻撃**

- 薬や杖などのアイテム効果を実装します。
- 状態異常の効果を実装します。

**Step 11. シンプルなゲームでテストプレイ**

- 作ったシステムが動作するかテストプレイでチェックします。
- 操作性や分かりづらい部分を改善します。

**Step 12. 階の生成と移動**

- 次の階層へ移動する処理を実装します。
- プロシージャル生成でマップを自動生成します。

**Step 13. 経験値とスキル**

- 経験値の獲得とレベルアップのシステムを作ります。
- プレイヤーのスキルを実装します。

**Step 14. その他の機能**

- ショップ、クエスト、実績など追加の要素を実装します。
- 総プレイ時間や倒した敵の種類などの統計データを記録します。

開発手順を参考にしながら、必要に応じてUnityの機能を活用して実装を進めていきます。デバッグ用のショートカットキーの割り当てやテストプレイの重要性なども意識しながら、ゲームの完成度を高めていきましょう。

おお!なんかすごい!
でももっとコードとかも書いて欲しい!

Step1から順番にローグライクをコードやオブジェクトを作成せよ。作成するオブジェクト、スクリプトを明記して、作成すること

このプロンプトならコードを書いてくれるはず!
(なんか見返したら質問の日本語が変だな)

はい、分かりました。開発手順に沿ってUnityでローグライクゲームを作成していきます。必要となるオブジェクトやスクリプトを明記しながら進めていきます。

**Step 1. ゲームのコンセプト決定**

- 新規Unityプロジェクトを作成します。
- 2Dゲームを作成するため、Unityの新しいプロジェクトの設定で「2D」を選択します。

**Step 2. Hello World! (開発環境の構築)**

- プロジェクトに必要なフォルダ構造を作成します(Scripts、Sprites、Prefabsなど)。
- 入力管理のためのインプットマネージャースクリプトを作成します。

**スクリプト**
- InputManager.cs

**Step 3. プレイヤーの表示とキー入力**

- プレイヤースプライトを作成/インポートします。

**Sprites**
- PlayerSprite.png

- プレイヤーのプレハブを作成します。

**Prefabs**
- Player.prefab

- プレイヤー移動スクリプトを作成します。

**スクリプト**
- PlayerMovement.cs

- メッセージウィンドウのUIキャンバスとテキストオブジェクトを作成します。

**UI要素**
- MessageCanvas
- MessageText

- メッセージ表示スクリプトを作成します。

**スクリプト**
- MessageManager.cs

**Step 4. マップとマップオブジェクト**

- タイルパレットを作成し、壁と床のタイルを設定します。

**Tiles**
- WallTile.png
- FloorTile.png

- マップのタイルマップを作成します。

**Tilemap**
- Dungeon

- マップデータ管理スクリプトを作成します。

**スクリプト**
- MapManager.cs

- 壁との当たり判定スクリプトを作成します。

**スクリプト**
- WallCollision.cs

**Step 5. 保存と読み込み**

- ゲームデータ保存・読み込みスクリプトを作成します。

**スクリプト**
- GameDataManager.cs

**Step 6. モンスターとターン制**

- モンスタースプライトを作成/インポートします。

**Sprites**
- MonsterSprite.png

- モンスタープレハブを作成します。

**Prefabs**
- Monster.prefab

- モンスターAIスクリプトを作成します。

**スクリプト**
- MonsterAI.cs

- ターン制御スクリプトを作成します。

**スクリプト**
- TurnManager.cs

**Step 7. 戦闘システム**

- キャラクターステータススクリプトを作成します。

**スクリプト**
- CharacterStats.cs

- ダメージ計算スクリプトを作成します。

**スクリプト**
- DamageCalculator.cs

これまでのステップで必要となるオブジェクトとスクリプトを作成しました。今後のステップでさらにオブジェクトやスクリプトを追加していきます。開発手順に沿って順次実装を進めていきましょう。

結構具体的になった!
あとは肝心のスクリプトを見せてくれ!

作成したスクリプトを具体的に記述せよ

さぁどうなる?

はい、作成したスクリプトの具体的な内容を記述します。

**InputManager.cs**
```csharp
using UnityEngine;
using UnityEngine.InputSystem;

public class InputManager : MonoBehaviour
{
    public delegate void OnInputAction(InputAction.CallbackContext context);
    public static event OnInputAction onMove;
    public static event OnInputAction onInteract;

    private PlayerInput playerInput;

    private void Awake()
    {
        playerInput = new PlayerInput();
        playerInput.Player.Move.performed += HandleMoveInput;
        playerInput.Player.Interact.performed += HandleInteractInput;
    }

    private void OnEnable()
    {
        playerInput.Player.Enable();
    }

    private void OnDisable()
    {
        playerInput.Player.Disable();
    }

    private void HandleMoveInput(InputAction.CallbackContext context)
    {
        onMove?.Invoke(context);
    }

    private void HandleInteractInput(InputAction.CallbackContext context)
    {
        onInteract?.Invoke(context);
    }
}
```

**PlayerMovement.cs**
```csharp
using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    public float moveSpeed = 5f;

    private Vector2 moveDirection;

    private void Awake()
    {
        InputManager.onMove += HandleMoveInput;
    }

    private void HandleMoveInput(InputAction.CallbackContext context)
    {
        moveDirection = context.ReadValue<Vector2>();
    }

    private void Update()
    {
        Move();
    }

    private void Move()
    {
        transform.Translate(moveDirection * moveSpeed * Time.deltaTime);
    }
}
```

**MessageManager.cs**
```csharp
using UnityEngine;
using UnityEngine.UI;

public class MessageManager : MonoBehaviour
{
    public Text messageText;
    public Queue<string> messageQueue = new Queue<string>();

    private void Update()
    {
        if (messageQueue.Count > 0 && string.IsNullOrEmpty(messageText.text))
        {
            messageText.text = messageQueue.Dequeue();
        }
    }

    public void AddMessage(string message)
    {
        messageQueue.Enqueue(message);
    }
}
```

**MapManager.cs**
```csharp
using UnityEngine;
using UnityEngine.Tilemaps;

public class MapManager : MonoBehaviour
{
    public Tilemap dungeon;
    public Tile wallTile;
    public Tile floorTile;

    private int[,] mapData;

    private void Start()
    {
        LoadMapData();
        RenderMap();
    }

    private void LoadMapData()
    {
        // Load map data from file or generate procedurally
        mapData = new int[,]
        {
            {1, 1, 1, 1, 1},
            {1, 0, 0, 0, 1},
            {1, 0, 1, 0, 1},
            {1, 0, 0, 0, 1},
            {1, 1, 1, 1, 1}
        };
    }

    private void RenderMap()
    {
        for (int y = 0; y < mapData.GetLength(1); y++)
        {
            for (int x = 0; x < mapData.GetLength(0); x++)
            {
                Tile tile = mapData[x, y] == 1 ? wallTile : floorTile;
                dungeon.SetTile(new Vector3Int(x, y, 0), tile);
            }
        }
    }
}
```

**WallCollision.cs**
```csharp
using UnityEngine;

public class WallCollision : MonoBehaviour
{
    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.CompareTag("Player"))
        {
            // Handle player collision with wall
            Debug.Log("Player collided with wall");
        }
    }
}
```

**GameDataManager.cs**
```csharp
using UnityEngine;

public class GameDataManager : MonoBehaviour
{
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.S))
        {
            SaveGame();
        }
        else if (Input.GetKeyDown(KeyCode.L))
        {
            LoadGame();
        }
    }

    private void SaveGame()
    {
        // Save game data to file or PlayerPrefs
    }

    private void LoadGame()
    {
        // Load game data from file or PlayerPrefs
    }
}
```

**MonsterAI.cs**
```csharp
using UnityEngine;

public class MonsterAI : MonoBehaviour
{
    public float moveSpeed = 2f;
    public Transform playerTransform;

    private void Update()
    {
        MoveTowardsPlayer();
    }

    private void MoveTowardsPlayer()
    {
        Vector3 direction = (playerTransform.position - transform.position).normalized;
        transform.Translate(direction * moveSpeed * Time.deltaTime);
    }
}
```

**TurnManager.cs**
```csharp
using System.Collections.Generic;
using UnityEngine;

public class TurnManager : MonoBehaviour
{
    public enum TurnState
    {
        PlayerTurn,
        EnemyTurn
    }

    public TurnState currentTurnState;
    public List<MonsterAI> enemies;

    private void Update()
    {
        switch (currentTurnState)
        {
            case TurnState.PlayerTurn:
                // Handle player turn
                break;
            case TurnState.EnemyTurn:
                EnemyTurn();
                break;
        }
    }

    private void EnemyTurn()
    {
        foreach (MonsterAI enemy in enemies)
        {
            enemy.MoveTowardsPlayer();
        }

        // Switch to player turn after all enemies have moved
        currentTurnState = TurnState.PlayerTurn;
    }
}
```

**CharacterStats.cs**
```csharp
using UnityEngine;

public class CharacterStats : MonoBehaviour
{
    public int maxHealth = 100;
    public int currentHealth;
    public int attack = 10;
    public int defense = 5;

    private void Start()
    {
        currentHealth = maxHealth;
    }
}
```

**DamageCalculator.cs**
```csharp
public static class DamageCalculator
{
    public static int CalculateDamage(int attackerAttack, int defenderDefense)
    {
        // Simple damage calculation formula: Attacker's attack - Defender's defense
        return Mathf.Max(attackerAttack - defenderDefense, 1);
    }
}
```

あれ?止まったな。
続きを書いてくれ!

DamageCalculatorから続きを記述せよ

using UnityEngine;

public static class DamageCalculator
{
    public static int CalculateDamage(int attackerAttack, int defenderDefense)
    {
        // Simple damage calculation formula: Attacker's attack - Defender's defense
        return Mathf.Max(attackerAttack - defenderDefense, 1);
    }

    public static void ApplyDamage(CharacterStats attacker, CharacterStats defender)
    {
        int damageAmount = CalculateDamage(attacker.attack, defender.defense);
        defender.currentHealth -= damageAmount;

        // Display damage message
        string message = $"{attacker.name} dealt {damageAmount} damage to {defender.name}";
        MessageManager.Instance.AddMessage(message);

        // Check if defender is dead
        if (defender.currentHealth <= 0)
        {
            HandleCharacterDeath(defender);
        }
    }

    private static void HandleCharacterDeath(CharacterStats character)
    {
        // Handle character death logic (e.g., drop loot, gain experience, remove from game)
        string message = $"{character.name} has died.";
        MessageManager.Instance.AddMessage(message);

        // Remove character from game
        Destroy(character.gameObject);
    }
}

このスクリプトでは、以下の機能が追加されています。

CalculateDamageメソッドでは、攻撃者の攻撃力と防御者の防御力を使って、ダメージ量を計算しています。
ApplyDamageメソッドでは、攻撃者と防御者のCharacterStatsコンポーネントを受け取り、ダメージ量を計算して防御者のHPから減算しています。また、ダメージメッセージをMessageManagerに追加しています。
HandleCharacterDeathメソッドでは、防御者のHPが0以下になった場合の処理を行っています。キャラクターの死亡メッセージを表示し、ゲームオブジェクトを破棄しています。
MessageManagerのインスタンスを使用するため、シングルトンパターンを使ってインスタンスにアクセスできるようにしています。

この実装では、単純な減算方式でダメージ計算を行っていますが、後々ゲームのバランスを取るためにダメージ計算式を変更する必要があるかもしれません。また、キャラクターの死亡時の処理(アイテムドロップや経験値獲得など)も追加する必要があります。

おお!コードが完成した! 
ところどころおかしなコードがあって
多分このコードをそのまま貼り付けてもちゃんと動くかどうかは分からないけど、プロトタイプとしては結構良さそうな感じがする。

これはここから質問してうまく調整していけば、
ちゃんとしたローグライクが作れそうなポテンシャルが
Claude3にある感じがする!

いやーなんかすごい時代になりましたね!
では今回はここまで!

続きはこちら

この記事を見た人におすすめの記事



この記事が参加している募集

ゲームの作り方

AIとやってみた

この記事が気に入ったらサポートをしてみませんか?