Skip to content

スクリプト完成版一覧 ​

第19回まで終わったときの、すべてのスクリプトの完成形です。 授業の途中で「どこかおかしい!」となったときの見比べ用に使ってください。

この教材のコードについて

この教材のコードは Unity 6(6000.0 LTS)向けに書いています。実際のプロジェクトでの動作検証は順次行っています。

置き場所

すべて Assets/_Game/Scripts/ に置きます。ファイル名とクラス名(public class ○○の○○)は必ず同じにしてください。

スクリプト付ける場所作る回
PlayerControllerPlayer第3回(第4回で追加)
PlayerShooterPlayer第5回(第9・15・16回で追加)
BulletPlayerBullet / Laser / Missile プレハブ第5回(第8・15回で追加)
BackgroundScroller背景の隕石など第6回
Enemy敵プレハブ・ボス第7回(第8・9・10・14・17回で追加)
EnemySpawnerEnemySpawner第7回(第11・13回で追加)
PlayerLifePlayer第8回(第9・11・15・19回で追加)
GameManagerGameManager第10回(第11・12・18・19回で追加)
TitleMenuタイトルシーンの TitleMenu第12回(第19回で追加)
EnemyWave波のように動く敵第13回
EnemyShooter弾を撃つ敵第13回
EnemyBulletEnemyBullet プレハブ第13回
PowerUpCapsulePowerUpCapsule プレハブ第14回
PowerUpManagerPlayer第14回(第15・16回で追加)
OptionFollowerOption プレハブ第16回
BossBoss第17回
BossHPBarCanvas の BossHPBar第17回
StageData(アセットとして作る)第18回
StageControllerStageController第18回
CameraShakeMain Camera第19回

PlayerController ​

csharp
using UnityEngine;
using UnityEngine.InputSystem;

// 自機を動かすスクリプト
public class PlayerController : MonoBehaviour
{
    public float speed = 6f;          // 移動の速さ

    // 動ける範囲(第4回)
    public float minX = -8f;
    public float maxX = 8f;
    public float minY = -4.5f;
    public float maxY = 4.5f;

    // 機体を傾ける演出(第4回)
    public Transform bank;            // 傾ける子オブジェクト
    public float bankAngle = 30f;     // 傾ける角度

    InputAction moveAction;

    void Start()
    {
        moveAction = InputSystem.actions.FindAction("Move");
    }

    void Update()
    {
        // 入力を読む(左右が x、上下が y。-1 から 1 の値)
        Vector2 input = moveAction.ReadValue<Vector2>();

        // 動かす
        Vector3 move = new Vector3(input.x, input.y, 0f);
        transform.position += move * speed * Time.deltaTime;

        // 画面からはみ出さないようにする
        Vector3 pos = transform.position;
        pos.x = Mathf.Clamp(pos.x, minX, maxX);
        pos.y = Mathf.Clamp(pos.y, minY, maxY);
        transform.position = pos;

        // 上下に動くときに機体を傾ける
        if (bank != null)
        {
            Quaternion target = Quaternion.Euler(-input.y * bankAngle, 0f, 0f);
            bank.localRotation = Quaternion.Lerp(bank.localRotation, target, 10f * Time.deltaTime);
        }
    }
}

PlayerShooter ​

csharp
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

// 弾の種類(第15回)
public enum ShotType
{
    Normal,
    Double,
    Laser
}

// 弾を撃つスクリプト
public class PlayerShooter : MonoBehaviour
{
    public GameObject bulletPrefab;     // ふつうの弾
    public Transform muzzle;            // 弾が出る場所
    public float fireInterval = 0.15f;  // 連射の間隔(秒)

    public AudioClip shotSound;         // 発射音(第9回)

    public GameObject laserPrefab;      // レーザー(第15回)
    public GameObject missilePrefab;    // ミサイル(第15回)
    public ShotType shotType = ShotType.Normal;
    public bool hasMissile = false;

