【Cocos Creator】アタッチするだけ!BossHealthBar (ボスHPバー)の実装方法【TypeScript】

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

【Cocos Creator 3.8】BossHealthBar の実装:アタッチするだけで「画面下部に固定表示される巨大ボスHPバーUI」を実現する汎用スクリプト

この記事では、任意のCanvas配下のノードにアタッチするだけで画面下部に固定表示されるボス用HPバーを制御できる汎用コンポーネント BossHealthBar を実装します。
ボス戦だけで使う大きなHPバーを毎回組むのは面倒ですが、このスクリプトを使えば、HP値の更新・アニメーション・色変化・自動非表示 まで一括で扱えるようになります。


コンポーネントの設計方針

機能要件の整理

  • 画面下部に固定表示される、横長のボスHPバーUI
  • Canvas配下の任意のノードにアタッチするだけで動作
  • インスペクタから以下を調整可能にする
    • 最大HP・現在HP
    • バーの見た目(背景色・前景色)
    • アニメーション(ダメージ時の減少速度・遅延)
    • HP 0 で自動的にフェードアウトして非表示にするか
    • 画面下部での位置・サイズ・マージン
    • 表示/非表示の初期状態
  • 外部スクリプト(GameManager や Boss スクリプトなど)への依存は一切禁止
  • HPの更新は 公開メソッドsetMaxHealth, setCurrentHealth, damage, heal など)経由で行う
  • 必要なUIノードは 自動生成 し、Prefab や別ノードへの依存をなくす
  • Canvas配下でのレイアウトは UITransformWidget を利用して「下部に固定」

このコンポーネントは、自身のノードを「ボスHPバーコンテナ」として再構成し、その子として

  • 背景バー(BG)
  • メインHPバー(即時反映)
  • ダメージ遅延バー(ゆっくり追従する赤いバーなど)

の 3 レイヤーを自動生成して管理します。

インスペクタで設定可能なプロパティ設計

以下のようなプロパティを用意します。

  • HP設定
    • maxHealth:最大HP(例: 10000)。初期最大値。
    • currentHealth:現在HP(例: 10000)。開始時のHP。
    • autoClampHealth:HP更新時に 0〜maxHealth に自動Clampするか。
  • 表示・レイアウト
    • startVisible:ゲーム開始時に表示状態で始めるか。
    • barWidth:バー全体の横幅(px)。
    • barHeight:バー全体の高さ(px)。
    • bottomMargin:画面下端からの距離(px)。
    • horizontalMargin:左右マージン(px)。0 の場合は中央寄せで width 優先。
    • useFullWidth:true の場合、画面幅いっぱいにバーを伸ばす。
  • 色・見た目
    • backgroundColor:背景バーの色。
    • foregroundColor:メインHPバーの色。
    • damageDelayColor:ダメージ遅延バーの色(例: 赤)。
    • cornerRadius:角丸の半径(Sprite.Type.SLICED 前提で使用)。
  • ダメージアニメーション
    • useDamageDelayBar:ダメージ遅延バーを表示するか。
    • damageDelay:ダメージ後、遅延バーが追従し始めるまでの待ち時間(秒)。
    • damageFollowSpeed:遅延バーがメインバーに追従する速度(1〜20 くらい)。
  • 自動非表示・フェード
    • autoHideOnZero:HP が 0 になったら自動的に非表示にするか。
    • fadeOutDuration:フェードアウト時間(秒)。0 の場合は即時非表示。
    • hideAfterFade:フェード後にノード自体を active = false にするか。

また、HPを外部から更新するためのメソッドも用意します。

  • setMaxHealth(value: number, keepRatio = false)
  • setCurrentHealth(value: number)
  • damage(amount: number)
  • heal(amount: number)
  • show() / hide()
  • reset()(最大HP・現在HPを初期値に戻す)

TypeScriptコードの実装

以下が、BossHealthBar.ts の完成コードです。


import { _decorator, Component, Node, UITransform, Widget, Color, Sprite, SpriteFrame, UIOpacity, tween, Vec3, math } from 'cc';
const { ccclass, property } = _decorator;

