Unity ゲーム開発 生成AI

【ゲーム開発】AI活用で進める独学個人ゲーム開発 #4_銃弾の実装

はじめに

前回は自機のアニメーションを実装しました。


移動すると傾きます。



今回はシューティングゲームに必須の"銃弾を発射する"という機能の実装を行っていきます。

銃弾の実装

銃弾をPrefabにして発射したいです。

銃弾をPrefabにして発射する方法を、Unity 2Dシューティング向けに解説します。

🎯 目的:
ボタンを押すと銃弾Prefabが発射され、一定時間後に消えるようにする

Step 1:銃弾オブジェクトの作成

  1. Hierarchy で 2D > Sprite を作成し、名前を Bullet に変更
  2. スプライト画像を設定(見た目)
  3. BoxCollider2D または CircleCollider2D を追加
  4. Rigidbody2D を追加
    → Gravity Scale = 0 に設定(重力で落ちないように)

Step 2:スクリプトを作成(Bullet.cs)

using UnityEngine;

public class Bullet : MonoBehaviour
{
    public float speed = 10f;
    public float lifeTime = 2f;

    void Start()
    {
        Destroy(gameObject, lifeTime); // 2秒後に自動消滅
    }

    void Update()
    {
        transform.Translate(Vector2.right * speed * Time.deltaTime); // 右方向に移動
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        Destroy(gameObject); // 何かに当たったら破壊
    }
}

Step 3:Prefab化
Project ウィンドウの Prefabs フォルダへ、Bullet オブジェクトをドラッグして登録

Step 4:発射スクリプトの作成(PlayerShoot.cs)

using UnityEngine;

public class PlayerShoot : MonoBehaviour
{
    public GameObject bulletPrefab;
    public Transform firePoint;
    public float fireRate = 0.5f;

    private float nextFireTime = 0f;

    void Update()
    {
        if (Input.GetButton("Fire1") && Time.time >= nextFireTime)
        {
            Instantiate(bulletPrefab, firePoint.position, firePoint.rotation);
            nextFireTime = Time.time + 1f / fireRate;
        }
    }
}

Step 5:Playerへの設定

  • 空のオブジェクトを Player の子として作成 → 名前を FirePoint
  • PlayerShoot スクリプトを Player にアタッチ
  • Inspector で Bullet Prefab に作成した銃弾プレハブを指定
  • Fire Point に FirePointオブジェクトを指定

Step 6:テスト
▶ 実行 → 左クリック(Fire1)で右方向に銃弾が発射され、2秒後に消えます

💡 方向が逆なら Vector2.rightVector2.left に変更するなど調整してください。

順番にやっていきましょう

Step1:銃弾オブジェクトの作成

これは自機の実装でも行った操作ですね。
アセットに含まれている銃弾用の画像を使います。


細長いやつ。これを横向きに飛ぶようにしたい。

Step 2:スクリプトを作成

Bulletというスクリプトを作ってコードをぶち込みます。
見た感じどのようなコードかは分かりますが、いくつか気になることがあります。

①2秒で消滅させる設定について
画面外に出た際に残らないようにしていると思われますが、銃弾の速度を遅くした場合に画面内で消滅しそうな設定です。カメラ外に消えたときに消滅するように変更できるか試したいです。

上記のスクリプトに関して、時間ではなく、カメラ外に消えたときに消滅するように変更できませんか?

はい、時間経過ではなく「カメラの外に出たら破壊」に変更できます。

Unityではこれを OnBecameInvisible() を使って簡単に実現できます。すでにあなたのスクリプトは正しく実装されています。以下は確認済みの修正済コードです👇

using UnityEngine;

public class Bullet : MonoBehaviour
{
    public float speed = 20f;

    void Update()
    {
        // 銃弾を前方に移動させる
        transform.Translate(Vector3.right * speed * Time.deltaTime);
    }

    void OnCollisionEnter2D(Collision2D collision)
    {
        // 衝突時に銃弾を破壊する
        Destroy(gameObject);
    }

    void OnBecameInvisible()
    {
        // カメラから見えなくなったら破壊
        Destroy(gameObject);
    }
}

補足

  • このメソッドはRendererコンポーネントに依存しており、「SpriteRenderer」などが付いていないと反応しません。
  • Sceneビューでは反応せず、Gameビューのカメラから外れた場合にのみ反応します。

👍 無駄なオブジェクトを自動で除去できる便利な方法です!

知らない機能が出てきました。

OnBecameInvisivle
任意のカメラで表示されなくなったときに呼び出される
とUnity公式ページに書いてありました。いい感じの機能があったものですね。


②Transformでの移動
Rigidbodyを付けてある自機はダメだったような気がするのですが、今回はTranslateで移動させていいものなのでしょうか。

銃弾にはすでにRigidbodyを付けていますが、Translateで移動させても問題ありませんか?

重要なポイントです。
すでに Rigidbody2D を付けている場合は Transform.Translate() は基本的に使わない方が良いです。

理由:Rigidbody2Dがあるなら物理ベースの移動が正解
Rigidbody2D がアタッチされているオブジェクトを Transform.Translate() で動かすと、以下の問題が発生しやすくなります:

問題 内容
衝突判定が正しく発生しない 物理エンジンが無視されるため、すり抜けやめり込みが起きる
将来的な不具合の原因 OnCollisionEnter2D や OnTriggerEnter2D が発生しない可能性あり

正しい移動方法(Rigidbody2D使用時)
FixedUpdate() で velocity を設定する方法が推奨です。

public class Bullet : MonoBehaviour
{
    public float speed = 20f;
    private Rigidbody2D rb;

    void Start()
    {
        rb = GetComponent();
        rb.velocity = Vector2.right * speed;  // 右方向へ一定速度で移動
    }

    void OnBecameInvisible()
    {
        Destroy(gameObject);  // 画面外に出たら削除
    }

    void OnTriggerEnter2D(Collider2D collision)
    {
        Destroy(gameObject);  // 衝突時に削除(Trigger使用)
    }
}

⚠️ 注意:Collider は isTrigger = true にしておくと処理がスムーズです。

ダメなんかい。まぁ気づけたのでヨシ
スクリプトは上記のものに差し替えましたがここで問題が発生しました。


メソッド 'Component.GetComponent()' の型引数を使い方から推論することはできません。型引数を明示的に指定してください。CS0411

なにやらエラーが発生しました。

このエラーはなんですか?

このエラーは、以下のコード行に型指定が抜けているために発生しています:

rb = GetComponent();

エラー内容(CS0411)
GetComponent<T>() の型引数を明示的に指定してください」

C#ではジェネリックメソッドの <T> の型が省略できない場合があり、Unityの GetComponent() もその1つです。

修正方法
たとえば Rigidbody2D を取得したいのであれば、以下のように型を明示的に指定します:

rb = GetComponent<Rigidbody2D>();

これでエラーは解消されます。

Unityで扱うGetComponet()というメソッドに関しては()前に取得したいコンポーネントを明示する必要があるようです。

これでスクリプトの設定が一旦終わりました。

C#メモ
コード 意味
void OnBecameInvisible() カメラ外に出た時に処理を実行
GetComponent() ()の前に取得したいコンポーネントを明示する(例:Rigidbody2D)

Step3:Prefab化
作ったBulletのオブジェクトをProjectにドラッグアンドドロップするだけです。

Step4:発射スクリプトの作成
Void updateの中身がどういう指示をしているのかよく分かりません……

void Update() の中を解説してください。

はい、void Update() の中身を1行ずつ丁寧に解説します。

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

🧠 Update() とは?
Unityのメインループの一部で、毎フレーム自動的に呼び出される関数です。
入力の監視や、アニメーション処理、時間経過チェックなどに適しています。

各行の解説:

if (Input.GetButton("Fire1") && Time.time >= nextFireTime)
Input.GetButton("Fire1")
 「Fire1」ボタン(デフォルトではマウス左クリックまたは Ctrl キー)が押されているか判定します。

Time.time >= nextFireTime
 現在の時間が、次に発射できる時刻を超えているか判定します。
 これにより連射速度の制限が可能になります。

➡ この if 文全体では、「射撃ボタンが押されていて、かつ次の発射タイミングを過ぎていれば」という意味になります。

Shoot();
・発射処理を行う関数です。通常、Instantiate() によって銃弾のPrefabを生成します。

nextFireTime = Time.time + 1f / fireRate;
・次に発射できる時刻を再設定します。
 たとえば fireRate = 2 なら 0.5秒ごとに発射可能になります(= 1 ÷ 2)。

この一行で、連射間隔をコントロールしています。

連射間隔はこんな計算をしなくてもfirerataだけでよさそうな感じがします。

なぜ nextFireTime が必要なのですか?
fireRate だけで管理できそうに見えるのですが。

とても良い質問です。
一見「fireRate だけで管理できそう」に思えますが、連射の間隔を正しくコントロールするには nextFireTime のような「発射可能な次の時間」を記録しておく必要があります。

🔍 そもそも fireRate の目的とは?

public float fireRate = 2f;  // 1秒間に2発撃てる

→ これは「1秒間に何発撃てるか」を意味します。
1f / fireRate が「1発ごとの間隔(秒)」になります。

fireRate だけで管理したらどうなる?
例えば以下のようなコードを考えてみましょう:

float timer;

void Update()
{
    timer += Time.deltaTime;

    if (Input.GetButton("Fire1") && timer >= 1f / fireRate)
    {
        Shoot();
        timer = 0f;
    }
}

これでも「一定間隔で発射」はできますが、以下のような問題が出ます:

🆚 timer 管理と nextFireTime 管理の違い

比較項目 timer += Time.deltaTime nextFireTime = Time.time + X
精度 少しずれる(フレームごと加算) 絶対時刻なのでブレがない
安定性 長時間で誤差が溜まりやすい Time.time を基準に一貫性あり
意図の明確さ 「経過時間」で判断 「次に撃てる時刻」が明確

なぜ nextFireTime が優れているか?
Time.time は「ゲーム開始からの経過時間(秒)」
nextFireTime に「次に撃てる絶対時刻」を保存しておけば、

if (Time.time >= nextFireTime)