    public List<Transform> options = new List<Transform>();  // オプション(第16回)

    float timer;
    InputAction attackAction;
    AudioSource audioSource;

    void Start()
    {
        attackAction = InputSystem.actions.FindAction("Attack");
        audioSource = GetComponent<AudioSource>();
    }

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

        if (attackAction.IsPressed() && timer <= 0f)
        {
            Shoot();
            timer = fireInterval;
        }
    }

    void Shoot()
    {
        Fire(muzzle.position);

        // オプションからも撃つ
        foreach (Transform option in options)
        {
            Fire(option.position);
        }

        if (shotSound != null)
        {
            audioSource.PlayOneShot(shotSound);
        }
    }

    // 指定した場所から、今の装備に合わせて弾を出す
    void Fire(Vector3 position)
    {
        if (shotType == ShotType.Laser)
        {
            Instantiate(laserPrefab, position, Quaternion.identity);
        }
        else
        {
            Instantiate(bulletPrefab, position, Quaternion.identity);

            if (shotType == ShotType.Double)
            {
                // 斜め上(45度)にも撃つ
                Instantiate(bulletPrefab, position, Quaternion.Euler(0f, 0f, 45f));
            }
        }

        if (hasMissile)
        {
            // 斜め下(-45度)にミサイル
            Instantiate(missilePrefab, position, Quaternion.Euler(0f, 0f, -45f));
        }
    }
}

Bullet ​

csharp
using UnityEngine;

// 自機の弾(ふつうの弾・レーザー・ミサイル共通)
public class Bullet : MonoBehaviour
{
    public float speed = 15f;      // 速さ
    public float lifeTime = 2f;    // 何秒で消えるか
    public int damage = 1;         // 敵に与えるダメージ(第8回)
    public bool pierce = false;    // 敵をつらぬくか(第15回 レーザー用)

    void Start()
    {
        Destroy(gameObject, lifeTime);
    }

    void Update()
    {
        // 自分の向いている「右」へ進む(回転させると斜めに飛ぶ)
        transform.Translate(Vector3.right * speed * Time.deltaTime);
    }

    // 何かにぶつかったとき(第8回)
    void OnTriggerEnter(Collider other)
    {
        Enemy enemy = other.GetComponent<Enemy>();
        if (enemy != null)
        {
            enemy.TakeDamage(damage);

            if (!pierce)
            {
                Destroy(gameObject);
            }
        }
    }
}

BackgroundScroller ​

csharp
using UnityEngine;

// 背景の飾りを左へ流し、画面の左に出たら右へ戻す
public class BackgroundScroller : MonoBehaviour
{
    public float speed = 1f;        // 流れる速さ
    public float resetX = -14f;     // ここより左へ行ったら
    public float startX = 14f;      // ここへ戻す

    void Update()
    {
        transform.Translate(Vector3.left * speed * Time.deltaTime, Space.World);

        if (transform.position.x < resetX)
        {
            Vector3 pos = transform.position;
            pos.x = startX;
            transform.position = pos;
        }
    }
}

Enemy ​

csharp
using UnityEngine;

// 敵の基本(移動・HP・やられたとき)
public class Enemy : MonoBehaviour
{
    public float speed = 3f;              // 左へ進む速さ
    public int hp = 1;                    // 体力(第8回)
    public GameObject explosionPrefab;    // 爆発(第9回)
    public AudioClip explosionSound;      // 爆発音(第9回)
    public int score = 100;               // 倒したときの点数(第10回)
    public GameObject capsulePrefab;      // 落とすカプセル(第14回)
    public bool isBoss = false;           // ボスかどうか(第17回)

    [HideInInspector] public int maxHp;   // 最初の体力(第17回)

    bool isDead = false;

    void Awake()
    {
        maxHp = hp;
    }

    void Update()
    {
        transform.Translate(Vector3.left * speed * Time.deltaTime, Space.World);

        // 画面の左に出たら消す
        if (transform.position.x < -12f)
        {
            Destroy(gameObject);
        }
    }