/**
 * BossHealthBar
 * 画面下部に固定表示される巨大ボスHPバーUIを制御する独立コンポーネント。
 * - このコンポーネントを Canvas 配下の任意のノードにアタッチするだけで動作します。
 * - 外部の GameManager や Boss スクリプトへの依存は一切ありません。
 */
@ccclass('BossHealthBar')
export class BossHealthBar extends Component {

    // =========================
    // HP 設定
    // =========================

    @property({
        tooltip: 'ボスの最大HP。ゲーム開始時の最大値として使用されます。'
    })
    maxHealth: number = 10000;

    @property({
        tooltip: 'ボスの現在HP。onLoad 時に maxHealth を超えないように調整されます。'
    })
    currentHealth: number = 10000;

    @property({
        tooltip: 'HPを更新する際に 0〜maxHealth の範囲に自動Clampするかどうか。'
    })
    autoClampHealth: boolean = true;

    // =========================
    // 表示・レイアウト
    // =========================

    @property({
        tooltip: 'ゲーム開始時にボスHPバーを表示状態で開始するかどうか。'
    })
    startVisible: boolean = true;

    @property({
        tooltip: 'ボスHPバー全体の横幅(ピクセル)。useFullWidth が true の場合は無視されます。'
    })
    barWidth: number = 800;

    @property({
        tooltip: 'ボスHPバー全体の高さ(ピクセル)。'
    })
    barHeight: number = 40;

    @property({
        tooltip: '画面下端からの距離(ピクセル)。Widget コンポーネントで制御されます。'
    })
    bottomMargin: number = 40;

    @property({
        tooltip: '左右マージン(ピクセル)。0 の場合は中央寄せで barWidth を優先します。'
    })
    horizontalMargin: number = 40;

    @property({
        tooltip: 'true の場合、画面幅いっぱいにバーを伸ばします(左右マージンを優先)。'
    })
    useFullWidth: boolean = false;

    // =========================
    // 色・見た目
    // =========================

    @property({
        tooltip: '背景バーの色。'
    })
    backgroundColor: Color = new Color(30, 30, 30, 200);

    @property({
        tooltip: 'メインHPバーの色。'
    })
    foregroundColor: Color = new Color(0, 200, 0, 255);

    @property({
        tooltip: 'ダメージ遅延バーの色(メインバーより遅れて減少するバー)。'
    })
    damageDelayColor: Color = new Color(200, 0, 0, 200);

    @property({
        tooltip: '角丸の半径(Sprite.Type.SLICED を使用する場合に有効)。\n実際の見た目は使用するSpriteFrameに依存します。',
    })
    cornerRadius: number = 8;

    @property({
        type: SpriteFrame,
        tooltip: 'バーの描画に使用するSpriteFrame(単色でもOK)。\n未設定の場合はエンジンのデフォルトSpriteFrameが使用されます。',
    })
    barSpriteFrame: SpriteFrame | null = null;

    // =========================
    // ダメージアニメーション
    // =========================

    @property({
        tooltip: 'ダメージ遅延バー(赤いバー)を表示するかどうか。'
    })
    useDamageDelayBar: boolean = true;

    @property({
        tooltip: 'ダメージを受けた後、遅延バーが追従し始めるまでの待ち時間(秒)。'
    })
    damageDelay: number = 0.3;

    @property({
        tooltip: '遅延バーがメインバーに追従する速度。\n値が大きいほど素早く追従します(推奨: 5〜15)。'
    })
    damageFollowSpeed: number = 8;

    // =========================
    // 自動非表示・フェード
    // =========================

    @property({
        tooltip: 'HPが0になったとき、自動的にフェードアウトして非表示にするかどうか。'
    })
    autoHideOnZero: boolean = true;

    @property({
        tooltip: 'フェードアウトにかける時間(秒)。0 の場合は即時非表示。'
    })
    fadeOutDuration: number = 0.4;

    @property({
        tooltip: 'フェードアウト完了後にこのノードを非アクティブ(active = false)にするかどうか。'
    })
    hideAfterFade: boolean = true;

    // =========================
    // 内部状態
    // =========================

    private _bgNode: Node | null = null;
    private _fgNode: Node | null = null;
    private _delayNode: Node | null = null;

    private _bgTransform: UITransform | null = null;
    private _fgTransform: UITransform | null = null;
    private _delayTransform: UITransform | null = null;

