シューティングゲーム作成⑦(敵の移動パターン/攻撃パターンの追加)

C#

はじめに

前バージョンまでは、敵は上から下に移動するだけの単調な動作のみでした。今回は、敵の攻撃パターンと移動パターンを追加しました。

  • 敵の移動を3パターン(直進 / サイン波 / Stop&Dash)追加
  • 敵の攻撃を5パターン(狙い撃ち / 真下 / 扇状 / 誘導 / 全方位 )追加

ここまでの成果

ゲームの仕様

新しく追加した仕様には「(新)」を付けています。
変更した仕様に「(変)」を付けています。

  • プレイヤーHP設定:初期HPは3
  • プレイヤーHP表示:画面右上に「HP:X」を常時表示する
  • ゲームタイトル表示:開始前画面にゲームタイトルを表示する
  • ゲーム開始:開始前に「TOUCH TO START」を点滅表示し、タップで開始する
  • 敗北条件:HPが0になった時点で「GAME OVER TOUCH TO RESTART」UIを表示し、ゲームを停止する
  • 操作方法:WASDキー または 十字キー(上下左右の移動)
  • 攻撃方法:自動発射
  • 敵キャラ実装数:100体(e001~e100)
  • ボス実装数:9体(b001~b009)
  • 敵キャラHP
    • e001〜e010:HP 1 , e011〜e020:HP 10 , e021〜e030:HP 15 , e031〜e040:HP 20 ,  e041〜e050:HP 25 ,,,
  • 敵キャラの移動:画面上から下方向に直進する
  • スコア:画面左上に「Score:X」を常時表示する
    • e001~e010:100点 , e011~e021:200点 , e021~e031:300点 ,,,
  • 背景仕様:Wave開始時に、Waveに対応する背景画像へ切り替える(フェード演出あり)
  • 撃破エフェクト:敵キャラ撃破時にエフェクトを表示する
  • 撃破エフェクト:ボス撃破時にエフェクトを表示する
  • エフェクト一覧
エフェクト発生タイミング 画像
敵撃破
ボス撃破
  •  ステータス:アイテムの取得で強化できるステータス
    • 弾速:上限無し
    • 連射速度:発射間隔は0.05秒未満にならないように設計
    • 攻撃力:上限無し
    • 移動速度:最大10(初期2から上昇、上限あり)
    • HP:上限は3
  • アイテム一覧
アイテム名 画像 効果 上限
青い札 取得ごとに弾の速度加算 上限無し
手裏剣 発射間隔を短縮 0.05秒未満にならない
赤い勾玉 ダメージ量をアップ 上限無し
韋駄天草履 取得ごとにプレイヤーの速度加算 最大10
プレイヤーのHPを1回復 最大3
直進弾 前方に直進する弾
Lv1:単発
Lv2:2連装
Lv3:3連装
Lv4:4連装
Lv5:5連装
レベル5
放射弾 扇状に広がる弾
Lv1:3方向
Lv2:3方向、攻撃範囲を拡大
Lv3:5方向
Lv4:5方向、弾サイズをアップ
Lv5:7方向
レベル5
誘導弾 最も近い敵を追尾する弾
Lv1:単発
Lv2:2発同時
Lv3:3発同時
Lv4:4発同時
Lv5:5発同時
レベル5
  • Waveの仕様
    • Waveは 1~100 まで進行
    • Waveは基本的に以下の流れ
      • 敵キャラが湧くフェーズ
      • Waveボスが湧くフェーズ
    • Waveボスを倒すと次のWaveに進む
    • 各Waveの撃破ノルマ:10+Wave×2(最大で100)
    • 敵キャラの同時出現上限:5+(Wave/10)
    • 各Waveの敵キャラの出現ID範囲:(Wave-5)~Wave
      • Wave30の場合、敵キャラの出現IDの範囲は25~30
    • 通常ボスを設定
      • Wave番号の敵キャラが通常ボス
      • 通常ボスのサイズ:敵キャラの2倍
      • 通常ボスのHP:敵キャラHP×Wave数×5
    •  特別ボスを設定
      • 出現するWave:5の倍数のWave
      • 出現するボスID:b00{Wave/5}
      • ボスの基本HP:1
      • ボスのHP倍率:基本HP×ボス回数×10
  • 背景画像の仕様
    • 47都道府県における各地域の特徴を加味した背景画像
    • 地域ごとに背景画像を2枚づつ用意
    • 東京、京都、大阪の主要都市は背景画像を3枚づつ用意
  • (新)敵の攻撃パターン
