ゲームエンジンⅡ④(2025/10/23)

ボスの行動パターンを作っていく

①画面外に出現
②画面中央に移動

まず、出現位置を決めるために出現座標を宣言して初期化。
次に、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終了値(目的の値)
t0〜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 を使う」といった方針を明記せよ、と案内している。 Unity+1
    • Unity 6 の公式スタイルガイド資料(リソース)でも、既存の業界標準ガイドの採用・適応を勧めている(= .NETの方針を取り込むのが無難)。 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 公式を参照。チーム合意で suggestionwarning に上げるのもアリ。 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.41
normalized を使わないと「右下に速く飛ぶ」ことになります。

でも dir.normalized にすると
(1/√2, -1/√2) ≈ (0.707, -0.707)
となり、速度は他の方向と同じになります。


✅ まとめ

書き方意味
dir元の方向ベクトル(長さがバラバラ)
dir.normalized同じ方向で、長さを1にしたベクトル

🔸 結論:
AddForce() に使うときは、ほぼ常に dir.normalized を使うのが正解!(by ChatGPT)

コメント

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