    // ダメージを受ける(第8回)
    public void TakeDamage(int damage)
    {
        if (isDead) return;

        hp -= damage;
        if (hp <= 0)
        {
            Die();
        }
    }

    void Die()
    {
        isDead = true;

        GameManager.instance.AddScore(score);

        if (explosionPrefab != null)
        {
            Instantiate(explosionPrefab, transform.position, Quaternion.identity);
        }
        if (explosionSound != null)
        {
            AudioSource.PlayClipAtPoint(explosionSound, Camera.main.transform.position);
        }
        if (capsulePrefab != null)
        {
            Instantiate(capsulePrefab, transform.position, Quaternion.identity);
        }
        if (isBoss)
        {
            CameraShake.instance.Shake(1f, 0.4f);
            GameManager.instance.StageClear();
        }

        Destroy(gameObject);
    }
}

EnemySpawner ​

csharp
using System.Collections;
using UnityEngine;

// 敵を一定時間ごとに出す
public class EnemySpawner : MonoBehaviour
{
    public GameObject[] enemyPrefabs;   // 出す敵(第13回で配列に)
    public float interval = 1.5f;       // 出す間隔(秒)
    public float minY = -4f;            // 出す高さの範囲
    public float maxY = 4f;
    public int formationCount = 1;      // 一度に何機つなげて出すか(第13回)
    public float formationGap = 0.3f;   // つなげて出すときの間隔

    void Start()
    {
        StartCoroutine(SpawnLoop());
    }

    IEnumerator SpawnLoop()
    {
        while (true)
        {
            yield return new WaitForSeconds(interval);

            // ゲームが終わっていたら出さない(第11回)
            if (!GameManager.instance.isPlaying) continue;

            // ランダムに敵の種類と高さを決める
            GameObject prefab = enemyPrefabs[Random.Range(0, enemyPrefabs.Length)];
            float y = Random.Range(minY, maxY);

            for (int i = 0; i < formationCount; i++)
            {
                Vector3 pos = new Vector3(transform.position.x, y, 0f);
                Instantiate(prefab, pos, Quaternion.identity);
                yield return new WaitForSeconds(formationGap);
            }
        }
    }
}

PlayerLife ​

csharp
using System.Collections;
using UnityEngine;

// 自機がやられる・復活する
public class PlayerLife : MonoBehaviour
{
    public GameObject explosionPrefab;    // 爆発(第9回)
    public AudioClip explosionSound;      // 爆発音(第9回)
    public GameObject model;              // 点滅させる見た目(第11回)
    public float invincibleTime = 2f;     // 無敵時間(第11回)

    bool isInvincible = false;

    void OnTriggerEnter(Collider other)
    {
        if (isInvincible) return;

        if (other.CompareTag("Enemy") || other.CompareTag("EnemyBullet"))
        {
            if (other.CompareTag("EnemyBullet"))
            {
                Destroy(other.gameObject);
            }

            // シールドがあれば、シールドが代わりに消える(第15回)
            PowerUpManager powerUp = GetComponent<PowerUpManager>();
            if (powerUp != null && powerUp.UseShield())
            {
                return;
            }

            Hit();
        }
    }

    void Hit()
    {
        if (explosionPrefab != null)
        {
            Instantiate(explosionPrefab, transform.position, Quaternion.identity);
        }
        if (explosionSound != null)
        {
            AudioSource.PlayClipAtPoint(explosionSound, Camera.main.transform.position);
        }

        CameraShake.instance.Shake(0.3f, 0.3f);   // 第19回

        // パワーアップを元に戻す(第15回)
        PowerUpManager powerUp = GetComponent<PowerUpManager>();
        if (powerUp != null)
        {
            powerUp.ResetAll();
        }

        GameManager.instance.LoseLife();

        if (GameManager.instance.lives > 0)
        {
            StartCoroutine(Respawn());
        }
        else
        {
            gameObject.SetActive(false);
        }
    }