攻撃パターン 挙動詳細
狙い撃ち プレイヤーの現在位置を正確に狙う。止まっていると被弾する。
真下 真下(または進行方向)へ弾を垂れ流す。弾の壁を作ることで、プレイヤーの「横移動」を制限する。
扇状 自機方向を中心に扇状(3/5/7方向)に発射。正面を危険地帯とし、「大きく回り込む」動きを誘発する。
誘導 発射後もプレイヤーを追いかける弾、または時間差攻撃。大きく動いて「弾を誘導して消す」動きが必要。
全方位 画面内に弾をばら撒く。
  • (新)敵の移動パターン
移動パターン 挙動詳細
直進 一定速度で下へ移動する。
サイン波 左右に揺れながら下へ落ちる。
Stop&Dash 画面の中央より少し上まで降下し、1秒停止、そのあとに加速落下する。

敵の移動パターン

<移動パターンの主要素>

カテゴリ コード内の要素 役割
移動種別の定義 Assets/Scripts/EnemyData.cs
EnemyMovementType
移動パターンの種類
移動パラメータ movementMagnitude/frequency/moveSpeed 移動の幅・速さ・基本速度
移動の実行 Assets/Scripts/EnemyController.cs パターンごとの移動処理

<実際のコード(移動タイプとパラメータ定義)>

// Assets/Scripts/EnemyData.cs
public enum EnemyMovementType
{
    Straight,       // 直進
    Sine,           // サイン波
    StopAndDash     // 停止&突進
}

[Header("移動パターン設定")]
public EnemyMovementType movementType = EnemyMovementType.Straight;

[Tooltip("動きの幅 (Sine/Zigzag用)")]
public float movementMagnitude = 2.0f;

[Tooltip("動きの速さ/周波数 (Sine/Zigzag用)")]
public float movementFrequency = 2.0f;

public float moveSpeed; // 基本移動速度

<実際のコード(EnemyControllerで移動を実行)>

EnemyController.Move() で movementType に応じて分岐します。

// Assets/Scripts/EnemyController.cs
protected virtual void Move()
{
    if (isDead) return;

    timeAlive += Time.deltaTime;

    switch (movementType)
    {
        case EnemyMovementType.Straight:
            transform.Translate(Vector3.down * moveSpeed * Time.deltaTime);
            break;

        case EnemyMovementType.Sine:
            float x = startPosition.x
                      + Mathf.Sin(timeAlive * movementFrequency) * movementMagnitude;
            transform.position = new Vector3(
                x,
                transform.position.y - moveSpeed * Time.deltaTime,
                0
            );
            break;

        case EnemyMovementType.StopAndDash:
            // 一定位置まで降下→停止→高速落下
            // (stopTimer/isStopping/hasDashed を使用)
            ...
            break;

        default:
            transform.Translate(Vector3.down * moveSpeed * Time.deltaTime);
            break;
    }
}

敵の攻撃パターン

<攻撃パターンの主要素>

カテゴリ コード内の要素 役割
攻撃種別の定義 Assets/Scripts/AttackPattern.cs 攻撃パターンの種類
攻撃パラメータ EnemyData
fireInterval/bulletSpeed/bulletCount
発射間隔・弾速・弾数・拡散角など
攻撃の実行 EnemyController.HandleAttack() 発火条件→パターン分岐
弾の共通挙動 Bullet.cs 直進弾(向き固定にも対応)
誘導弾 HomingBullet.cs ターゲットへ旋回しながら移動

<実際のコード(攻撃パターン定義)>

// Assets/Scripts/AttackPattern.cs
public enum AttackPattern
{
    None,
    Sniper,   // 自機狙い
    Wall,     // 真下へ直進
    Spread,   // 扇状拡散(N-way)
    Homing,   // 誘導弾
    Storm,    // 全方位(ランダムオフセット)
}

<実際のコード(攻撃の発火条件:範囲+間隔)>

// Assets/Scripts/EnemyController.cs
protected virtual void HandleAttack()
{
    if (myData == null || myData.attackPattern == AttackPattern.None) return;

    // 攻撃範囲(Y)チェック
    if (transform.position.y > myData.attackMaxY
        || transform.position.y < myData.attackMinY) return; fireTimer += Time.deltaTime; if (fireTimer >= myData.fireInterval)
    {
        Shoot();
        fireTimer = 0f;
    }
}

