はじめに
プレイヤーが発射する弾の種類に、放射弾と誘導弾を追加しました。
初期の弾は、直進弾としてしています。アイテムを取得することで放射弾と誘導弾に変更することができます。
<弾の種類>
ここまでの成果
ゲームの仕様
新しく追加した仕様には「(新)」を付けています。
変更した仕様に「(変)」を付けています。
- プレイヤーHP設定:初期HPは5
- プレイヤーHP表示:画面右上に「HP:X」を常時表示する
- 敗北条件:HPが0になった時点で「GAME OVER」UIを表示し、ゲームを停止する
- 操作方法:WASDキー または 十字キー(上下左右の移動)
- 攻撃方法:自動発射
- 敵キャラ実装数:20体(e001~e020)
- 敵キャラHP
- e001~e010:1
- e011~e020:2
- 敵キャラの移動:画面上から下方向に直進する
- スコア:画面左上に「Score:X」を常時表示する
- e001~e010:100点
- e011~e021:200点
- ステータス:アイテムの取得で強化できるステータス
- 弾速:上限無し
- 連射速度:発射間隔は0.05秒未満にならないように設計
- 攻撃力:上限無し
- 移動速度:初期値から4倍以上にならにように設計
- HP:上限は5
- (新)アイテム一覧
3種類の弾の実装
直進弾の実装
Unityにおいて「何かを動かして、衝突したら消す」という処理を作る際は基本的に下記を使用します。
<動きを作る主要素>
| カテゴリ | コード内容の要素 | 役割 |
| 移動 | transform.Translate | 現在の一から指定した分だけ座標をずらす |
| 方向 | Vector3.up | 自分にとってのY軸+を表すベクトル |
| 時間 | Time.deltaTime | 前のフレームからの経過時間。これを掛けることで、一定の速度で動かせる。 |
| 更新 | Update() ※MonoBehaviour |
Unityが1秒間に約60回自動で呼び出すメソッド。ここに移動処理を書くことで動き続ける |
<当たり判定を作る主要素>
| カテゴリ | コード内の要素 | 役割 |
| 検知 | OnTriggerEnter2D ※MonoBehaviour |
IsTriggerがONの物体同士が重なった瞬間に、Unityが自動で呼び出すメソッド |
| 相手 | CompareTag ※Collider2D |
衝突してきた相手(other)が「Enemy」かどうかタグを確認するメソッド |
| 衝突 | Destroy ※Object |
指定したゲームオブジェクトをシーンから消去するメソッド |
<実際のコード>
transform.Translate で動かして、OnTriggerEnter2D で当たり判定を取る実装になります。
public class Bullet : MonoBehaviour
{
public float speed = 10f;
public int power = 1;
void Update()
{
// 常に自分の向いている方向(上)へ進む
transform.Translate(Vector3.up * speed * Time.deltaTime);
}
private void OnTriggerEnter2D(Collider2D other)
{
// 敵との衝突判定、ダメージ処理など
if (other.CompareTag("Enemy"))
{
// 敵へのダメージ処理...
Destroy(gameObject);
}
}
}
放射弾の実装
弾そのものの動きは直進弾と同じです。
放射弾は、生成する瞬間に「角度」をつけて「複数の弾」を発射する必要があります。
<角度(扇状)を作る主要素>
| カテゴリ | コード内の要素 | 役割 |
| 回転 | Quaternion.Euler(x, y, z) | 「Z軸に◯◯度」という数値を、Unityが扱える回転データ(Quaternion)に変換する |
| 開始 | startAngle | 扇形の左端(スタート地点)の角度を求めます。「-(全体角度/2)」で計算する |
| 角度 | bulletSpacing | 弾と弾の間の角度(例:15度など)。ループごとにこの値を加算して角度をずらす |
<複数の弾を作る主要素>
| カテゴリ | コード内の要素 | 役割 |
| 発射数 | bulletCount | 一度に発射する弾の総数。ループの終了条件に使用 |
| 実体化 | Instantiate | 計算した「位置」と「角度」を指定して、プレハブをシーン上に登場させる |
<実際のコード>
「ループの中で角度をずらしながらInstantiateする」のが放射弾実装の核心部分となります。
// 放射弾(扇形)の発射ロジック
private void FireWide(WeaponData weapon, float damage)
{
// 1. 扇形の全体の角度を計算
// (弾数 - 1) × 1つあたりの角度間隔
float totalAngle = (weapon.bulletCount - 1) * weapon.bulletSpacing;
// 2. 開始角度(左端)を計算
// 全体の半分だけマイナス方向にずらす
float startAngle = -totalAngle / 2f;
// 3. ループ処理で弾を生成
for (int i = 0; i < weapon.bulletCount; i++)
{
// 今回の弾の角度を計算 (開始角度 + インデックス × 間隔)
float currentAngle = startAngle + (i * weapon.bulletSpacing);
// 角度をUnityの回転データ(Quaternion)に変換
// 2DなのでZ軸を回す
Quaternion rotation = Quaternion.Euler(0, 0, currentAngle);
// 弾を生成
// firePoint.rotation(プレイヤーの向き) に rotation(扇状の角度) を掛け合わせる
CreateBullet(weapon, firePoint.position, firePoint.rotation * rotation, damage);
}
}
誘導弾の実装
直進弾や放射弾は、発射された瞬間に「飛んでいく角度」が決まり、その後はひたすら真っ直ぐ進むだけでした。
誘導弾は、発射された後も毎フレーム「敵の位置」を確認し、自分の向きを「操舵(ステアリング)」し続ける必要があります。
実装は大きく分けて、「① 誰を狙うか決める(索敵)」と「② その敵に向かって曲がる(追尾)」の2つのステップで構成されています。
<狙う敵を決める主要素>
発射された瞬間、あるいはターゲットが消えた際に、次に狙うべき敵を探す処理
| カテゴリ | コード内の主要素 | 役割 |
| 検索 | GameObject.FindGameObjectsWithTag | 画面上に存在する、特定のタグ(”Enemy”)がついた全オブジェクトを配列として取得する |
| 距離計算 | Vector2.Distance | 弾と敵との距離を測り、これをループで回し、closestDistance(最短距離)の敵を特定する |
| 標的 | Transform target | 見つけた敵の場所(Transform)を変数に保存しておき、毎フレームの追尾対象として利用 |
<追尾する主要素>
| カテゴリ | コード内の主要素 | 役割 |
| 方向 | (Vector2)target.position – (Vector2)transform.position | 「敵の位置」から「自分の位置」を引くことで、敵への方向ベクトル(矢印)を求める |
| 角度 | Mathf.Atan2 | ベクトルのX, Y成分から、具体的な「角度(度数)」を算出する |
| 回転 | Quaternion.RotateTowards | 「現在の向き」から「敵の向き」へ、指定した旋回速度(rotateSpeed)で徐々に回転させる |
| 前進 | transform.Translate(Vector3.up …) | 弾自身は「自分の上(前方)」に進む |
<実際のコード>
「一番近い敵を見つけて」「その方向へハンドルを切り続ける」処理によって、誘導弾の実装
public class HomingBullet : MonoBehaviour
{
private Transform target; // 狙う敵
public float rotateSpeed = 200f; // 旋回性能(1秒間に何度回れるか)
public float speed = 10f;
// 初期化時にターゲットを探す
public void Initialize()
{
FindNearestTarget();
}
void Update()
{
// ターゲットが消滅していたら再検索
if (target == null)
{
FindNearestTarget();
}
// ターゲットがいる場合、向きを変える(操舵)
if (target != null)
{
// 1. ターゲットへの方向ベクトルを計算
Vector2 direction = (Vector2)target.position - (Vector2)transform.position;
direction.Normalize();
// 2. 向きを角度に変換 (Atan2の結果はラジアンなのでデグリーに変換)
// 2Dのスプライトが上(Y+)を向いている前提で -90度の補正を入れる
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg - 90f;
// 3. 目的の角度データ(Quaternion)を作成
Quaternion targetRotation = Quaternion.AngleAxis(angle, Vector3.forward);
// 4. 現在の向きから目的の向きへ、rotateSpeedの速度で徐々に回転させる
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, rotateSpeed * Time.deltaTime);
}
// 常に「自分の向いている方向(上)」に進む
transform.Translate(Vector3.up * speed * Time.deltaTime);
}
// 最も近い敵を探す
void FindNearestTarget()
{
GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy");
float closestDistance = Mathf.Infinity;
target = null;
foreach (GameObject enemy in enemies)
{
float distance = Vector2.Distance(transform.position, enemy.transform.position);
if (distance < closestDistance)
{
closestDistance = distance;
target = enemy.transform;
}
}
}
}
以上です。
コメント