    // 少しのあいだ点滅しながら無敵になる
    IEnumerator Respawn()
    {
        isInvincible = true;
        transform.position = new Vector3(-6f, 0f, 0f);

        float time = 0f;
        while (time < invincibleTime)
        {
            model.SetActive(!model.activeSelf);
            yield return new WaitForSeconds(0.1f);
            time += 0.1f;
        }

        model.SetActive(true);
        isInvincible = false;
    }
}

GameManager ​

csharp
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;

// スコア・残機・ゲームの状態を管理する
public class GameManager : MonoBehaviour
{
    public static GameManager instance;   // どこからでも使えるようにする

    public int score = 0;
    public TextMeshProUGUI scoreText;

    public int lives = 3;                 // 残機(第11回)
    public TextMeshProUGUI livesText;
    public GameObject gameOverPanel;
    public bool isPlaying = true;         // ゲーム中かどうか

    public GameObject clearPanel;         // クリア画面(第18回)

    public TextMeshProUGUI highScoreText; // ハイスコア(第19回)

    void Awake()
    {
        instance = this;
    }

    void Start()
    {
        UpdateUI();
    }

    public void AddScore(int amount)
    {
        score += amount;
        UpdateUI();
    }

    public void LoseLife()
    {
        lives--;
        UpdateUI();

        if (lives <= 0)
        {
            GameOver();
        }
    }

    void GameOver()
    {
        isPlaying = false;
        SaveHighScore();
        gameOverPanel.SetActive(true);
    }

    public void StageClear()
    {
        isPlaying = false;
        SaveHighScore();
        clearPanel.SetActive(true);
    }

    // ハイスコアを保存する(第19回)
    void SaveHighScore()
    {
        int best = PlayerPrefs.GetInt("HighScore", 0);
        if (score > best)
        {
            PlayerPrefs.SetInt("HighScore", score);
            PlayerPrefs.Save();
        }
        UpdateUI();
    }

    void UpdateUI()
    {
        scoreText.text = "SCORE " + score.ToString("D6");
        livesText.text = "LIFE " + lives;

        if (highScoreText != null)
        {
            highScoreText.text = "HI " + PlayerPrefs.GetInt("HighScore", 0).ToString("D6");
        }
    }

    // ボタンから呼ぶ
    public void Retry()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    }

    public void BackToTitle()
    {
        SceneManager.LoadScene("Title");
    }
}

TitleMenu ​

csharp
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;

// タイトル画面のボタン
public class TitleMenu : MonoBehaviour
{
    public TextMeshProUGUI highScoreText;   // 第19回

    void Start()
    {
        if (highScoreText != null)
        {
            highScoreText.text = "HI-SCORE " + PlayerPrefs.GetInt("HighScore", 0).ToString("D6");
        }
    }

    public void StartGame()
    {
        SceneManager.LoadScene("Game");
    }

    public void QuitGame()
    {
        Application.Quit();
    }
}

EnemyWave ​

csharp
using UnityEngine;

// 波のように上下しながら進む(Enemy と一緒に付ける)
public class EnemyWave : MonoBehaviour
{
    public float amplitude = 1.5f;   // 波の高さ
    public float frequency = 1.5f;   // 波の細かさ

    float startY;

    void Start()
    {
        startY = transform.position.y;
    }

    void Update()
    {
        // x の位置で高さを決めるので、つなげて出すと「ヘビ」のような編隊になる
        Vector3 pos = transform.position;
        pos.y = startY + Mathf.Sin(pos.x * frequency) * amplitude;
        transform.position = pos;
    }
}

EnemyShooter ​

csharp
using UnityEngine;

// 自機をねらって弾を撃つ(Enemy と一緒に付ける)
public class EnemyShooter : MonoBehaviour
{
    public GameObject bulletPrefab;    // EnemyBullet プレハブ
    public float interval = 2f;        // 撃つ間隔(秒)

    float timer;

    void Start()
    {
        timer = interval;
    }