    private _uiOpacity: UIOpacity | null = null;

    private _targetHealthRatio: number = 1;   // currentHealth / maxHealth
    private _displayHealthRatio: number = 1;  // メインバーに表示されている比率
    private _delayHealthRatio: number = 1;    // 遅延バーに表示されている比率

    private _damageDelayTimer: number = 0;
    private _isTakingDamage: boolean = false;
    private _isFadingOut: boolean = false;

    onLoad() {
        // HP の初期整合性をとる
        if (this.maxHealth <= 0) {
            console.warn('[BossHealthBar] maxHealth が 0 以下です。1 に補正します。');
            this.maxHealth = 1;
        }
        if (this.autoClampHealth) {
            this.currentHealth = math.clamp(this.currentHealth, 0, this.maxHealth);
        }

        this._targetHealthRatio = this.currentHealth / this.maxHealth;
        this._displayHealthRatio = this._targetHealthRatio;
        this._delayHealthRatio = this._targetHealthRatio;

        // 必要な UI コンポーネントをセットアップ
        this._setupRootNode();
        this._createBarNodes();
        this._applyInitialLayout();
        this._applyColors();

        // 初期表示状態
        this._uiOpacity = this.node.getComponent(UIOpacity);
        if (!this._uiOpacity) {
            this._uiOpacity = this.node.addComponent(UIOpacity);
        }
        if (this.startVisible) {
            this._uiOpacity.opacity = 255;
            this.node.active = true;
        } else {
            this._uiOpacity.opacity = 0;
            this.node.active = false;
        }
    }

    start() {
        // 特別な処理は不要だが、将来の拡張用に残しておく
    }

    update(deltaTime: number) {
        // フェードアウト中は HP アニメーションを止める
        if (this._isFadingOut) {
            return;
        }

        // メインバーをターゲット比率にスムーズに追従させる
        const lerpSpeed = 15; // メインバーの追従速度
        this._displayHealthRatio = math.lerp(this._displayHealthRatio, this._targetHealthRatio, deltaTime * lerpSpeed);
        this._displayHealthRatio = math.clamp01(this._displayHealthRatio);

        // ダメージ遅延バーの挙動
        if (this.useDamageDelayBar) {
            if (this._isTakingDamage) {
                // ダメージを受けた直後は一定時間待つ
                if (this._damageDelayTimer > 0) {
                    this._damageDelayTimer -= deltaTime;
                } else {
                    // 待機後、メインバーに向かって追従開始
                    this._delayHealthRatio = math.lerp(this._delayHealthRatio, this._displayHealthRatio, deltaTime * this.damageFollowSpeed);
                    this._delayHealthRatio = math.clamp01(this._delayHealthRatio);

                    // ほぼ同じになったら完了
                    if (Math.abs(this._delayHealthRatio - this._displayHealthRatio) < 0.001) {
                        this._delayHealthRatio = this._displayHealthRatio;
                        this._isTakingDamage = false;
                    }
                }
            } else {
                // ダメージを受けていない時は同じ値にしておく
                this._delayHealthRatio = this._displayHealthRatio;
            }
        } else {
            this._delayHealthRatio = this._displayHealthRatio;
        }

        this._updateBarSizes();

        // HP が 0 になったら自動非表示
        if (this.autoHideOnZero && this.currentHealth <= 0 && !this._isFadingOut) {
            this._startFadeOut();
        }
    }

    // =========================
    // 公開API
    // =========================

    /**
     * 最大HPを設定します。
     * @param value 新しい最大HP
     * @param keepRatio true の場合、現在の割合を維持して currentHealth を再計算します。
     */
    public setMaxHealth(value: number, keepRatio: boolean = false) {
        if (value <= 0) {
            console.warn('[BossHealthBar] setMaxHealth: value が 0 以下です。1 に補正します。');
            value = 1;
        }

        if (keepRatio) {
            const ratio = this.currentHealth / this.maxHealth;
            this.maxHealth = value;
            this.currentHealth = math.clamp(Math.round(this.maxHealth * ratio), 0, this.maxHealth);
        } else {
            this.maxHealth = value;
            if (this.autoClampHealth) {
                this.currentHealth = math.clamp(this.currentHealth, 0, this.maxHealth);
            }
        }

        this._updateTargetRatio();
    }

