Unity ゲーム開発 生成AI

Unity素人がAI活用で進める独学個人ゲーム開発 #8_アイテムの実装①

本記事では、ChatGPTを活用してUnityの自機の強化に使うアイテムを実装する過程を紹介します。

記事の目的

前回、敵が生成される仕組みの実装&敵の種類を増やしました。
前回記事 ⇒ #7_敵の実装②

ここからアイテムを実装していきます。
基本的な機能

  • 銃弾で破壊可能
  • 破壊すると自機がパワーアップ

アイテムの種類

  • 火力UP
  • 連射速度UP
  • 自機を増加

今回はアイテム実装~ステータスUPまで実装したいと思います。

アイテムの実装

アイテムは以下の要件を満たすようにしようと思います。

  • アイテムは3つ横並びで流れてくる
  • アイテムに当たると自機は破壊される
  • アイテムを下記すると自機を強化
  • 3つは壊せないくらいの耐久値

あとで調整可能な部分もあるのでノリでやっていきます。

ここに挙げた条件を満たすようなアイテムの実装方法を詳しく解説してください。

ChatGPTの回答(クリックで展開)
いいですね!要件を整理すると次のようになります:

要件まとめ
1️⃣ アイテムは3つ横並び(横1列)で流れてくる
2️⃣ 自機がアイテムにぶつかると破壊される(ダメージ扱い)
3️⃣ 弾を当てると自機を強化する(撃ち落としによるパワーアップ)
4️⃣ アイテムは耐久値が高く、弾1発では壊れない

💡 実装ステップ詳細(Unity C#)

アイテムPrefabの用意
Item オブジェクトを作成(SpriteRenderer付き)
BoxCollider2D(isTrigger = true)と Rigidbody2D(Body Type = Kinematic)を追加

public class Item : MonoBehaviour
{
    public int maxHP = 10;
    private int currentHP;

    void Start() { currentHP = maxHP; }

    public void TakeDamage(int amount)
    {
        currentHP -= amount;
        if (currentHP <= 0) { StrengthenPlayer(); Destroy(gameObject); }
    }

    void StrengthenPlayer()
    {
        Debug.Log("プレイヤー強化!");
        // 例: Player.instance.PowerUp();
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Player"))
        {
            collision.GetComponent<Player>().TakeDamage();
            Destroy(gameObject);
        }
    }
}

アイテムのスポーン(3つ横並び)

public class ItemSpawner : MonoBehaviour
{
    public GameObject itemPrefab;
    public float spawnInterval = 5f;
    public float horizontalSpacing = 1f;
    public Transform spawnPoint;

    void Start() { InvokeRepeating("SpawnItemGroup", 2f, spawnInterval); }

    void SpawnItemGroup()
    {
        for (int i = 0; i < 3; i++)
        {
            Vector3 pos = spawnPoint.position + new Vector3(0, (i - 1) * horizontalSpacing, 0);
            Instantiate(itemPrefab, pos, Quaternion.identity);
        }
    }
}

弾との当たり判定(Bullet側)

public class Bullet : MonoBehaviour
{
    public int damage = 1;

    void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Item"))
        {
            collision.GetComponent<Item>().TakeDamage(damage);
            Destroy(gameObject);
        }
    }
}

移動処理

public float speed = 2f;

void Update()
{
    transform.Translate(Vector2.left * speed * Time.deltaTime);
}

耐久値調整
Item.maxHP を10~20に設定、弾.damage = 1 で10発くらいで破壊できるように調整

注意点
✅ Player側の TakeDamage() 実装
✅ Tag 設定(Item, Player)
✅ Collider サイズ調整(必要に応じて)

🛠 補足(オプション)
・種類別強化:
enum ItemType { PowerUp, SpeedUp, Shield } を作り、StrengthenPlayer()内で分岐
・HPバーUIを表示すると親切です✨

①アイテムPrefabの用意

アイテムの画像はひとまずVertical 2D Shooting BE4(UnityAssetStoreから拝借したアセット)に収録されているコインのようなもので仮実装します。画像生成AIを勉強するときにアイテムっぽい画像を作って切り替えたいと思います。

また、ChatGPTが過去のやり取りを参照できずにスクリプトが狂ってしまっていました。
Item.csのvoid OnTriggerEnter2Dを以下の通り修正
collision.GetComponent().TakeDamage();

collision.GetComponent().TakeDamage(1);

プレイヤー強化の中身も決まっているので作るようにお願いします。

自機強化の種類は以下の3つにしたいです。

火力UP
連射速度UP
自機を増加

ChatGPTの回答(クリックで展開)
いいですね!自機強化の種類を3つに分けるのはゲーム性がぐっと面白くなります。
以下に具体的な実装案をまとめます。

