ボスの行動パターンを作っていく
①画面外に出現
②画面中央に移動
まず、出現位置を決めるために出現座標を宣言して初期化。
次に、Lerpを使って、出現座標から目的座標への移動を組み込み。
(BossEnemy.cs)
using UnityEngine;
public class BossEnemy : BaseEnemy
{
public enum BossState
{
Appear, //出現
Idle, //待機
Attack_01, //攻撃1:ばらまき射撃
Attack_02, //攻撃2:集中攻撃
Dead, //死亡
}
public BossState currentState;
public Vector3 defaultPosition = newVector3(0, 2.0f, 0); //BossEnemyの出現座標
void Start()
{
currentState = BossState.Appear;
}
// Update is called once per frame
void Update()
{
switch(currentState)
{
case BossState.Appear:
Appear();
break;
case BossState.Idle:
Idle();
break;
case BossState.Attack_01:
Attack_01();
break;
case BossState.Attack_02:
Attack_02();
break;
}
}
public void Appear()
{
transform.position = Vector3.Lerp(
transform.position, //現在の座標を
defaultPosition, //目的の座標へ
Time.deltaTime);
}
public void Idle()
{
}
public void Attack_01()
{
}
public void Attack_02()
{
}
public void Death()
{
}
public override void OnDamage(int damage)
{
int adjusted = Mathf.CeilToInt(damage * 0.5f);//ダメージ半減
base.OnDamage(adjusted);
}
}
Unityでの Lerp(Linear Interpolation:線形補間) の機能と使い方を、初心者にも分かるように説明します👇
🧮 Lerpとは?
「ある値AからBへ、なめらかに変化させる」ための関数です。
正式には「Linear Interpolation(線形補間)」の略。
たとえば:
A = 0、B = 10 のとき、
Lerp(A, B, 0.5) ⇒ 5
(AとBの中間の値)
💡 基本構文
float result = Mathf.Lerp(a, b, t);
| 引数 | 意味 |
|---|---|
a | 開始値(最初の値) |
b | 終了値(目的の値) |
t | 0〜1の範囲。0でa、1でb、0.5で中間 |
🎮 1. 位置をなめらかに動かす(Vector3.Lerp)
transform.position = Vector3.Lerp(transform.position, target.position, 0.05f);
👉 これでオブジェクトが「target」に向かって、
フレームごとにちょっとずつ近づく動きになります。t の値(ここでは 0.05f)が小さいほど「ゆっくり」。
💡 ポイント:
Vector3.Lerp は、3D座標(x, y, z)を全部同時にLerpしてくれる便利関数。
Color.Lerp:色を滑らかに変化Quaternion.Lerp:回転を滑らかに補間
なども同じ仕組み。
🎨 2. 色を滑らかに変える(Color.Lerp)
GetComponent<SpriteRenderer>().color =
Color.Lerp(Color.white, Color.red, Mathf.PingPong(Time.time, 1));
→ これで白から赤へ、そして赤から白へゆっくり点滅します。
🕹️ 3. 実時間を使って移動(時間で補間する例)
public Transform startPos;
public Transform endPos;
public float moveTime = 2f;
private float elapsed = 0f;
void Update()
{
elapsed += Time.deltaTime;
float t = Mathf.Clamp01(elapsed / moveTime);
transform.position = Vector3.Lerp(startPos.position, endPos.position, t);
}
👉 これで2秒かけて startPos から endPos に移動。t は 0→1 に比例して増えるので、時間で制御できる。
🧠 応用:なめらかな減速(SmoothDamp / Lerp)
Lerp は直線的(一定速度)に近づくけど、
「減速して止まる」動きを作りたいなら次のように:
transform.position = Vector3.Lerp(
transform.position,
target.position,
Time.deltaTime * 5f
);
t に「Time.deltaTime × 係数」を入れると、
自然な「イージング」っぽい動きになります(近づくほどゆっくり)。
⚠️ よくある勘違い
| 誤解 | 正しい理解 |
|---|---|
Lerp(a, b, 2f) とすると早く動く | ❌ tは0〜1の範囲(1超えるとbを超える) |
Lerpは自動でなめらかになる | ❌ tを時間やdeltaTimeで制御しないと一定スピードじゃない |
Lerpは常に一定速度 | ❌ 「差分に比例」して動く(近づくほど遅くなる) |
✅ まとめ
| 種類 | 説明 | 例 |
|---|---|---|
Mathf.Lerp(a,b,t) | 数値を補間 | 数値、スコア、透明度など |
Vector3.Lerp(a,b,t) | 座標を補間 | オブジェクトの移動 |
Color.Lerp(a,b,t) | 色を補間 | フェードや点滅 |
Quaternion.Lerp(a,b,t) | 回転を補間 | 向きの変更 |
🪄 一言で言うと:
「Lerp = AからBへ、t%だけ進む」
もし、あなたのシューティングで「敵がゆっくり出現位置に移動」「プレイヤーの照準をスムーズに追従」などの用途で使いたいなら、
実際のシーンに合わせたサンプルを一緒に書きましょうか?
※var(valiable型)での宣言
ローカル変数としてのみ可能
なんでも入る
C#(Unityでも同じ)での var 宣言 は、実はすごく便利な文法糖衣(=短縮表現)なんです。
でも「なんでも使える魔法の型」ではないので、使いどころを理解しておくのがポイントです。
🧠 varとは?
➡️ 型推論(type inference) をしてくれるキーワードです。
コンパイラが「右辺から型を自動で判断」してくれます。
たとえば:
var hp = 100;
これはコンパイル時に
int hp = 100;
と同じ意味になります。
📘 使い方のルール
| 条件 | 説明 |
|---|---|
| ① 初期化が必須 | var x; はエラー。右辺から型を推論するため。 |
| ② 代入された型で固定 | 一度決まると、別の型は入れられない。 |
| ③ 右辺の型が明確である必要 | null など曖昧な値だけでは推論できない。 |
例:
var num = 5; // int
var text = "Hello"; // string
var pos = new Vector3(); // Vector3
💡 実際にUnityで使う場面
✅ OKな使い方(読みやすい・安全)
var enemy = Instantiate(enemyPrefab); // enemyPrefabの型が明確ならOK
var rb = GetComponent<Rigidbody2D>(); // 戻り値が明確
var distance = Vector3.Distance(a, b); // 戻り値がfloat
⚠️ 微妙な使い方(型がわからなくなる)
var result = DoSomething(); // DoSomethingの戻り値がわからないと、可読性が下がる
→ 「後で見たとき型が何かわからない」状態になるのが最大のデメリット。
🧩 varの正体を理解する例
var enemy = new Enemy();
enemy.TakeDamage(10);
これはコンパイル時に
Enemy enemy = new Enemy();
enemy.TakeDamage(10);
に変換されます。
つまり、実行時ではなく、コンパイル時に型は確定している。
「動的型」ではありません(※JavaScriptのvarとはまったく違う)。
🎮 Lerpと一緒に使う例
var targetPos = target.position;
var current = transform.position;
var next = Vector3.Lerp(current, targetPos, 0.05f);
transform.position = next;
これも全部の右辺が明確な型を返しているので、安全に var が使えます。
✅ まとめ
| 項目 | 説明 |
|---|---|
| 正式名 | 型推論キーワード |
| 意味 | 右辺の型から自動的に左辺の型を決める |
| 注意点 | 初期化必須、右辺が曖昧だとエラー |
| メリット | コードが短くなる・型名が長い時に便利 |
| デメリット | 型が見えにくく、可読性が下がることも |
💬 ざっくり言うと:
var= 「型名が長いときの省略記法」
でも、「右辺を見ないと型がわからない」ような使い方は避けよう。
MicrosoftとUnity(公式リソース)に沿って、varの推奨使用範囲と設定方法をコンパクトにまとめます。
方針(要点だけ)
- Microsoft(.NET公式)
- 「右辺から型が明確なときだけ
varを使う」。
例:リテラル、new 型名(...)、明示キャスト、戻り値が自明なAPI。
逆に、型がパッと分からないなら明示型を使う。 Microsoft Learn - エディタで強制したい場合はIDE0007/IDE0008のスタイルルールを使う。 Microsoft Learn+1
- 「右辺から型が明確なときだけ
- Unity(公式ドキュメント/記事)
具体例(Unity開発での“使う/使わない”)
✅ var を使う(型が自明)
var go = new GameObject(); // new で自明
var rb = GetComponent<Rigidbody2D>(); // 戻り値が明快
var t = transform; // Unityのプロパティで型が明快
var p = Vector3.Lerp(a, b, 0.1f); // 戻り値が Vector3 と明快
- **匿名型(LINQの select new { … })**は
varが必須。 Microsoft Learn
❌ var を避ける(型が読みにくい)
var result = DoSomethingHard(); // 戻り値が推測しづらい
var x = SomeFactory.Make(); // Makeの型が不明
- 将来メソッドの戻り型が変わった時、気づきにくい&レビューで読みづらい。
現場での運用テンプレ(.editorconfig)
「型が明確なら var、明確でないなら明示型」を機械的に支援する設定例です。
# .editorconfig(ソリューション直下)
[*.cs]
# 右辺が "明らか" なときは var を推奨(IDE0007)
csharp_style_var_when_type_is_apparent = true:suggestion
# 右辺が "明らかでない" ときは明示型を推奨(IDE0008)
csharp_style_var_elsewhere = false:suggestion
# new/キャスト/リテラル等は「明らか」に該当
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_for_locals = true:suggestion
- ルールの中身(IDE0007/IDE0008)の詳細は .NET 公式を参照。チーム合意で
suggestionをwarningに上げるのもアリ。 Microsoft Learn+1
よくある質問(Unity文脈)
- Q. Unityは“公式に var 推奨/非推奨”を定めてる?
A. いいえ。 統一の“公式規約”は出していません。公式記事では「自チームのスタイルガイドを決め、varは可読性が上がる場合に限定」の趣旨です。実務では .NET のガイドに倣うのが無難です。 Unity+1 - Q. じゃあ最終結論は?
→ 「右辺で一目で型が分かるならvar、分からないなら明示型」
これが Microsoft 直系の原則で、Unityでもそのまま通じます。(by ChatGPT)
確率で2種類の攻撃を実装していく。
まずはタイマーに使う変数宣言。
次に、Random.Range(0,100);でifを使ってIdle状態から2通りに分岐させる。
(BossEnemy.cs)
public float attackInterval = 2.0f;
public float attackTimer = 0.0f;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public void Idle()
{
attackTimer += Time.deltaTime;
if(attackTimer >= attackInterval)
{
attackTimer = 0.0f;
int random = Random.Range(0, 100);//ランダムで1~100の範囲の値を返す
if(random > 50)
currentState = BossState.Attack_01;
else
currentState = BossState.Attack_02;
}
}
各状態から一定時間経ったらIdle状態に戻る処理を組み込む。
(BossEnemy.cs)
public float shotInterval = 2.0f;
public float shotTimer = 0.0f;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public void Attack_01()
{
shotTimer += Time.deltaTime;
if(shotTimer >= shotInterval)
{
shotTimer = 0.0f;
currentState = BossState.Idle;
}
}
public void Attack_02()
{
shotTimer += Time.deltaTime;
if(shotTimer >= shotInterval)
{
shotTimer = 0.0f;
currentState = BossState.Idle;
}
}
Enemy側に射撃を組み込む。
(BossEnemy.cs)
public GameObject bulletPrefab;
public float shotPower = 10f;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public void Attack_02()
{
shotTimer += Time.deltaTime;
if (shotTimer >= shotInterval)
{
Shot();
shotTimer = 0.0f;
currentState = BossState.Idle;
}
}
public void Shot()
{
GameObject bullet = Instantiate(bulletPrefab, transform.position, transform.rotation);
Vector2 direction = new Vector2(0f, -1f);//射撃方向を決める
//Rigidbody2Dを取得して力を加える
Rigidbody2D rb2d = bullet.GetComponent<Rigidbody2D>();
rb2d.AddForce(direction * shotPower * Random.Range(0.6f, 1f), ForceMode2D.Impulse);
}
Bullet.csをコピーしてEnemyBullet.csにリネーム。クラス名もEnemyBulletに修正。
Tag判定部を”Player”に変更する。
(EnemyBullet.cs)
using UnityEngine;
public class EnemyBullet : MonoBehaviour
{
void Update()
{
if (Mathf.Abs(transform.position.y) > 10f) // 画面外に出たら
{
Destroy(gameObject);
}
}
private void OnTriggerEnter2D(Collider2D collision)
{
var enemy = collision.GetComponent<Player>();
if (collision.tag.Contains("Player"))
{
Destroy(collision.gameObject); //弾丸を消す
}
}
}
Bullet.prefabをコピーしてEnemyBullet.prefabにリネーム。
Bullet.csをRemoveして、代わりにEnemyBulletをアタッチする。