    /**
     * 現在HPを直接設定します。
     * @param value 新しい現在HP
     */
    public setCurrentHealth(value: number) {
        if (this.autoClampHealth) {
            this.currentHealth = math.clamp(value, 0, this.maxHealth);
        } else {
            this.currentHealth = value;
        }
        this._updateTargetRatio();
    }

    /**
     * ダメージを与えます(現在HPを減少させます)。
     * @param amount ダメージ量(正の値)
     */
    public damage(amount: number) {
        if (amount <= 0) {
            return;
        }
        const newHealth = this.currentHealth - amount;
        this.setCurrentHealth(newHealth);

        // ダメージ遅延バー用のフラグ
        if (this.useDamageDelayBar) {
            this._isTakingDamage = true;
            this._damageDelayTimer = this.damageDelay;
        }
    }

    /**
     * 回復させます(現在HPを増加させます)。
     * @param amount 回復量(正の値)
     */
    public heal(amount: number) {
        if (amount <= 0) {
            return;
        }
        const newHealth = this.currentHealth + amount;
        this.setCurrentHealth(newHealth);
        // 回復時は遅延バーもすぐ追従させる
        this._delayHealthRatio = this.currentHealth / this.maxHealth;
        this._isTakingDamage = false;
    }

    /**
     * HPバーを表示します(フェードイン付き)。
     */
    public show() {
        if (!this._uiOpacity) {
            this._uiOpacity = this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity);
        }
        this.node.active = true;
        this._isFadingOut = false;
        tween(this._uiOpacity)
            .to(0.2, { opacity: 255 })
            .start();
    }

    /**
     * HPバーを非表示にします(フェードアウト付き)。
     */
    public hide() {
        this._startFadeOut();
    }

    /**
     * HPバーを初期状態にリセットします。
     * - currentHealth を maxHealth に戻し、
     * - フェード状態を解除し、
     * - 表示状態にします。
     */
    public reset() {
        this.currentHealth = this.maxHealth;
        this._targetHealthRatio = 1;
        this._displayHealthRatio = 1;
        this._delayHealthRatio = 1;
        this._isTakingDamage = false;
        this._damageDelayTimer = 0;
        this._isFadingOut = false;

        if (!this._uiOpacity) {
            this._uiOpacity = this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity);
        }
        this._uiOpacity.opacity = 255;
        this.node.active = true;
        this._updateBarSizes();
    }

    // =========================
    // 内部処理
    // =========================

    private _updateTargetRatio() {
        this._targetHealthRatio = math.clamp01(this.currentHealth / this.maxHealth);
        if (this._targetHealthRatio > this._displayHealthRatio) {
            // 回復時はメインバーを即座に反映させる
            this._displayHealthRatio = this._targetHealthRatio;
        }
    }

    private _startFadeOut() {
        if (!this.autoHideOnZero) {
            return;
        }
        if (!this._uiOpacity) {
            this._uiOpacity = this.node.getComponent(UIOpacity) || this.node.addComponent(UIOpacity);
        }
        this._isFadingOut = true;

        if (this.fadeOutDuration <= 0) {
            this._uiOpacity.opacity = 0;
            if (this.hideAfterFade) {
                this.node.active = false;
            }
            return;
        }

        tween(this._uiOpacity)
            .to(this.fadeOutDuration, { opacity: 0 })
            .call(() => {
                if (this.hideAfterFade) {
                    this.node.active = false;
                }
            })
            .start();
    }

    /**
     * このノード自体を「ボスHPバーコンテナ」としてセットアップします。
     */
    private _setupRootNode() {
        // UITransform が必須
        let uiTrans = this.node.getComponent(UITransform);
        if (!uiTrans) {
            uiTrans = this.node.addComponent(UITransform);
        }

        // Widget を利用して画面下部に固定
        let widget = this.node.getComponent(Widget);
        if (!widget) {
            widget = this.node.addComponent(Widget);
        }
        widget.isAlignTop = false;
        widget.isAlignBottom = true;
        widget.isAlignLeft = this.useFullWidth || this.horizontalMargin > 0;
        widget.isAlignRight = this.useFullWidth || this.horizontalMargin > 0;
        widget.bottom = this.bottomMargin;
        widget.left = this.useFullWidth ? this.horizontalMargin : 0;
        widget.right = this.useFullWidth ? this.horizontalMargin : 0;

        // サイズ設定
        if (this.useFullWidth) {
            // 幅は Canvas の幅に依存(Widget に任せる)
            uiTrans.height = this.barHeight;
        } else {
            uiTrans.width = this.barWidth;
            uiTrans.height = this.barHeight;
            // 中央寄せ
            this.node.setPosition(new Vec3(0, -this.bottomMargin, 0));
        }
    }

    /**
     * 背景・メイン・遅延バーのノードを生成します。
     */
    private _createBarNodes() {
        // 既存の子ノードはそのまま残す(他用途との共存を許可)
        const rootTransform = this.node.getComponent(UITransform);
        if (!rootTransform) {
            console.error('[BossHealthBar] UITransform がノードに存在しません。コンポーネントの追加に問題があります。');
            return;
        }

        // 背景バー
        this._bgNode = new Node('BossHP_BG');
        this.node.addChild(this._bgNode);
        this._bgTransform = this._bgNode.addComponent(UITransform);
        this._bgTransform.setAnchorPoint(0, 0.5); // 左中央
        this._bgNode.setPosition(new Vec3(-rootTransform.width / 2, 0, 0));

        const bgSprite = this._bgNode.addComponent(Sprite);
        if (this.barSpriteFrame) {
            bgSprite.spriteFrame = this.barSpriteFrame;
        }
        bgSprite.type = Sprite.Type.SLICED;

        // ダメージ遅延バー
        this._delayNode = new Node('BossHP_Delay');
        this.node.addChild(this._delayNode);
        this._delayTransform = this._delayNode.addComponent(UITransform);
        this._delayTransform.setAnchorPoint(0, 0.5);
        this._delayNode.setPosition(new Vec3(-rootTransform.width / 2, 0, 0));

        const delaySprite = this._delayNode.addComponent(Sprite);
        if (this.barSpriteFrame) {
            delaySprite.spriteFrame = this.barSpriteFrame;
        }
        delaySprite.type = Sprite.Type.SLICED;

        // メインHPバー
        this._fgNode = new Node('BossHP_FG');
        this.node.addChild(this._fgNode);
        this._fgTransform = this._fgNode.addComponent(UITransform);
        this._fgTransform.setAnchorPoint(0, 0.5);
        this._fgNode.setPosition(new Vec3(-rootTransform.width / 2, 0, 0));

        const fgSprite = this._fgNode.addComponent(Sprite);
        if (this.barSpriteFrame) {
            fgSprite.spriteFrame = this.barSpriteFrame;
        }
        fgSprite.type = Sprite.Type.SLICED;
    }

    /**
     * 初期レイアウトを適用します。
     */
    private _applyInitialLayout() {
        const rootTransform = this.node.getComponent(UITransform);
        if (!rootTransform) {
            console.error('[BossHealthBar] UITransform がノードに存在しません。');
            return;
        }

        const width = rootTransform.width;
        const height = rootTransform.height;

        if (this._bgTransform) {
            this._bgTransform.width = width;
            this._bgTransform.height = height;
        }
        if (this._fgTransform) {
            this._fgTransform.width = width * this._displayHealthRatio;
            this._fgTransform.height = height;
        }
        if (this._delayTransform) {
            this._delayTransform.width = width * this._delayHealthRatio;
            this._delayTransform.height = height;
        }
    }

    /**
     * バーの色を適用します。
     */
    private _applyColors() {
        if (this._bgNode) {
            const s = this._bgNode.getComponent(Sprite);
            if (s) {
                s.color = this.backgroundColor;
            }
        }
        if (this._fgNode) {
            const s = this._fgNode.getComponent(Sprite);
            if (s) {
                s.color = this.foregroundColor;
            }
        }
        if (this._delayNode) {
            const s = this._delayNode.getComponent(Sprite);
            if (s) {
                s.color = this.damageDelayColor;
            }
        }
    }

    /**
     * 毎フレーム、HP比率に基づいてバーの幅を更新します。
     */
    private _updateBarSizes() {
        const rootTransform = this.node.getComponent(UITransform);
        if (!rootTransform) return;

        const width = rootTransform.width;
        const height = rootTransform.height;

        if (this._bgTransform) {
            this._bgTransform.width = width;
            this._bgTransform.height = height;
        }
        if (this._fgTransform) {
            this._fgTransform.width = width * this._displayHealthRatio;
            this._fgTransform.height = height;
        }
        if (this._delayTransform) {
            this._delayTransform.width = width * this._delayHealthRatio;
            this._delayTransform.height = height;
            this._delayNode!.active = this.useDamageDelayBar;
        }

        // 左端基準位置を再設定(Canvas サイズが変わる可能性もあるため)
        const leftX = -width / 2;
        if (this._bgNode) this._bgNode.setPosition(new Vec3(leftX, 0, 0));
        if (this._fgNode) this._fgNode.setPosition(new Vec3(leftX, 0, 0));
        if (this._delayNode) this._delayNode.setPosition(new Vec3(leftX, 0, 0));
    }
}