🌟 強化種類(3タイプ)
1️⃣ 火力UP → 弾のダメージ増加(例: bullet.damage += 1)

2️⃣ 連射速度UP → 弾発射間隔を短縮(例: fireRate -= 0.1f、ただし下限あり)

3️⃣ 自機を増加 → 分身を追加(例: FormationManagerなどで編成増加)

🏗 実装方針

【1】Item.cs 側の設定
まず、Itemの種類を識別するために enum を追加。

public enum ItemType
{
    PowerUp,       // 火力UP
    RapidFireUp,   // 連射速度UP
    AddClone       // 自機増加
}

Item.csに次を追加:

public ItemType itemType;

StrengthenPlayer() を拡張:

void StrengthenPlayer()
{
    var player = FindObjectOfType<PlayerController>();
    if (player != null)
    {
        switch (itemType)
        {
            case ItemType.PowerUp:
                player.IncreasePower();
                break;
            case ItemType.RapidFireUp:
                player.IncreaseFireRate();
                break;
            case ItemType.AddClone:
                player.AddClone();
                break;
        }
    }
}

【2】PlayerController.cs 側の実装

public class PlayerController : MonoBehaviour
{
    public int life = 3;
    public int bulletDamage = 1;
    public float fireRate = 0.5f; // 秒間発射間隔
    public int cloneCount = 0;

    public void TakeDamage(int damage)
    {
        life -= damage;
        if (life <= 0)
        {
            Debug.Log("プレイヤーがやられた!");
            // ゲームオーバー処理
        }
    }

    public void IncreasePower()
    {
        bulletDamage += 1;
        Debug.Log("火力UP! 現在のダメージ: " + bulletDamage);
    }

    public void IncreaseFireRate()
    {
        fireRate = Mathf.Max(0.1f, fireRate - 0.1f); // 下限0.1秒
        Debug.Log("連射速度UP! 現在の発射間隔: " + fireRate);
    }

    public void AddClone()
    {
        cloneCount += 1;
        Debug.Log("自機増加! 現在の分身数: " + cloneCount);
        // 例:FormationManager.instance.AddClone();
    }
}

【3】Bullet.cs 側(ダメージ反映)

void Start()
{
    var player = FindObjectOfType<PlayerController>();
    if (player != null)
    {
        damage = player.bulletDamage;
    }
}

【4】Item配置(インスペクター設定)
ItemオブジェクトのInspectorで ItemType を設定。
例えば:

・左 → PowerUp
・中央 → RapidFireUp
・右 → AddClone

Item.csで

  • PowerUP
  • RapidFireUp
  • AddClone

のアイテムタイプを作成して
PlayerController.csでそれぞれのアイテムタイプを取得した時に

  • IncereasePower
  • IncreaseFireRate
  • AddClone

が発生するように定義した感じですかね?

また、PlayerController.csでIncreasePowerが発生⇒bulletdamageが+される⇒その値を銃弾のダメージとして扱うようにBullet.csに返す って感じの流れだと思われます。

今まで1つの機能を実装したときには他のところをあまりいじったりは無かったですが、どんどん関連するものが増えて管理が大変になってきましたね。どこが繋がっているか分からなくならないように覚えておく、もしくはどこかにまとめておいた方がいいかもしれません。

ここからさらに実装をChatGPTと進めているとある一文が目に留まります。

FindObjectOfType<PlayerController>() は少し重いので、
本格的には弾生成側(例: PlayerShooting.cs)で生成時にセットする方がベターです。

今の実装だと重くなるとのこと。たしかにいちいちオブジェクトを探しに行くのは処理に負担がかかりそうな気がします。無駄に重いゲームってイライラしますよね。そうならないためにも改善を考えたいところです。

"弾生成側(例: PlayerShooting.cs)で生成時にセットする方がベターです。"

この場合の操作を全て教えて下さい。

ChatGPTの回答(クリックで展開)
とても良い質問です!
FindObjectOfType<PlayerController>() を弾側で毎回呼ぶのは確かに非効率なので、
弾を生成する側(PlayerShooting.cs など)から弾に直接ダメージ値を渡す方式に変えるのがベストです。

🌟 目標
1️⃣ Player(自機)が弾を撃つとき、
 PlayerController にある bulletDamage を弾に渡す。

2️⃣ Bullet 側では、もう Player を探さない。

🏗 手順まとめ

【1】Bullet.cs を修正
Start() 内の FindObjectOfType を削除します。

using UnityEngine;

public class Bullet : MonoBehaviour
{
    public float speed = 20f;
    public int damage = 1;  // ← 発射側からセットされる値