    void Update()
    {
        timer -= Time.deltaTime;
        if (timer <= 0f)
        {
            Shoot();
            timer = interval;
        }
    }

    void Shoot()
    {
        GameObject player = GameObject.FindWithTag("Player");
        if (player == null) return;   // 自機がいなければ撃たない

        // 自機への向きを角度にする
        Vector3 dir = player.transform.position - transform.position;
        float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;

        Instantiate(bulletPrefab, transform.position, Quaternion.Euler(0f, 0f, angle));
    }
}

EnemyBullet ​

csharp
using UnityEngine;

// 敵の弾(向いている方向へまっすぐ進む)
public class EnemyBullet : MonoBehaviour
{
    public float speed = 5f;
    public float lifeTime = 5f;

    void Start()
    {
        Destroy(gameObject, lifeTime);
    }

    void Update()
    {
        transform.Translate(Vector3.right * speed * Time.deltaTime);
    }
}

PowerUpCapsule ​

csharp
using UnityEngine;

// パワーアップカプセル
public class PowerUpCapsule : MonoBehaviour
{
    public float speed = 1.5f;

    void Start()
    {
        Destroy(gameObject, 15f);
    }

    void Update()
    {
        transform.Translate(Vector3.left * speed * Time.deltaTime, Space.World);
        transform.Rotate(0f, 180f * Time.deltaTime, 0f);   // くるくる回す
    }

    void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Player"))
        {
            PowerUpManager powerUp = other.GetComponent<PowerUpManager>();
            if (powerUp != null)
            {
                powerUp.AddCapsule();
            }
            Destroy(gameObject);
        }
    }
}

PowerUpManager ​

csharp
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UI;

// グラディウス風パワーアップゲージ
public class PowerUpManager : MonoBehaviour
{
    // ゲージのマス(0:SPEED 1:MISSILE 2:DOUBLE 3:LASER 4:OPTION 5:?)
    public Image[] gaugeSlots;
    public Color normalColor = new Color(0.2f, 0.2f, 0.5f);
    public Color selectedColor = Color.yellow;

    // パワーアップの中身(第15回)
    public PlayerController controller;
    public PlayerShooter shooter;
    public float speedUpAmount = 1.5f;
    public float maxSpeed = 12f;
    public GameObject shield;

    // オプション(第16回)
    public GameObject optionPrefab;
    public int maxOptions = 2;

    int cursor = -1;          // 光っているマス(-1 はどこも光っていない)
    float startSpeed;
    InputAction powerUpAction;

    void Start()
    {
        powerUpAction = InputSystem.actions.FindAction("PowerUp");
        startSpeed = controller.speed;
        UpdateGauge();
    }

    void Update()
    {
        if (powerUpAction.WasPressedThisFrame() && cursor >= 0)
        {
            Activate(cursor);
            cursor = -1;
            UpdateGauge();
        }
    }

    // カプセルを取ったとき:光るマスを1つ右へ
    public void AddCapsule()
    {
        cursor++;
        if (cursor >= gaugeSlots.Length)
        {
            cursor = 0;
        }
        UpdateGauge();
    }

    void Activate(int index)
    {
        switch (index)
        {
            case 0: // SPEED UP
                controller.speed = Mathf.Min(controller.speed + speedUpAmount, maxSpeed);
                break;
            case 1: // MISSILE
                shooter.hasMissile = true;
                break;
            case 2: // DOUBLE
                shooter.shotType = ShotType.Double;
                break;
            case 3: // LASER
                shooter.shotType = ShotType.Laser;
                break;
            case 4: // OPTION
                AddOption();
                break;
            case 5: // ?(シールド)
                shield.SetActive(true);
                break;
        }
    }