主要メソッドの解説

  • onLoad
    • maxHealth / currentHealth の整合性を取り、内部の比率変数を初期化。
    • HPバー用の UI ノード(背景・メイン・遅延)を自動生成。
    • Widget と UITransform を利用して「画面下部固定」のレイアウトを構築。
    • UIOpacity を追加し、初期表示状態(startVisible)に応じて active / opacity を設定。
  • update
    • targetHealthRatio(論理的なHP比率)に対して、displayHealthRatio(見た目)を補間。
    • ダメージ遅延バー用の delayHealthRatio を、タイマーと速度に応じて追従させる。
    • HP が 0 になった場合、autoHideOnZero が true ならフェードアウト処理を開始。
    • 毎フレーム _updateBarSizes() で UITransform の width を更新し、バーの長さを調整。
  • 公開API(setCurrentHealth / damage / heal)
    • 外部スクリプトから HP を直接変更するための窓口。
    • damage() はダメージ遅延バー用のフラグやタイマーも更新。
    • heal() は回復時に遅延バーも即座に追従させる仕様。
  • show / hide / reset
    • show(): HPバーをフェードイン表示。
    • hide(): HPバーをフェードアウト非表示。
    • reset(): HPを最大に戻し、バーを初期状態で表示。
  • _setupRootNode / _createBarNodes
    • BossHealthBar がアタッチされたノードを「コンテナ」として設定し、Widget で画面下配置。
    • 背景・メイン・遅延バーを子ノードとして自動生成し、Sprite / UITransform を追加。