    private Rigidbody2D rb;

    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        rb.velocity = Vector2.right * speed;  // 右方向へ移動
    }

    void OnBecameInvisible()
    {
        Destroy(gameObject);
    }

    void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Item"))
        {
            var item = collision.GetComponent<Item>();
            if (item != null)
            {
                item.TakeDamage(damage);
            }
        }

        if (collision.CompareTag("Enemy"))
        {
            var enemy = collision.GetComponent<Enemy>();
            if (enemy != null)
            {
                enemy.TakeDamage(damage);
            }
        }

        Destroy(gameObject);
    }
}

✅ ここで重要なのは:
・弾の damage は Player 側から渡される。
・Start() 内で FindObjectOfType<PlayerController>() をやめる。

【2】PlayerShooting.cs を作成・修正
以下は PlayerController とは別の、
弾の生成・発射を管理するスクリプト例 です。

using UnityEngine;

public class PlayerShooting : MonoBehaviour
{
    public GameObject bulletPrefab;
    public Transform firePoint;
    private PlayerController playerController;

    public float fireRate = 0.5f;
    private float nextFireTime = 0f;

    void Start()
    {
        playerController = GetComponent<PlayerController>();
    }

    void Update()
    {
        if (Input.GetButton("Fire1") && Time.time >= nextFireTime)
        {
            Shoot();
            nextFireTime = Time.time + fireRate;
        }
    }

    void Shoot()
    {
        GameObject bullet = Instantiate(bulletPrefab, firePoint.position, firePoint.rotation);
        Bullet bulletScript = bullet.GetComponent<Bullet>();
        bulletScript.damage = playerController.bulletDamage;  // ← ここでダメージ値を渡す
    }
}

説明:
・bulletPrefab → 弾のPrefab(Bulletスクリプトが付いている)
・firePoint → 弾を発射する位置(空オブジェクトのTransformを指定)
・playerController.bulletDamage → 現在のプレイヤーの攻撃力を取得

銃弾生成に使っていたPlayerShoot.csがあったのでそれを変更します。
生成された銃弾がdamageを参照しに行く⇒銃弾を生成するスクリプトがdamageを保持して銃弾に渡す に変更されたという認識です。

コードにbulletとかBulletがごちゃごちゃ出てきすぎてて理解しづらいのでまとめておきます。

C#メモ
コード / 変数 意味
bullet 生成されるオブジェクトを変数として格納したもの
bulletScript bulletを参照し、そこにアタッチされているスクリプトを取得して変数に格納したもの
bulletScript.damage bulletScriptに存在する damage(publicで定義されたフィールド)に代入
playerController.bulletDamage playerControllerから参照される bulletDamage の値

ここまででひとまずアイテムを"銃弾で破壊すると自機が強化される"というのは実装されました。前回までのようにGIFで表示しようと思ったのですが、見た目上何も変化が起こらないため一旦見送りました。自機が強化されたときにUIが変化したり、SEが鳴ったり、自機の何かしらが強そうになったり…といった変化が分かる何かを発生させないといけないですね。やり始めたらこれも時間がかかりそうなので今後の課題とします。

アイテムのスポーン

続いてアイテムが横並びに出現する仕組みについて
ItemSpawnerのスクリプトを出力してもらっていました。

ChatGPTの回答(クリックで展開)
② アイテムのスポーン(3つ横並び)

public class ItemSpawner : MonoBehaviour
{
    public GameObject itemPrefab;
    public float spawnInterval = 5f;
    public float horizontalSpacing = 1f;
    public Transform spawnPoint;

    void Start() { InvokeRepeating("SpawnItemGroup", 2f, spawnInterval); }

    void SpawnItemGroup()
    {
        for (int i = 0; i < 3; i++)
        {
            Vector3 pos = spawnPoint.position + new Vector3(0, (i - 1) * horizontalSpacing, 0);
            Instantiate(itemPrefab, pos, Quaternion.identity);
        }
    }
}

どこに付けるべきかなど書かれていませんがおそらく、ItemSpawnerというオブジェクトを作ってそこにアタッチでしょう。調整はあとでやるとして進めていきます。

ここでゲームを動かしたところ全くアイテムが流れてきませんでした。
よく確認すると画面外にはいました。

最初に戻ってスクリプトを確認します。

移動処理

public float speed = 2f;

void Update()
{
    transform.Translate(Vector2.left * speed * Time.deltaTime);
}

これで動くように変更されました。

現在のスクリプトだとSpawnPointの指示に加えてHorizontalSpacingというのも使って生成位置を決めないといけないようです。
少し面倒な気がするので敵の実装をしたときのように生成位置を5か所作ってそこから生成するようにします。

