はじめに
前バージョンまでは、敵は上から下に移動するだけの単調な動作のみでした。今回は、敵の攻撃パターンと移動パターンを追加しました。
- 敵の移動を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
- アイテム一覧
- 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;
}
}
<実際のコード(各攻撃パターンの中身:抜粋)>
- Sniper:自機狙い1発
private void ShootSniper() { Vector3 direction = (playerTransform.position - transform.position).normalized; CreateBullet(direction, myData.bulletSpeed); } - Wall:真下へ直進
private void ShootWall() { CreateBullet(Vector3.down, myData.bulletSpeed); } - 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); } } - 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); } } - 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);
}
以上です。
コメント