使用手順と動作確認

1. スクリプトファイルの作成

  1. Assets パネルで右クリック → Create → TypeScript を選択します。
  2. 新規スクリプトの名前を BossHealthBar.ts に変更します。
  3. ダブルクリックしてエディタで開き、中身をすべて削除して、上記のコードを貼り付けて保存します。

2. テスト用 Canvas / シーン準備

  1. Hierarchy パネルで、シーン内に Canvas が存在することを確認します。
    • もし無ければ、右クリック → Create → UI → Canvas で作成します。
  2. Canvas の RenderModeSCREEN_SPACE_CAMERA または SCREEN_SPACE_OVERLAY のいずれかになっていることを確認します(通常のUI用途)。

3. BossHealthBar 用ノードの作成とアタッチ

  1. Hierarchy で Canvas を右クリック → Create → Empty Node で空ノードを作成します。
  2. ノード名を分かりやすく BossHealthBarRoot などに変更します。
  3. BossHealthBarRoot ノードを選択し、Inspector 右下の Add Component をクリックします。
  4. Custom → BossHealthBar を選択してアタッチします。

4. Inspector でプロパティを設定

BossHealthBar コンポーネントを選択すると、多数のプロパティが表示されます。まずは基本的な動作を確認するため、以下のように設定してみてください。

  • HP 設定
    • maxHealth10000
    • currentHealth10000
    • autoClampHealthON
  • 表示・レイアウト
    • startVisibleON
    • barWidth800
    • barHeight40
    • bottomMargin40
    • horizontalMargin40
    • useFullWidth:最初は OFF(後でONも試してみてください)
  • 色・見た目
    • backgroundColor:暗いグレー(デフォルトのままでOK)
    • foregroundColor:緑(デフォルトのままでOK)
    • damageDelayColor:赤(デフォルトのままでOK)
    • barSpriteFrame任意の単色SpriteFrame を指定するのが望ましいですが、未設定でも動作します。
  • ダメージアニメーション
    • useDamageDelayBarON
    • damageDelay0.3
    • damageFollowSpeed8〜12 程度
  • 自動非表示・フェード
    • autoHideOnZeroON
    • fadeOutDuration0.4
    • hideAfterFadeON