    // オプションを増やす(第16回)
    void AddOption()
    {
        if (shooter.options.Count >= maxOptions) return;

        // 1つ目は自機を、2つ目は1つ目のオプションを追いかける
        Transform target = transform;
        if (shooter.options.Count > 0)
        {
            target = shooter.options[shooter.options.Count - 1];
        }

        GameObject option = Instantiate(optionPrefab, target.position, Quaternion.identity);
        option.GetComponent<OptionFollower>().target = target;
        shooter.options.Add(option.transform);
    }

    // シールドがあれば使う。使えたら true を返す
    public bool UseShield()
    {
        if (shield.activeSelf)
        {
            shield.SetActive(false);
            return true;
        }
        return false;
    }

    // やられたときに全部元に戻す
    public void ResetAll()
    {
        controller.speed = startSpeed;
        shooter.shotType = ShotType.Normal;
        shooter.hasMissile = false;
        shield.SetActive(false);

        foreach (Transform option in shooter.options)
        {
            Destroy(option.gameObject);
        }
        shooter.options.Clear();

        cursor = -1;
        UpdateGauge();
    }

    void UpdateGauge()
    {
        for (int i = 0; i < gaugeSlots.Length; i++)
        {
            if (i == cursor)
            {
                gaugeSlots[i].color = selectedColor;
            }
            else
            {
                gaugeSlots[i].color = normalColor;
            }
        }
    }
}

OptionFollower ​

csharp
using System.Collections.Generic;
using UnityEngine;

// 自機(または前のオプション)が通った道をあとから追いかける
public class OptionFollower : MonoBehaviour
{
    public Transform target;    // 追いかける相手
    public int delay = 12;      // どれくらい遅れてついてくるか

    Queue<Vector3> history = new Queue<Vector3>();
    Vector3 lastTargetPos;

    void Start()
    {
        lastTargetPos = target.position;
    }

    void LateUpdate()
    {
        // 相手が動いたときだけ記録する(止まるとオプションも止まる)
        if (target.position != lastTargetPos)
        {
            history.Enqueue(target.position);
            lastTargetPos = target.position;

            if (history.Count > delay)
            {
                transform.position = history.Dequeue();
            }
        }
    }
}

Boss ​

csharp
using System.Collections;
using UnityEngine;

// ボスの動きと攻撃(Enemy と一緒に付ける。Enemy の speed は 0 にする)
public class Boss : MonoBehaviour
{
    public float enterX = 6f;          // ここまで入ってきたら戦闘開始
    public float enterSpeed = 2f;
    public float moveRange = 3f;       // 上下に動く幅
    public float moveSpeed = 1f;
    public GameObject bulletPrefab;    // EnemyBullet プレハブ
    public Transform muzzle;           // 弾が出る場所

    enum State
    {
        Enter,   // 登場中
        Fight    // 戦闘中
    }

    State state = State.Enter;
    float fightTime = 0f;

    void Update()
    {
        switch (state)
        {
            case State.Enter:
                transform.Translate(Vector3.left * enterSpeed * Time.deltaTime, Space.World);
                if (transform.position.x <= enterX)
                {
                    state = State.Fight;
                    StartCoroutine(AttackLoop());
                }
                break;

            case State.Fight:
                fightTime += Time.deltaTime;
                Vector3 pos = transform.position;
                pos.y = Mathf.Sin(fightTime * moveSpeed) * moveRange;
                transform.position = pos;
                break;
        }
    }

    // 攻撃パターンをくり返す
    IEnumerator AttackLoop()
    {
        while (true)
        {
            // パターン1:扇形に5発 × 3回
            for (int i = 0; i < 3; i++)
            {
                FireSpread();
                yield return new WaitForSeconds(0.6f);
            }
            yield return new WaitForSeconds(1f);

            // パターン2:自機ねらいの連射
            for (int i = 0; i < 8; i++)
            {
                FireAimed();
                yield return new WaitForSeconds(0.15f);
            }
            yield return new WaitForSeconds(1f);
        }
    }

    void FireSpread()
    {
        for (int angle = -30; angle <= 30; angle += 15)
        {
            // 180度 = 左向き
            Instantiate(bulletPrefab, muzzle.position, Quaternion.Euler(0f, 0f, 180f + angle));
        }
    }