→ この条件だけで「もう撃っていいか」を正確に判定できます。

🔁 結果:ズレがない安定した連射が実現できます。

Time.deltaTimeが出てくるとフレームで計算するためズレが生じやすいということでしょうか?
この後もいくつか質問してみたものの、一定間隔で発生するイベントはTime.timeを使うといいと言われました。
いまいち理解できない感覚ですが、今回はこれで行きます。

Step 5:Playerへの設定


FirePoint(銃弾が生み出される位置)を子オブジェクトに

PlayerにPlayerShootスクリプトをアタッチして要素を入れました。

Step 6:テスト

これでテストしてみます。


なんだこれ……

おかしいことだらけですが確認していきます。

  • 弾が遠く離れたところに出ている
  • 弾がその場に留まっている
  • 弾が上向きに出ている
  • 発射間隔(時間)が広すぎる

とりあえず画面を見た感じこんな異常が出ています。
1つずつ潰していきましょう。

銃弾が自機の目の前から出ずに、画面右側に突如現れます

了解です。
「銃弾が自機の前からではなく、画面右側に突如現れる」場合、以下の原因が考えられます。

🔍 主な原因と対策

1. firePoint の位置が正しく設定されていない
PlayerShoot スクリプトではこのように発射位置を指定しています:

Instantiate(bulletPrefab, firePoint.position, firePoint.rotation);

🔧 確認すること:
・Hierarchy 上の firePoint オブジェクトが自機の 子オブジェクト になっているか?
firePoint の Transform.position が 自機の正面(右方向) にあるか?

💡 修正方法:

  1. Player オブジェクトの中に空の GameObject を作成(名前例:FirePoint
  2. この FirePoint を Player の子にする
  3. 銃口の位置(右方向、つまり +X 側)に移動
  4. PlayerShoot スクリプトの firePoint フィールドに、この GameObject をアサイン

これで、自機の正面から正しく銃弾が発射されるようになります。

Firepointを付けてくださいと言われただけなのでなにも設定は触っていませんでした。

Instantiate(bulletPrefab, firePoint.position, firePoint.rotation)は
bulletPrefabに設定されているオブジェクトを
firepointに設定されているポジション
firePointに設定されている回転
で生成するという役割らしいです。

また、PlayerShootスクリプトはPlayerにアタッチされているので、
Playerを原点とした位置を設定する模様


この設定変更で自機の目の前から弾が生成されるようになりました。

C#メモ
コード 意味
Instantiate(Object,position,rotation) オブジェクトを位置と回転を設定して生成する

ここで同時にFirePointのRotationが z = 90 になっているのを確認しました。
これを z = 0 にすることで上向きになっているのも解消されました。

続いて、銃弾が現れた位置から動かずその場に留まり続けます

トラブルシュートチェックリスト
銃弾が動かない原因として、以下の点を確認してみてください:

チェック項目 確認内容
Rigidbody2D 付いているか?
Gravity Scale は 0 になっているか?
Collider2D 付いているか?
IsTrigger は ON になっているか?
Bullet.cs 正しくアタッチされているか?
velocity rb.velocity = Vector2.right になっているか?
または transform.up にしてみたか?
見た目 プレハブの向きが右を向いているか?
Z回転が -90 になっていないか?

上記を順番に確認することで、原因を絞り込めると思います。
特に Rigidbody2D の velocity やプレハブの向きが原因になりやすいです。

確認していくとBullet.csがどこにもアタッチされていませんでした。
Step2でスクリプトを生成しろと言われたものの生成して終わっていましたね。1から10まで説明されないと分からない初心者だということを最初に教え込まないといけませんかね?

Bullet.csはどこにアタッチするものですか?

非常に重要なポイントです。
Bullet.cs は 銃弾プレハブ(bulletPrefab) にアタッチします。

Bullet.cs をアタッチすべき場所
🎯 銃弾として生成されるオブジェクト(Prefab) にアタッチします。

🔧 具体的な手順

  1. Project ビューで bulletPrefab を選択(例:Assets/Prefabs/Bullet.prefab)
  2. Inspector ウィンドウを確認
  3. Add Component」ボタンをクリック
  4. Bullet と検索して Bullet.cs を追加

これで、銃弾が生成されたときに Bullet.csStart()Update() が正しく実行されるようになります。

アタッチすることで弾が飛んでいくようになりました。

最後に銃弾の発射間隔ですがFirerateを0.5から2に変更しました。
1秒に0.5発→1秒に2発 の変更です。

銃弾が飛んでいくようになりました!

色々と問題は発生しましたが思い通りの挙動になって一安心です。

次回は敵の動きを設定していきます。
大まかには

  • 弾が当たるとHPが減少
  • HPが0になれば消滅
  • 自機に向かって飛んでくる
  • 自機に当たると自機を減らして消滅

こんな感じでしょうか

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

  • この記事を書いた人

たげお

ソロゲームもオンラインゲームも対戦ゲームもソシャゲも楽しむ雑食ゲーマー。特筆して好きなジャンルはRPG、FPS。最近は格ゲーにお熱。 ゲーム好きが高じて自分で作りたくなった。

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