【Cocos Creator 3.8】RegenEffect(自動回復)の実装:アタッチするだけで一定間隔でHPが自動回復する汎用スクリプト
この記事では、ノードにアタッチするだけで「一定時間ごとにHPが自動回復」する汎用コンポーネント RegenEffect を実装します。
外部の GameManager やカスタム HealthManager に一切依存せず、このコンポーネント単体で「HPの管理」+「自動回復」まで完結するように設計します。HP の最大値・現在値・回復量・回復間隔などは、すべてインスペクタから調整可能にします。
コンポーネントの設計方針
機能要件の整理
- このコンポーネントをアタッチしたノードは、「HP」を持つ。
- HP は「最大値」「現在値」を持ち、0〜最大値の範囲にクランプされる。
- 一定時間ごとに、現在の HP を少しずつ回復させる。
- 自動回復は「オン/オフ」できる。
- ゲーム開始時の HP 初期値を「最大HP」「任意の値」から選べる。
- 外部のスクリプトに依存せず、このコンポーネント単体で完結する。
- 将来的に「外部からダメージを与える」「現在HPを参照する」用途も想定し、
シンプルなパブリックメソッドを用意する(任意で利用可能)。
本来の仕様では「親の HealthManager を呼び出す」とありますが、「他のカスタムスクリプトへの依存禁止」という条件があるため、
このコンポーネント自体が「簡易 HealthManager 兼 自動回復機能」として完結するようにします。
インスペクタで設定可能なプロパティ設計
RegenEffect コンポーネントに定義するプロパティとその役割は以下の通りです。
- maxHp: number
- HP の最大値。
- 0 より大きい値を推奨(例: 100)。
- 現在 HP はこの値を超えないようにクランプされる。
- initialHpRatio: number
- ゲーム開始時の HP 初期値を「最大HP に対する割合(0〜1)」で指定。
- 例: 1.0 なら最大HPから開始、0.5 なら半分のHPから開始。
- 0〜1 の範囲外を指定した場合は、自動的にクランプされる。
- enableRegen: boolean
- 自動回復を有効にするかどうか。
- チェックを外すと、自動回復は行われない(HP管理のみ)。
- regenAmount: number
- 1 回の回復で増加する HP の量。
- 正の値を推奨(例: 1, 5, 10 など)。
- 0 以下の場合は、警告ログを出しつつ実質的に回復しない。
- regenInterval: number
- 自動回復の発生間隔(秒)。
- 例: 1.0 なら 1 秒ごとに
regenAmountだけ回復。 - 0 以下の場合は、自動回復が動作しないようにし、警告ログを出す。
- stopRegenAtFullHp: boolean
- HP が最大になったら自動回復タイマーを止めるかどうか。
- true の場合、HP が最大値に達した時点で以後の回復処理を行わない。
- false の場合、HP が最大でもチェックは続くが、実質的には何も起こらない。
- logDebug: boolean
- HPの変化やエラー・警告をログに出すかどうか。
- 開発・デバッグ時のみ ON、本番では OFF を推奨。
内部的な挙動
- onLoad
- プロパティの値を安全な範囲にクランプ。
- 現在HP(
_currentHp)をmaxHp * initialHpRatioから計算。
- start
enableRegenが true かつregenInterval > 0かつregenAmount > 0の場合のみ、タイマーを開始。- Cocos Creator の
this.schedule()を使って、一定間隔でdoRegen()を呼び出す。
- onDisable / onDestroy
- スケジュールしている回復処理を停止して、メモリリークや予期しない挙動を防止。
- doRegen
- 現在HPが 0 以下の場合は、回復を行うかどうかをゲームデザインに応じて選べるが、ここでは「0 でも回復する(蘇生可能な仕様)」にしている。
- 現在HPが最大HP以上なら、
stopRegenAtFullHpに応じてスケジュールを停止する。 - それ以外の場合、
regenAmountを加算し、最大値を超えないようにクランプ。
- パブリックメソッド
getCurrentHp(): number… 現在HPを取得。getMaxHp(): number… 最大HPを取得。applyDamage(amount: number): void… ダメージを与えて HP を減少させる。heal(amount: number): void… 即時回復。setRegenEnabled(enabled: boolean): void… 自動回復のオン/オフをコードから切り替え。
TypeScriptコードの実装
import { _decorator, Component, log, warn, error } from 'cc';
const { ccclass, property } = _decorator;
/**
* RegenEffect
* ノードにアタッチするだけで「HP管理 + 一定間隔の自動回復」を行う汎用コンポーネント。
*/
@ccclass('RegenEffect')
export class RegenEffect extends Component {
@property({
tooltip: '最大HP。現在HPはこの値を超えません。',
})
public maxHp: number = 100;
@property({
tooltip: '開始時のHP割合(0〜1)。1なら最大HPから開始、0.5なら半分から開始します。',
slide: true,
range: [0, 1, 0.01],
})
public initialHpRatio: number = 1.0;
@property({
tooltip: '自動回復を有効にするかどうか。',
})
public enableRegen: boolean = true;
@property({
tooltip: '1回の回復で増加するHP量。正の値を指定してください。',
})
public regenAmount: number = 1;
@property({
tooltip: '回復間隔(秒)。例: 1.0 で1秒ごとに回復します。',
})
public regenInterval: number = 1.0;
@property({
tooltip: 'HPが最大になったら自動回復を停止するかどうか。',
})
public stopRegenAtFullHp: boolean = true;
@property({
tooltip: 'HP変化や警告をログに出力するかどうか(デバッグ用)。',
})
public logDebug: boolean = false;
// 現在のHP(インスペクタには表示しない)
private _currentHp: number = 0;
// スケジュール中かどうかのフラグ
private _isRegenScheduled: boolean = false;
onLoad() {
// 防御的な値のクランプ
if (this.maxHp <= 0) {
warn('[RegenEffect] maxHp が 0 以下です。1 に補正します。');
this.maxHp = 1;
}
if (this.initialHpRatio < 0) {
this.initialHpRatio = 0;
} else if (this.initialHpRatio > 1) {
this.initialHpRatio = 1;
}
if (this.regenInterval < 0) {
warn('[RegenEffect] regenInterval が負の値です。絶対値を使用します。');
this.regenInterval = Math.abs(this.regenInterval);
}
if (this.regenAmount < 0) {
warn('[RegenEffect] regenAmount が負の値です。正の値に反転します。');
this.regenAmount = Math.abs(this.regenAmount);
}
// 初期HPを設定
this._currentHp = this.maxHp * this.initialHpRatio;
this._currentHp = this._clamp(this._currentHp, 0, this.maxHp);
if (this.logDebug) {
log(`[RegenEffect] onLoad: maxHp=${this.maxHp}, initialHp=${this._currentHp}`);
}
}
start() {
// 自動回復の開始条件をチェック
this._updateRegenSchedule();
}
onDisable() {
// ノードが無効化されたらスケジュールを停止
this._unscheduleRegen();
}
onDestroy() {
// ノード破棄時もスケジュールを停止
this._unscheduleRegen();
}
/**
* 内部用: 値を指定範囲にクランプする。
*/
private _clamp(value: number, min: number, max: number): number {
if (value < min) return min;
if (value > max) return max;
return value;
}
/**
* 自動回復のスケジュール状態を、現在の設定に合わせて更新する。
*/
private _updateRegenSchedule() {
// いったん全て解除
this._unscheduleRegen();
if (!this.enableRegen) {
if (this.logDebug) {
log('[RegenEffect] 自動回復は無効化されています。');
}
return;
}
if (this.regenInterval <= 0) {
warn('[RegenEffect] regenInterval が 0 以下のため、自動回復は実行されません。');
return;
}
if (this.regenAmount <= 0) {
warn('[RegenEffect] regenAmount が 0 以下のため、自動回復は実行されません。');
return;
}
// すでに最大HPなら、必要に応じて停止
if (this._currentHp >= this.maxHp && this.stopRegenAtFullHp) {
if (this.logDebug) {
log('[RegenEffect] すでに最大HPのため、自動回復は開始しません。');
}
return;
}
// 一定間隔で doRegen を呼び出す
this.schedule(this.doRegen, this.regenInterval);
this._isRegenScheduled = true;
if (this.logDebug) {
log(`[RegenEffect] 自動回復を開始: interval=${this.regenInterval}, amount=${this.regenAmount}`);
}
}
/**
* 自動回復のスケジュールを解除する。
*/
private _unscheduleRegen() {
if (this._isRegenScheduled) {
this.unschedule(this.doRegen);
this._isRegenScheduled = false;
if (this.logDebug) {
log('[RegenEffect] 自動回復を停止しました。');
}
}
}
/**
* スケジュールから呼ばれる実際の回復処理。
*/
private doRegen() {
// すでに最大HPの場合
if (this._currentHp >= this.maxHp) {
this._currentHp = this.maxHp;
if (this.stopRegenAtFullHp) {
if (this.logDebug) {
log('[RegenEffect] HPが最大に達したため、自動回復を停止します。');
}
this._unscheduleRegen();
}
return;
}
const before = this._currentHp;
this._currentHp += this.regenAmount;
this._currentHp = this._clamp(this._currentHp, 0, this.maxHp);
if (this.logDebug) {
log(`[RegenEffect] 自動回復: ${before} → ${this._currentHp}`);
}
}
// ====== パブリックAPI(任意で利用可能) ======
/**
* 現在のHPを取得します。
*/
public getCurrentHp(): number {
return this._currentHp;
}
/**
* 最大HPを取得します。
*/
public getMaxHp(): number {
return this.maxHp;
}
/**
* ダメージを与えてHPを減少させます。
* @param amount 減少させるHP量(正の値を想定)
*/
public applyDamage(amount: number): void {
if (amount < 0) {
warn('[RegenEffect] applyDamage に負の値が渡されました。絶対値を使用します。');
amount = Math.abs(amount);
}
const before = this._currentHp;
this._currentHp -= amount;
this._currentHp = this._clamp(this._currentHp, 0, this.maxHp);
if (this.logDebug) {
log(`[RegenEffect] ダメージ: ${before} → ${this._currentHp} (amount=${amount})`);
}
// HPが減った結果、自動回復が必要であれば再スケジュール
if (this.enableRegen && !this._isRegenScheduled) {
this._updateRegenSchedule();
}
}
/**
* 即時回復を行います。
* @param amount 増加させるHP量(正の値を想定)
*/
public heal(amount: number): void {
if (amount < 0) {
warn('[RegenEffect] heal に負の値が渡されました。絶対値を使用します。');
amount = Math.abs(amount);
}
const before = this._currentHp;
this._currentHp += amount;
this._currentHp = this._clamp(this._currentHp, 0, this.maxHp);
if (this.logDebug) {
log(`[RegenEffect] 即時回復: ${before} → ${this._currentHp} (amount=${amount})`);
}
// HPが最大に達した場合、必要に応じて自動回復を停止
if (this._currentHp >= this.maxHp && this.stopRegenAtFullHp) {
this._unscheduleRegen();
}
}
/**
* 自動回復の有効/無効を切り替えます。
*/
public setRegenEnabled(enabled: boolean): void {
this.enableRegen = enabled;
this._updateRegenSchedule();
}
}
コードの要点解説
- onLoad
- maxHp, initialHpRatio, regenInterval, regenAmount を安全な値に補正。
_currentHpをmaxHp * initialHpRatioから計算し、0〜maxHp にクランプ。
- start
_updateRegenSchedule()を呼び出し、プロパティの状態に応じて自動回復のスケジュールを開始。- 条件を満たさない場合(interval や amount が 0 など)は、警告を出して何もしない。
- _updateRegenSchedule / _unscheduleRegen
- 現在の設定に合わせて
schedule(this.doRegen, this.regenInterval)を開始・停止。 - 二重スケジュールを防ぐため、フラグ
_isRegenScheduledで管理。
- 現在の設定に合わせて
- doRegen
- HP が最大なら、
stopRegenAtFullHpに応じてスケジュールを停止。 - それ以外なら
regenAmount分だけ HP を増加させ、最大値を超えないようにクランプ。 logDebugが true のときのみログ出力。
- HP が最大なら、
- applyDamage / heal
- 外部から任意にダメージや即時回復を行いたいときに利用可能。
- ダメージ後に自動回復が有効で、まだスケジュールされていなければ再スケジュール。
使用手順と動作確認
1. スクリプトファイルの作成
- エディタ左下の Assets パネルで、スクリプトを置きたいフォルダ(例:
assets/scripts)を選択します。 - フォルダ内で右クリック → Create → TypeScript を選択します。
- 作成されたファイル名を
RegenEffect.tsに変更します。 RegenEffect.tsをダブルクリックして開き、内容をすべて削除して、前述の TypeScript コードを貼り付けて保存します。
2. テスト用ノードの作成
- 上部メニューから Scene を開き、テスト用のシーン(例:
TestScene.scene)を開きます。 - 中央上部の Hierarchy パネルで、空の場所を右クリック → Create → 3D Object → Node など、何でもよいのでノードを 1 つ作成します。
- 2D ゲームの場合は Create → UI → Sprite などでも構いません。
- 作成したノードの名前を分かりやすく
PlayerやTestRegenなどに変更します。
3. RegenEffect コンポーネントをアタッチ
- Hierarchy で先ほど作成したノードを選択します。
- 右側の Inspector パネルの下部にある Add Component ボタンをクリックします。
- メニューから Custom → RegenEffect を選択します。
- もし一覧に見つからない場合は、スクリプトのコンパイル中か、クラス名とファイル名の綴りを確認してください。
4. プロパティの設定例
Inspector の RegenEffect セクションに以下のようなプロパティが表示されます。
- Max Hp(maxHp)
- Initial Hp Ratio(initialHpRatio)
- Enable Regen(enableRegen)
- Regen Amount(regenAmount)
- Regen Interval(regenInterval)
- Stop Regen At Full Hp(stopRegenAtFullHp)
- Log Debug(logDebug)
まずは以下のように設定してみてください。
- Max Hp:
100 - Initial Hp Ratio:
0.5(HP50から開始) - Enable Regen: チェック
ON - Regen Amount:
5 - Regen Interval:
1.0(1秒ごとに5回復) - Stop Regen At Full Hp: チェック
ON - Log Debug: チェック
ON(動作確認用にログを出す)
5. 動作確認(プレビュー)
- エディタ右上の Play ボタン(▶)をクリックして、ゲームをプレビューします。
- プレビューウィンドウが開いたら、Cocos Creator の Console タブを確認します。
- 毎秒ごとに、以下のようなログが流れていれば成功です。
[RegenEffect] 自動回復: 50 → 55[RegenEffect] 自動回復: 55 → 60- …
[RegenEffect] HPが最大に達したため、自動回復を停止します。
6. ダメージとの連携を試す(任意)
よりゲームらしい挙動を確認するために、一定時間ごとにダメージを与える簡単なテストスクリプトを追加してもよいでしょう。
例として、同じノードに以下のような簡易テストスクリプトを追加することで、
「ダメージ → 自動回復 → ダメージ → …」の挙動を確認できます。
import { _decorator, Component } from 'cc';
import { RegenEffect } from './RegenEffect';
const { ccclass } = _decorator;
@ccclass('RegenTester')
export class RegenTester extends Component {
private _timer = 0;
update(deltaTime: number) {
this._timer += deltaTime;
if (this._timer >= 3.0) { // 3秒ごとにダメージ
this._timer = 0;
const regen = this.getComponent(RegenEffect);
if (regen) {
regen.applyDamage(20);
}
}
}
}
この RegenTester はあくまで「テスト用」の例なので、本番用のゲームロジックには依存させず、RegenEffect 単体で機能する構成を維持できます。
まとめ
RegenEffectは、ノードにアタッチするだけで「HP管理+一定間隔の自動回復」を実現する汎用コンポーネントです。- 外部の GameManager や HealthManager に依存せず、このスクリプト単体で完結するため、小さなプロトタイプから本格的なゲームまで簡単に組み込めます。
- インスペクタから
- 最大HP(maxHp)
- 開始時のHP割合(initialHpRatio)
- 回復量(regenAmount)
- 回復間隔(regenInterval)
- 最大HP到達時の挙動(stopRegenAtFullHp)
を調整できるため、キャラクターごとに異なる自動回復特性を簡単に設定できます。
- パブリックメソッド(
applyDamage,heal,setRegenEnabledなど)を使えば、
既存の攻撃判定やUI更新ロジックとも柔軟に連携できます。
このように、「状態管理+時間経過処理」を1つの独立コンポーネントにまとめることで、
シーン構成やゲームロジックをシンプルに保ちつつ、再利用性の高い設計が可能になります。
別のステータス(MP、スタミナ、シールド耐久など)にも、同じパターンで簡単に応用できます。