    void FireAimed()
    {
        GameObject player = GameObject.FindWithTag("Player");
        if (player == null) return;

        Vector3 dir = player.transform.position - muzzle.position;
        float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
        Instantiate(bulletPrefab, muzzle.position, Quaternion.Euler(0f, 0f, angle));
    }
}

BossHPBar ​

csharp
using UnityEngine;

// ボスの体力バー(Fill の横の大きさを変える)
public class BossHPBar : MonoBehaviour
{
    public Enemy boss;
    public RectTransform fill;   // Pivot X を 0(左はし)にしておく

    void Update()
    {
        if (boss == null)
        {
            // ボスが倒されたらバーを消す
            gameObject.SetActive(false);
            return;
        }

        float rate = (float)boss.hp / boss.maxHp;
        fill.localScale = new Vector3(rate, 1f, 1f);
    }
}

StageData ​

csharp
using UnityEngine;

// 「何秒に、どの敵を、どこに、何機」出すかの1行分
[System.Serializable]
public class SpawnInfo
{
    public float time;            // ステージ開始から何秒後か
    public GameObject enemyPrefab;
    public float y;               // 出す高さ
    public int count = 1;         // 何機つなげて出すか
    public float gap = 0.3f;      // つなげるときの間隔
}

// ステージの「敵の出現表」
[CreateAssetMenu(fileName = "StageData", menuName = "Shooting/Stage Data")]
public class StageData : ScriptableObject
{
    public SpawnInfo[] spawns;    // 時間の早い順に並べる
    public float bossTime = 60f;  // ボスが出てくる時間
}

StageController ​

csharp
using System.Collections;
using UnityEngine;

// StageData の表どおりに敵を出し、最後にボスを出す
public class StageController : MonoBehaviour
{
    public StageData stageData;
    public GameObject boss;          // シーンに置いて非表示にしておいたボス
    public GameObject bossHPBar;     // 非表示にしておいた体力バー
    public GameObject warningText;   // 「WARNING!!」の文字

    void Start()
    {
        StartCoroutine(StageFlow());
    }

    IEnumerator StageFlow()
    {
        float time = 0f;

        foreach (SpawnInfo info in stageData.spawns)
        {
            // 次の出現時間まで待つ
            yield return new WaitForSeconds(info.time - time);
            time = info.time;
            StartCoroutine(SpawnGroup(info));
        }

        // ボスの時間まで待つ
        yield return new WaitForSeconds(stageData.bossTime - time);

        warningText.SetActive(true);
        yield return new WaitForSeconds(3f);
        warningText.SetActive(false);

        boss.SetActive(true);
        bossHPBar.SetActive(true);
    }

    IEnumerator SpawnGroup(SpawnInfo info)
    {
        for (int i = 0; i < info.count; i++)
        {
            if (!GameManager.instance.isPlaying) yield break;

            Vector3 pos = new Vector3(transform.position.x, info.y, 0f);
            Instantiate(info.enemyPrefab, pos, Quaternion.identity);
            yield return new WaitForSeconds(info.gap);
        }
    }
}

CameraShake ​

csharp
using System.Collections;
using UnityEngine;

// カメラをゆらす
public class CameraShake : MonoBehaviour
{
    public static CameraShake instance;

    Vector3 startPos;

    void Awake()
    {
        instance = this;
        startPos = transform.localPosition;
    }

    public void Shake(float duration, float strength)
    {
        StopAllCoroutines();
        StartCoroutine(ShakeRoutine(duration, strength));
    }

    IEnumerator ShakeRoutine(float duration, float strength)
    {
        float time = 0f;
        while (time < duration)
        {
            Vector2 offset = Random.insideUnitCircle * strength;
            transform.localPosition = startPos + new Vector3(offset.x, offset.y, 0f);
            time += Time.deltaTime;
            yield return null;
        }
        transform.localPosition = startPos;
    }
}