(関連記事はこちら⇒敵の実装②

EnemySpawnerを作った時のように5か所の生成位置を作って、その内3か所からランダムで出てくるようにしたいです。

ChatGPTの回答(クリックで展開)
いいですね!
要件を整理し、具体的な実装例を用意しました。

🌟 要件まとめ
✅ 5か所の生成位置(固定座標 or Transform)を用意する。
✅ 毎回その中から ランダムに3か所 を選び、同時にItemを生成する。

🏗 実装ステップ

【1】Spawnerオブジェクトを作る
Hierarchy に空のGameObjectを作成 → 名前を ItemSpawner。
このオブジェクトの下に、5つの空オブジェクト(名前例:SpawnPoint0 ~ SpawnPoint4)を配置し、
それぞれ生成したい位置に移動させます。

【2】ItemSpawner.cs スクリプトを作成

using UnityEngine;

public class ItemSpawner : MonoBehaviour
{
    public GameObject itemPrefab;
    public Transform[] spawnPoints;  // 5か所の生成位置
    public float spawnInterval = 5f; // 生成間隔(秒)

    void Start()
    {
        InvokeRepeating(nameof(SpawnItems), 2f, spawnInterval);
    }

    void SpawnItems()
    {
        int[] indices = { 0, 1, 2, 3, 4 };

        for (int i = indices.Length - 1; i > 0; i--)
        {
            int rand = Random.Range(0, i + 1);
            int temp = indices[i];
            indices[i] = indices[rand];
            indices[rand] = temp;
        }

        for (int i = 0; i < 3; i++)
        {
            Transform spawnPoint = spawnPoints[indices[i]];
            Instantiate(itemPrefab, spawnPoint.position, Quaternion.identity);
        }
    }
}

【3】Inspector設定
1️⃣ ItemSpawner オブジェクトに ItemSpawner.cs をアタッチ。
2️⃣ Inspectorの Spawn Points 配列に、SpawnPoint0 ~ SpawnPoint4 を順番通りにドラッグ登録。
3️⃣ Item Prefab に、生成したい Item プレハブをドラッグ登録。


それっぽく見える状態でアイテムの実装は完了しました!
一応、破壊することで自機の攻撃力が上がっていっています。

ただし、問題が残っていることを確認したのでそこだけ修正します。

まず、画面外に出ても永遠に残り続けています。
これはOnBecameInvisibleでDestroyにすればよかったはずです。

また、ItemとPlayerがぶつかったときにItemがPlayerを押して行って画面外に消えてしまいます。本来はお互いが消滅するはずです。

ItemとPlayerがぶつかったときにItemがPlayerを押して行って画面外に消えてしまいます。
本来はお互いが消滅するはずです。

ChatGPTの回答(クリックで展開)
とても良い観察です!
この現象は、物理挙動(Rigidbody2D + Collider2D)による「押し出し」が起きているのが原因です。

🌟 問題の原因
Unityでよくある次の組み合わせが原因です:

Item側
Rigidbody2D(Kinematic または Dynamic)
Collider2D(IsTrigger → false または true)

Player側
Rigidbody2D(Dynamic または Kinematic)
Collider2D(IsTrigger → false)

もし Item または Player の Rigidbody2D が Dynamic で、
Collider2D の IsTrigger が false だと、
Unityの物理演算(衝突&反発&摩擦)が働いてしまいます。

その結果:
✅ Item が Player を押し出す。
✅ Item は Update() で動き続けるので、Player を押しっぱなしにする。
✅ 最終的に画面外へ。

改善策(おすすめ)
1️⃣ Item側の Collider2D を IsTrigger = true にする
ItemのColliderの設定で「Is Trigger」にチェックを入れる。

これで物理衝突(押し出し)は発生せず、
代わりに OnTriggerEnter2D が発生。

これで問題はなくなりました。
アイテム破壊~ステータスUPの枠組みは完成しましたがアイテム関連で実装したいことはまだ多数残っています。

  • 攻撃力の強化だけしかないので自機増加や連射速度UPなどの種類を増やす。
  • アイテムの残り体力を表示する。
  • ゲーム進行につれて火力が上がるはずなのでその分アイテムの体力も上がるようにする。
  • アイテムを破壊したときに発生する効果を明示する。

パッと思いつくだけでもすごい数があります。
順番に実装していきましょう。

今回はこれで終わります。お疲れさまでした。

◇ 関連記事
自機の操作はこちら

  • この記事を書いた人

たげお

ソロゲームもオンラインゲームも対戦ゲームもソシャゲも楽しむ雑食ゲーマー。特筆して好きなジャンルはRPG、FPS。最近は格ゲーにお熱。 思い出のゲームは『FF9』

-Unity, ゲーム開発, 生成AI
-, , ,