Unityを触り始めた頃は、つい「とりあえず Update() に全部書く」スタイルになりがちですよね。HPの管理、移動、攻撃パターン、アニメーション、SE再生…すべてを1つのスクリプトに押し込んでしまうと、ボスの挙動を少し変えたいだけでも巨大なコードをスクロールして探すハメになります。

特にボス戦の「形態変化(フェーズ)」は、ロジックが複雑になりやすい代表例です。
「HPが半分を切ったら攻撃パターンを変える」「見た目も変える」「BGMも変えたい」などを1つのクラスに直書きすると、あっという間にGodクラス化してしまいます。

そこでこの記事では、「HPが半分を切ったら攻撃パターンや見た目を切り替える」部分だけに責務を絞ったコンポーネント 「BossPhase」 を作ってみましょう。
ボスの移動ロジックや攻撃ロジックは別コンポーネントに任せ、BossPhase は「いつ、どのフェーズにいるか」を決めるだけにすると、コードの見通しがぐっと良くなります。

【Unity】HPで形態変化を制御!「BossPhase」コンポーネント

以下に、Unity6(C#)で動作する BossPhase コンポーネントのフルコードを示します。
このコンポーネントは:

  • HPを監視して「通常フェーズ」「怒りフェーズ(HP半分以下)」「撃破」の状態を管理
  • フェーズごとに有効化する攻撃コンポーネントの切り替え
  • 見た目(MeshRenderer や Animator パラメータ)の切り替え
  • インスペクターで設定可能なイベント(UnityEvent)の発火

までを担当します。ボスの攻撃パターンそのものは、他のコンポーネントに分離しておきましょう。

ソースコード(フル)


using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// ボスのHPに応じて「フェーズ(形態)」を管理するコンポーネント。
/// - HPが最大のとき: 通常フェーズ
/// - HPが半分以下のとき: 怒りフェーズ
/// - HPが0以下のとき: 撃破
/// 
/// 攻撃コンポーネントの有効/無効や、見た目の切り替えをここで行います。
/// 攻撃ロジック自体は別コンポーネントに任せることで、責務を分離します。
/// </summary>
[DisallowMultipleComponent]
public class BossPhase : MonoBehaviour
{
    /// <summary>最大HP(初期HP)</summary>
    [Header("HP 設定")]
    [SerializeField]
    private int maxHp = 100;

    /// <summary>現在HP(インスペクターで確認用)</summary>
    [SerializeField, Tooltip("現在HP。ゲーム開始時に maxHp で初期化されます。")]
    private int currentHp;

    /// <summary>怒りフェーズに入る割合(0〜1)。0.5 なら HP が半分以下で怒りフェーズ。</summary>
    [SerializeField, Range(0.1f, 0.9f)]
    private float rageThresholdRate = 0.5f;

    /// <summary>現在のフェーズを表す列挙型</summary>
    public enum Phase
    {
        Normal, // 通常
        Rage,   // 怒り(第二形態)
        Dead    // 撃破
    }

    [Header("フェーズ状態(読み取り専用)")]
    [SerializeField, Tooltip("現在のフェーズ。インスペクターで確認用です。")]
    private Phase currentPhase = Phase.Normal;

    /// <summary>現在フェーズを外部から参照したい場合用のプロパティ(読み取り専用)</summary>
    public Phase CurrentPhase => currentPhase;

    [Header("フェーズごとの攻撃コンポーネント")]

    [SerializeField, Tooltip("通常フェーズで有効化したいコンポーネント(例: 通常攻撃スクリプト)")]
    private Behaviour[] normalPhaseBehaviours;

    [SerializeField, Tooltip("怒りフェーズで有効化したいコンポーネント(例: 強化攻撃スクリプト)")]
    private Behaviour[] ragePhaseBehaviours;

    [Header("見た目の切り替え")]

    [SerializeField, Tooltip("通常フェーズで表示したい Renderer(メッシュやスキンメッシュなど)")]
    private Renderer[] normalRenderers;

    [SerializeField, Tooltip("怒りフェーズで表示したい Renderer(色違いモデルなど)")]
    private Renderer[] rageRenderers;

    [SerializeField, Tooltip("Animator がある場合、フェーズを表す int パラメータ名(空なら無視)")]
    private string phaseAnimatorParameter = "Phase";

    [SerializeField, Tooltip("Animator コンポーネント(任意)。設定されていなければ自動取得を試みます。")]
    private Animator animator;

    [Header("イベント(インスペクターから設定可能)")]

    [SerializeField, Tooltip("怒りフェーズに突入したときに呼ばれるイベント")]
    private UnityEvent onEnterRagePhase;

    [SerializeField, Tooltip("ボスが撃破されたときに呼ばれるイベント")]
    private UnityEvent onDead;

    // 内部用:怒りフェーズへ入るHP閾値
    private int rageThresholdHp;

    // 内部用:既に Dead フェーズへ入ったかどうか
    private bool isDead = false;

    private void Reset()
    {
        // コンポーネント追加時に、Animator を自動で探して設定しておく
        animator = GetComponentInChildren<Animator>();
        maxHp = Mathf.Max(1, maxHp);
        currentHp = maxHp;
    }

    private void Awake()
    {
        // HP初期化
        maxHp = Mathf.Max(1, maxHp);
        currentHp = maxHp;

        // 怒りフェーズに入るHP閾値を計算
        rageThresholdHp = Mathf.FloorToInt(maxHp * rageThresholdRate);

        // Animator が未設定なら自動取得
        if (animator == null)
        {
            animator = GetComponentInChildren<Animator>();
        }

        // 開始時は通常フェーズとしてセットアップ
        SetPhase(Phase.Normal, force: true);
    }

    /// <summary>
    /// ダメージを与える公開メソッド。
    /// 他のスクリプト(例: 弾やプレイヤー攻撃判定)がここを呼び出します。
    /// マイナス値を渡すと回復として扱います。
    /// </summary>
    /// <param name="amount">与えるダメージ量(例: 10)。負の値で回復。</param>
    public void ApplyDamage(int amount)
    {
        if (isDead)
        {
            // すでに撃破済みなら何もしない
            return;
        }

        // HPを減算(または回復)
        currentHp -= amount;

        // HPを 0〜maxHp の範囲にクランプ
        currentHp = Mathf.Clamp(currentHp, 0, maxHp);

        // HPに応じてフェーズを更新
        UpdatePhaseByHp();
    }

    /// <summary>
    /// HPに応じてフェーズを更新する内部メソッド。
    /// </summary>
    private void UpdatePhaseByHp()
    {
        // すでに Dead フェーズなら何もしない
        if (isDead)
        {
            return;
        }

        if (currentHp <= 0)
        {
            // 撃破
            SetPhase(Phase.Dead);
            return;
        }

        if (currentHp <= rageThresholdHp)
        {
            // 怒りフェーズ
            SetPhase(Phase.Rage);
        }
        else
        {
            // 通常フェーズ
            SetPhase(Phase.Normal);
        }
    }

    /// <summary>
    /// 実際にフェーズを切り替える処理。
    /// 攻撃コンポーネントの有効/無効や、見た目の切り替えをここで行います。
    /// </summary>
    /// <param name="nextPhase">切り替え先のフェーズ</param>
    /// <param name="force">同じフェーズでも強制的に再適用したい場合 true</param>
    private void SetPhase(Phase nextPhase, bool force = false)
    {
        if (!force && currentPhase == nextPhase)
        {
            // すでに同じフェーズなら何もしない
            return;
        }

        currentPhase = nextPhase;

        // Animator パラメータの更新
        UpdateAnimatorParameter();

        switch (currentPhase)
        {
            case Phase.Normal:
                isDead = false;
                SetBehavioursEnabled(normalPhaseBehaviours, true);
                SetBehavioursEnabled(ragePhaseBehaviours, false);
                SetRenderersEnabled(normalRenderers, true);
                SetRenderersEnabled(rageRenderers, false);
                break;

            case Phase.Rage:
                isDead = false;
                SetBehavioursEnabled(normalPhaseBehaviours, false);
                SetBehavioursEnabled(ragePhaseBehaviours, true);
                SetRenderersEnabled(normalRenderers, false);
                SetRenderersEnabled(rageRenderers, true);

                // 怒りフェーズ突入イベント
                onEnterRagePhase?.Invoke();
                break;

            case Phase.Dead:
                isDead = true;
                // すべての攻撃コンポーネントを無効化
                SetBehavioursEnabled(normalPhaseBehaviours, false);
                SetBehavioursEnabled(ragePhaseBehaviours, false);

                // 見た目は好みで。ここでは両方有効のままにしておく
                // SetRenderersEnabled(normalRenderers, false);
                // SetRenderersEnabled(rageRenderers, false);

                // 撃破イベント
                onDead?.Invoke();
                break;
        }
    }

    /// <summary>
    /// Animator にフェーズを表す int パラメータを渡す。
    /// パラメータ名が空、または Animator が未設定なら何もしない。
    /// </summary>
    private void UpdateAnimatorParameter()
    {
        if (animator == null) return;
        if (string.IsNullOrEmpty(phaseAnimatorParameter)) return;

        // Phase の enum を int にキャストして渡す
        animator.SetInteger(phaseAnimatorParameter, (int)currentPhase);
    }

    /// <summary>
    /// Behaviour(MonoBehaviour, NavMeshAgent など)配列の有効/無効を一括で切り替える。
    /// </summary>
    private void SetBehavioursEnabled(Behaviour[] behaviours, bool enabled)
    {
        if (behaviours == null) return;

        foreach (var behaviour in behaviours)
        {
            if (behaviour == null) continue;
            behaviour.enabled = enabled;
        }
    }

    /// <summary>
    /// Renderer 配列の表示/非表示を一括で切り替える。
    /// </summary>
    private void SetRenderersEnabled(Renderer[] renderers, bool enabled)
    {
        if (renderers == null) return;

        foreach (var r in renderers)
        {
            if (r == null) continue;
            r.enabled = enabled;
        }
    }

    // --- デバッグ用:インスペクターから手動でダメージを与えるためのヘルパー ---

#if UNITY_EDITOR
    [Header("デバッグ用(エディタ限定)")]
    [SerializeField, Tooltip("エディタ上からテストダメージを与えるための値")]
    private int debugDamage = 10;

    [ContextMenu("Debug: Apply Damage")]
    private void DebugApplyDamage()
    {
        ApplyDamage(debugDamage);
        Debug.Log($"[BossPhase] Debug ダメージ {debugDamage} を適用。現在HP: {currentHp}, フェーズ: {currentPhase}");
    }
#endif
}

使い方の手順

  1. ボス用のプレハブを用意する
    例として「巨大なドラゴンボス」を想定します。
    プレハブ構成例:
    • 親オブジェクト: DragonBoss
    • 子オブジェクト: Model_Normal(通常見た目)
    • 子オブジェクト: Model_Rage(怒りフェーズ見た目、色違いなど)
    • 子オブジェクト: Attack_Normal(通常攻撃スクリプト付き)
    • 子オブジェクト: Attack_Rage(怒り攻撃スクリプト付き)
  2. 「BossPhase」コンポーネントを追加して設定する
    • DragonBoss の親オブジェクトに BossPhase をアタッチします。
    • HP 設定maxHp をボスの耐久力に合わせて設定(例: 500)。
    • rageThresholdRate0.5 にして「HP半分以下で怒りフェーズ」にします。
    • フェーズごとの攻撃コンポーネント:
      • normalPhaseBehavioursAttack_Normal のスクリプトをドラッグ&ドロップ。
      • ragePhaseBehavioursAttack_Rage のスクリプトをドラッグ&ドロップ。
    • 見た目の切り替え:
      • normalRenderersModel_NormalMeshRenderer を登録。
      • rageRenderersModel_RageMeshRenderer を登録。
    • Animator を使っている場合は:
      • animator にドラゴンの Animator を設定。
      • Animator Controller に int Phase パラメータを作成し、ステート遷移条件に使う。
      • phaseAnimatorParameter"Phase" に設定。
  3. ダメージを与える側から「ApplyDamage」を呼ぶ
    例として、プレイヤーの弾がボスに当たったときにダメージを与えるコンポーネントを用意します。
    
    using UnityEngine;
    
    /// <summary>
    /// プレイヤーの弾。ボスに当たったらダメージを与える簡易例。
    /// </summary>
    [RequireComponent(typeof(Collider))]
    public class PlayerBullet : MonoBehaviour
    {
        [SerializeField]
        private int damage = 10;
    
        private void OnTriggerEnter(Collider other)
        {
            // BossPhase を持っているオブジェクトに当たったらダメージを与える
            var bossPhase = other.GetComponentInParent<BossPhase>();
            if (bossPhase != null)
            {
                bossPhase.ApplyDamage(damage);
                // 弾を破壊
                Destroy(gameObject);
            }
        }
    }
    

    このように、「HPを減らす」ことだけをBossPhaseに任せることで、弾側のスクリプトはシンプルに保てます。

  4. イベントを使って演出を追加する
    • インスペクターの On Enter Rage Phase(怒りフェーズ突入)イベントに:
      • 画面エフェクト用のコンポーネント(例: カメラシェイク、画面フラッシュ)
      • BGMを切り替えるスクリプト

      を登録しておくと、コードを書かずに演出を追加できます。

    • On Dead に「ドロップアイテム生成」「次のシーンに遷移」などを登録しておくと、撃破時の処理を柔軟に組めます。

メリットと応用

BossPhase を導入することで、以下のようなメリットがあります。

  • 責務が明確:HPとフェーズ管理だけを担当するので、クラスが肥大化しません。
  • プレハブの再利用性アップ:ボスごとに HP や閾値、攻撃コンポーネント、見た目を差し替えるだけで、別のボスを簡単に作れます。
  • レベルデザインが楽になる:UnityEvent によって、フェーズ移行タイミングで演出を差し込めるため、スクリプトを書き換えずにゲームデザイナーが調整できます。
  • テストしやすい:コンテキストメニュー(Debug: Apply Damage)でエディタ上から簡単にダメージを与えられるので、「怒りフェーズのテスト」「撃破演出のテスト」が素早く行えます。

応用として、フェーズを2段階以上に増やしたり、時間経過でもフェーズが変化するようにしたりといった拡張も簡単に行えます。
例えば「一定時間ごとに自動で回復する」改造を入れて、放置するとボスが回復するギミックを作ることもできます。

以下は、そのような回復ギミックを追加する例のスニペットです(BossPhase クラスに追記する形)。


/// <summary>
/// 一定間隔で自動回復させる簡易例。
/// 例えば「プレイヤーが離れているとボスが回復する」などのギミックに使えます。
/// </summary>
private void HealOverTime(float interval, int healAmountPerTick)
{
    // コルーチンで実装してもよいですが、
    // シンプルな例として InvokeRepeating を使います。
    CancelInvoke(nameof(HealTick));
    _healAmountPerTick = healAmountPerTick;
    InvokeRepeating(nameof(HealTick), interval, interval);
}

private int _healAmountPerTick;

private void HealTick()
{
    if (isDead) return;

    // ApplyDamage に負の値を渡すことで回復として扱う
    ApplyDamage(-_healAmountPerTick);
}

このように、「HPとフェーズの管理」コンポーネントとして BossPhase を分離しておくと、後からの改造やギミック追加がとてもやりやすくなります。
巨大なボス用スクリプトを1つ作るのではなく、小さな責務ごとにコンポーネントを分けていく設計を意識してみてください。