<実際のコード(パターン分岐:Shoot)>

// Assets/Scripts/EnemyController.cs
protected virtual void Shoot()
{
    if (playerTransform == null
        && myData.attackPattern != AttackPattern.Wall
        && myData.attackPattern != AttackPattern.Storm) return;

    switch (myData.attackPattern)
    {
        case AttackPattern.Sniper: ShootSniper(); break;
        case AttackPattern.Wall:   ShootWall();   break;
        case AttackPattern.Spread: ShootSpread(); break;
        case AttackPattern.Homing: ShootHoming(); break;
        case AttackPattern.Storm:  ShootStorm();  break;
        case AttackPattern.Tricky: ShootTricky(); break;
    }
}

<実際のコード(各攻撃パターンの中身:抜粋)>

  1. Sniper:自機狙い1発
    private void ShootSniper()
    {
        Vector3 direction = (playerTransform.position - transform.position).normalized;
        CreateBullet(direction, myData.bulletSpeed);
    }
    
  2. Wall:真下へ直進
    private void ShootWall()
    {
        CreateBullet(Vector3.down, myData.bulletSpeed);
    }
    
  3. Spread:自機方向を中心にN-way
    private void ShootSpread()
    {
        Vector3 baseDir = (playerTransform.position - transform.position).normalized;
        float baseAngle = Mathf.Atan2(baseDir.y, baseDir.x) * Mathf.Rad2Deg;
    
        int count = Mathf.Max(1, myData.bulletCount);
        float step = (count > 1) ? myData.spreadAngle / (count - 1) : 0f;
        float start = baseAngle - (myData.spreadAngle / 2f);
    
        for (int i = 0; i < count; i++)
        {
            float a = start + step * i;
            Vector3 dir = new Vector3(Mathf.Cos(a * Mathf.Deg2Rad), Mathf.Sin(a * Mathf.Deg2Rad), 0);
            CreateBullet(dir, myData.bulletSpeed);
        }
    }
    
  4. Homing:誘導弾(専用Prefab+初期化)
    private void ShootHoming()
    {
        Quaternion initRotation = Quaternion.Euler(0, 0, 180f);
        GameObject bulletObj = Instantiate(myData.bulletPrefab, transform.position, initRotation);
    
        HomingBullet homing = bulletObj.GetComponent();
        if (homing != null)
        {
            float rotateSpeed = 200.0f;
            float searchRange = 20.0f;
            homing.InitializeForEnemy(myData.bulletSpeed, 1.0f, 5.0f, rotateSpeed, searchRange);
        }
    }
    
  5. Storm:360度の全方位(毎回オフセットをランダム)
    private void ShootStorm()
    {
        int count = Mathf.Max(3, myData.bulletCount);
        float step = 360f / count;
        float offset = Random.Range(0f, 360f);
    
        for (int i = 0; i < count; i++)
        {
            float a = offset + step * i;
            Vector3 dir = new Vector3(Mathf.Cos(a * Mathf.Deg2Rad), Mathf.Sin(a * Mathf.Deg2Rad), 0);
            CreateBullet(dir, myData.bulletSpeed);
        }
    }
    

<実際のコード(弾生成の共通処理:fixedRotation対応)>

弾の見た目を「進行方向へ回転させる/させない」を「EnemyData.fixedRotation」で切り替えています。人魂のように「画像の向きを固定したい弾」は、回転しない+ワールド方向指定で飛ばします。

// Assets/Scripts/EnemyController.cs
private void CreateBullet(Vector3 direction, float speed)
{
    Quaternion rotation;
    Vector3? fixedDir = null;

    if (myData.fixedRotation)
    {
        rotation = Quaternion.identity;
        fixedDir = direction;
    }
    else
    {
        float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg - 90f;
        rotation = Quaternion.Euler(0, 0, angle);
    }

    GameObject bulletObj = Instantiate(myData.bulletPrefab, transform.position, rotation);

    Bullet bullet = bulletObj.GetComponent();
    if (bullet != null)
    {
        bullet.InitializeForEnemy(speed, 1.0f, 8.0f, fixedDir);
    }
}
// Assets/Scripts/Bullet.cs(移動ロジック抜粋)
if (worldDirection.HasValue)
{
    transform.Translate(worldDirection.Value * speed * Time.deltaTime, Space.World);
}
else
{
    transform.Translate(Vector3.up * speed * Time.deltaTime);
}

以上です。

コメント

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