5. シーン再生してHPバーの表示を確認

  1. 上部ツールバーの ▶(Play)ボタン を押してシーンを再生します。
  2. ゲーム画面の 下部中央に、横長のボスHPバー が表示されていることを確認します。
  3. useFullWidth を ON にすると、画面幅いっぱいに伸びたバーが表示されます(左右マージンで内側に寄せることも可能)。

6. HPの変化をテストする(簡易テスト用スクリプト)

BossHealthBar 自体は完全に独立しており、外部からメソッドを呼べばHPが変化します。
動作確認のため、簡単なテストスクリプトを別途作って、キー入力でダメージ/回復させてみましょう。

  1. Assets パネルで右クリック → Create → TypeScriptBossHealthBarTester.ts を作成します。
  2. 以下のような最低限のテストコードを貼り付けます(任意):

import { _decorator, Component, KeyCode, input, Input } from 'cc';
import { BossHealthBar } from './BossHealthBar';
const { ccclass, property } = _decorator;

@ccclass('BossHealthBarTester')
export class BossHealthBarTester extends Component {

    @property({ type: BossHealthBar, tooltip: '操作対象の BossHealthBar コンポーネント' })
    bossHealthBar: BossHealthBar | null = null;

    onLoad() {
        input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this);
    }

    onDestroy() {
        input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this);
    }

    private onKeyDown(event: any) {
        if (!this.bossHealthBar) return;

        switch (event.keyCode) {
            case KeyCode.KEY_Z:
                // Z キーでダメージ
                this.bossHealthBar.damage(500);
                break;
            case KeyCode.KEY_X:
                // X キーで回復
                this.bossHealthBar.heal(500);
                break;
            case KeyCode.KEY_C:
                // C キーでリセット
                this.bossHealthBar.reset();
                break;
        }
    }
}

テスト手順:

  1. Hierarchy で Canvas を右クリック → Create → Empty NodeBossHealthBarTesterRoot を作成。
  2. Inspector の Add Component → Custom → BossHealthBarTester を追加。
  3. BossHealthBarTesterbossHealthBar プロパティに、先ほど作成した BossHealthBarRoot ノードをドラッグ&ドロップして割り当て。
  4. ゲームを再生し、キーボードで以下を試す:
    • Z キー:ダメージ(HPが減り、赤い遅延バーが追従してくる)
    • X キー:回復(HPが増え、バーが伸びる)
    • C キー:リセット(最大HPに戻る)

HP が 0 になるまで Z キーを連打すると、バーが空になったタイミングでフェードアウトし、非表示になることを確認できます。


まとめ

この BossHealthBar コンポーネントは、

  • Canvas配下の任意のノードにアタッチするだけで 画面下部に固定表示されるボスHPバーを構築し、
  • 最大HP・現在HP・見た目・アニメーション・フェードアウトを すべてインスペクタだけで調整 でき、
  • HPの更新は damage() / heal() / setCurrentHealth() などの シンプルな公開メソッド で完結します。

外部の GameManager や Boss スクリプトに依存しないため、

  • どのプロジェクトにも そのままコピペで持ち込める
  • ボスごとに HP バーの見た目やサイズを変えたい場合も、Prefab 化して複製 するだけ
  • UI デザイナーとプログラマが インスペクタ経由で役割分担 しやすい

といった利点があります。

このコンポーネントをベースに、

  • 残りHP割合で色を変える(緑→黄→赤)
  • ボス名テキストやアイコンを追加する
  • HP変化時に揺れアニメーションを追加する

などを拡張していけば、ボス戦用UI一式を「1ノード+1スクリプト」で完結させることも可能です。
まずはこの記事のコードをそのまま試し、動作を確認しながら、自分のゲーム用にカスタマイズしてみてください。

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

URLをコピーしました!