ここまでで、Attack_2状態で下方向の弾が射出されるようになった。
弾の打ち分け(今回は「射出方向」の調整)
shot()に引数をつけて、射出方向を指定できるようにする。
using UnityEngine;
public class BossEnemy : BaseEnemy
{
public enum BossState
{
Appear, //出現
Idle, //待機
Attack_01, //攻撃1:ばらまき射撃
Attack_02, //攻撃2:集中攻撃
Dead, //死亡
}
public BossState currentState;
public Vector3 defaultPosition = new Vector3(0, 2.0f, 0); //BossEnemyの出現座標
public float attackInterval = 2.0f;
public float attackTimer = 0.0f;
public float shotInterval = 2.0f;
public float shotTimer = 0.0f;
public GameObject bulletPrefab;
public float shotPower = 10f;
void Start()
{
currentState = BossState.Appear;
}
// Update is called once per frame
void Update()
{
switch(currentState)
{
case BossState.Appear:
Appear();
break;
case BossState.Idle:
Idle();
break;
case BossState.Attack_01:
Attack_01();
break;
case BossState.Attack_02:
Attack_02();
break;
}
}
public void Appear()
{
transform.position = Vector3.Lerp(
transform.position, //この変数を
defaultPosition, //ここまで動かす
Time.deltaTime);
var distance = Vector3.Distance(//座標AとBの距離を求めてくれる
transform.position, // 座標A
defaultPosition); // 座標B
if(distance <= 0.01f)
{
currentState = BossState.Idle;//目的地に到達したら待機状態へ移行
}
}
public void Idle()
{
attackTimer += Time.deltaTime;
if(attackTimer >= attackInterval)
{
attackTimer = 0.0f;
int random = Random.Range(0, 100);//ランダムで1~100の範囲の値を返す
if(random > 50)
currentState = BossState.Attack_01;
else
currentState = BossState.Attack_02;
}
}
public void Attack_01()
{
Shot(new Vector2(Random.Range(-1f, 1f), -1));
shotTimer += Time.deltaTime;
if(shotTimer >= shotInterval)
{
shotTimer = 0.0f;
currentState = BossState.Idle;
}
}
public void Attack_02()
{
shotTimer += Time.deltaTime;
if (shotTimer >= shotInterval)
{
Shot(new Vector2(0,-1));
shotTimer = 0.0f;
currentState = BossState.Idle;
}
}
public void Shot(Vector2 dir)
{
GameObject bullet = Instantiate(bulletPrefab, transform.position, transform.rotation);
Vector2 direction = dir.normalized;//方向だけ保って、長さを1に
//Rigidbody2Dを取得して力を加える
Rigidbody2D rb2d = bullet.GetComponent<Rigidbody2D>();
rb2d.AddForce(direction * shotPower * Random.Range(0.6f, 1f), ForceMode2D.Impulse);
}
public void Death()
{
}
public override void OnDamage(int damage)
{
int adjusted = Mathf.CeilToInt(damage * 0.5f);//ダメージ半減
base.OnDamage(adjusted);
}
}
normalized は ベクトルを「長さ1」にそろえる プロパティです。
つまり:
Vector2 dir = new Vector2(2, -3);
Vector2 unitDir = dir.normalized; // ← 方向だけ保って、長さを1に
こうすると、斜め方向でもスピードが均一になります。
🧩 これで何が変わるか?
たとえば:
Shot(new Vector2(1, -1));
→ このベクトルの長さは√2 ≈ 1.41normalized を使わないと「右下に速く飛ぶ」ことになります。
でも dir.normalized にすると
(1/√2, -1/√2) ≈ (0.707, -0.707)
となり、速度は他の方向と同じになります。
✅ まとめ
| 書き方 | 意味 |
|---|---|
dir | 元の方向ベクトル(長さがバラバラ) |
dir.normalized | 同じ方向で、長さを1にしたベクトル |
🔸 結論:AddForce() に使うときは、ほぼ常に dir.normalized を使うのが正解!(by ChatGPT)

コメント