【Cocos Creator】アタッチするだけ!CriticalHitter (クリティカル判定)の実装方法【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】CriticalHitter の実装:アタッチするだけで「クリティカル確率と倍率つきのダメージ計算」を実現する汎用スクリプト

このガイドでは、攻撃ダメージを扱うあらゆるシーンで使い回せる「クリティカル判定コンポーネント」を実装します。
ノードに CriticalHitter をアタッチしておくだけで、任意のダメージ値に対して「クリティカル確率」「クリティカル倍率」「乱数シード」「ログ出力」などを統一的に扱えるようになります。

本コンポーネントは、他のスクリプトや GameManager に一切依存せず、インスペクタで設定した値だけで動作するように設計します。


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

1. 機能要件の整理

  • 攻撃時に「クリティカル判定」を行い、ダメージを増幅する。
  • クリティカル発生確率(%)をインスペクタから調整できる。
  • クリティカル時のダメージ倍率をインスペクタから調整できる。
  • ダメージ計算処理を「関数」として提供し、他のスクリプトから呼び出して使える。
  • 乱数のブレを抑えるため、任意のシード値で擬似乱数を制御できるオプションを持つ。
  • デバッグ用に、クリティカル判定の結果をログ出力できる。
  • 他のノード・シングルトンに依存せず、このコンポーネント単体で完結する。

CriticalHitter は「何かを自動で動かす」タイプではなく、他のスクリプトから呼び出して使う「ロジック提供コンポーネント」として設計します。
たとえば、あなたの攻撃スクリプト側で:

  • 基礎攻撃力(baseDamage)を計算する。
  • CriticalHitter の applyCritical() に渡して最終ダメージを得る。
  • 得られたダメージと「クリティカル発生フラグ」を使って演出(大きな数字・エフェクトなど)を行う。

このように、「クリティカルロジック」を 1 箇所に集約できるため、ゲーム全体のバランス調整が容易になります。

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

CriticalHitter コンポーネントが持つ @property の一覧と役割です。

  • criticalChance (number)
    • ツールチップ: 0〜100 の範囲で指定するクリティカル発生確率(%)
    • 単位はパーセント(%)。例: 25 と設定すると 25% の確率でクリティカル。
    • 防御的に 0〜100 の範囲にクランプして使用します。
  • criticalMultiplier (number)
    • ツールチップ: クリティカル時のダメージ倍率。1.0 なら増加なし、2.0 で2倍
    • クリティカルが発生したときに、元のダメージにこの倍率を掛け合わせます。
    • 1.0 未満の値が設定された場合でも、そのまま使用(ペナルティ的な演出にも対応)。
  • useSeedRandom (boolean)
    • ツールチップ: true の場合、擬似乱数シードを使って再現性のある結果を得る
    • ON: 内部のシード値に基づく擬似乱数で判定。
    • OFF: Math.random() を使用。
  • seed (number)
    • ツールチップ: useSeedRandom が true のときに使用する初期シード値
    • 整数値推奨。ゲーム開始時に同じシードを使えば、同じクリティカル発生パターンを再現できます。
    • 負数や 0 が設定されても、そのまま内部で正規化して扱います。
  • autoIncrementSeed (boolean)
    • ツールチップ: true の場合、判定ごとにシードを自動で進める
    • ON: 毎回違う結果になりやすくするため、判定のたびにシードを更新します。
    • OFF: まったく同じシードから何度も判定する(テスト用途など)。
  • enableDebugLog (boolean)
    • ツールチップ: 判定結果や最終ダメージをコンソールにログ出力する
    • ON: クリティカルの有無・ダメージ値・乱数値を console.log に出力。
    • OFF: ログ出力なしで静かに動作。
  • roundDamage (boolean)
    • ツールチップ: 最終ダメージを整数に丸めるかどうか
    • ON: Math.round で整数に丸める。
    • OFF: 小数ダメージをそのまま返す。

また、外部から使うためのメイン API として、以下のメソッドを公開します。

  • applyCritical(baseDamage: number): { finalDamage: number; isCritical: boolean; randomValue: number; }
    • 引数: クリティカル判定前の基礎ダメージ。
    • 戻り値:
      • finalDamage: クリティカル倍率が適用された最終ダメージ。
      • isCritical: クリティカルが発生したかどうか。
      • randomValue: 判定に使用した 0〜1 の乱数値(デバッグ用)。

TypeScriptコードの実装

以下が完成した CriticalHitter.ts の全コードです。


import { _decorator, Component } from 'cc';
const { ccclass, property } = _decorator;

/**
 * CriticalHitter
 * 任意のダメージ値に対して「クリティカル判定+倍率適用」を行う汎用コンポーネント。
 *
 * 他のスクリプトから applyCritical() を呼び出して使用します。
 * 例:
 *   const ch = this.node.getComponent(CriticalHitter);
 *   if (ch) {
 *       const result = ch.applyCritical(100);
 *       console.log(result.finalDamage, result.isCritical);
 *   }
 */
@ccclass('CriticalHitter')
export class CriticalHitter extends Component {

    @property({
        tooltip: 'クリティカル発生確率(%)。0〜100 の範囲で指定します。例: 25 で 25% の確率。',
        slide: true,
        min: 0,
        max: 100,
    })
    public criticalChance: number = 20;

    @property({
        tooltip: 'クリティカル時のダメージ倍率。1.0 で増加なし、2.0 で 2 倍ダメージ。',
        min: 0,
    })
    public criticalMultiplier: number = 2.0;

    @property({
        tooltip: 'true の場合、擬似乱数シードを使って再現性のある結果を得ます。',
    })
    public useSeedRandom: boolean = false;

    @property({
        tooltip: 'useSeedRandom が true のときに使用する初期シード値(整数推奨)。',
    })
    public seed: number = 12345;

    @property({
        tooltip: 'true の場合、判定ごとにシードを自動で進めて毎回違う結果になりやすくします。',
    })
    public autoIncrementSeed: boolean = true;

    @property({
        tooltip: 'true の場合、クリティカル判定の詳細とダメージをコンソールにログ出力します。',
    })
    public enableDebugLog: boolean = false;

    @property({
        tooltip: 'true の場合、最終ダメージを整数に丸めます (Math.round)。',
    })
    public roundDamage: boolean = true;

    // 内部で使用する現在のシード値
    private _currentSeed: number = 0;

    onLoad() {
        // シード乱数を使用する場合は現在シードを初期化
        this._currentSeed = this.seed;

        // 防御的チェック: プロパティの範囲をクランプしておく
        if (this.criticalChance < 0) {
            console.warn('[CriticalHitter] criticalChance が 0 未満だったため 0 に補正しました。');
            this.criticalChance = 0;
        } else if (this.criticalChance > 100) {
            console.warn('[CriticalHitter] criticalChance が 100 を超えていたため 100 に補正しました。');
            this.criticalChance = 100;
        }

        if (this.criticalMultiplier < 0) {
            console.warn('[CriticalHitter] criticalMultiplier が 0 未満だったため 0 に補正しました。');
            this.criticalMultiplier = 0;
        }

        if (this.enableDebugLog) {
            console.log('[CriticalHitter] Initialized on node:', this.node.name);
        }
    }

    /**
     * 擬似乱数を生成する簡易 Linear Congruential Generator。
     * 0 以上 1 未満の値を返します。
     */
    private _nextRandom(): number {
        // 乱数の元となるシードを正規化
        if (!Number.isFinite(this._currentSeed)) {
            this._currentSeed = 0;
        }

        // LCG パラメータ (例: Numerical Recipes)
        const a = 1664525;
        const c = 1013904223;
        const m = 0x100000000; // 2^32

        this._currentSeed = (a * this._currentSeed + c) % m;

        // 0〜1 に正規化
        const rand = this._currentSeed / m;
        return rand;
    }

    /**
     * 0〜1 の乱数値を取得します。
     * useSeedRandom の設定に応じて Math.random() か LCG を使用します。
     */
    private _getRandom(): number {
        if (this.useSeedRandom) {
            const value = this._nextRandom();
            if (this.autoIncrementSeed) {
                // すでに _nextRandom 内部でシードは更新されているので、
                // 特別な処理は不要だが、意図を明示するためにログのみ行うことも可能。
            }
            return value;
        } else {
            return Math.random();
        }
    }

    /**
     * クリティカル判定を行い、最終ダメージを返します。
     * @param baseDamage クリティカル前の基礎ダメージ
     * @returns { finalDamage, isCritical, randomValue }
     */
    public applyCritical(baseDamage: number): {
        finalDamage: number;
        isCritical: boolean;
        randomValue: number;
    } {
        // NaN や負のダメージに対する防御的チェック
        if (!Number.isFinite(baseDamage)) {
            console.warn('[CriticalHitter] baseDamage が数値ではありません。0 として扱います。', baseDamage);
            baseDamage = 0;
        }

        // クリティカル確率を 0〜1 に変換
        const chance = this.criticalChance / 100.0;

        // 乱数取得
        const rand = this._getRandom();

        // クリティカル発生判定
        const isCritical = rand <= chance && this.criticalMultiplier > 0;

        // ダメージ計算
        let finalDamage = baseDamage;
        if (isCritical) {
            finalDamage = baseDamage * this.criticalMultiplier;
        }

        // 丸め処理
        if (this.roundDamage) {
            finalDamage = Math.round(finalDamage);
        }

        if (this.enableDebugLog) {
            console.log(
                `[CriticalHitter] node="${this.node.name}", baseDamage=${baseDamage}, ` +
                `finalDamage=${finalDamage}, isCritical=${isCritical}, ` +
                `chance=${chance.toFixed(3)}, rand=${rand.toFixed(3)}`
            );
        }

        return {
            finalDamage,
            isCritical,
            randomValue: rand,
        };
    }

    /**
     * 外部から明示的にシードをリセットしたい場合に使用します。
     * @param seed 新しいシード値
     */
    public resetSeed(seed: number): void {
        this.seed = seed;
        this._currentSeed = seed;

        if (this.enableDebugLog) {
            console.log('[CriticalHitter] Seed reset:', seed);
        }
    }
}

コードのポイント解説

  • onLoad()
    • インスペクタで設定された criticalChancecriticalMultiplier の値を防御的に補正しています。
    • _currentSeed を初期化し、シード乱数を使う準備を行います。
  • _nextRandom()
    • 簡易的な LCG(線形合同法)で 0〜1 の擬似乱数を生成します。
    • useSeedRandom が true のときにのみ内部的に利用されます。
  • _getRandom()
    • useSeedRandom の設定に応じて Math.random() か LCG を切り替える窓口です。
  • applyCritical(baseDamage)
    • このコンポーネントのメイン API です。他のスクリプトから呼び出して使います。
    • 0〜100% の確率を 0〜1 に変換し、乱数と比較してクリティカル発生を判定します。
    • クリティカル発生時のみ criticalMultiplier を掛け算して最終ダメージを算出します。
    • roundDamage が true の場合は整数に丸めます。
    • 戻り値には、最終ダメージだけでなく isCriticalrandomValue も含まれているので、演出やデバッグに活用できます。
  • resetSeed(seed)
    • ゲーム中にシードを変更したい場合(例: ステージ開始時にプレイヤーIDベースのシードをセットするなど)に使用できます。

使用手順と動作確認

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

  1. Assets パネルで右クリックします。
  2. Create → TypeScript を選択します。
  3. 新規スクリプトに CriticalHitter.ts という名前を付けます。
  4. 作成された CriticalHitter.ts をダブルクリックしてエディタ(VS Code など)で開き、先ほどのコード全文を貼り付けて保存します。

2. テスト用ノードの作成とコンポーネントのアタッチ

  1. Hierarchy パネルで右クリック → Create → Empty Node を選択し、テスト用ノードを作成します。
  2. ノード名を CriticalTester など分かりやすい名前に変更します。
  3. CriticalTester ノードを選択した状態で、Inspector パネルを確認します。
  4. Inspector 下部の Add Component ボタンをクリックします。
  5. Custom または検索バーから CriticalHitter を探し、クリックしてアタッチします。

これで CriticalHitter コンポーネントがノードに追加され、インスペクタから設定できるようになります。

3. インスペクタでプロパティを設定

CriticalTester ノードにアタッチされた CriticalHitter を選択し、Inspector の各プロパティを確認します。

  • criticalChance: 例として 30(30%)に設定。
  • criticalMultiplier: 例として 2.5(2.5倍ダメージ)に設定。
  • useSeedRandom: 動作確認のため、まずは false にしてランダム性を確認。
  • seed: 一旦デフォルトの 12345 のままで OK。
  • autoIncrementSeed: true(デフォルト)のままで OK。
  • enableDebugLog: ログを見たいので true に設定。
  • roundDamage: true(整数ダメージ)に設定。

4. テスト用の呼び出しスクリプトを作る(任意)

CriticalHitter は applyCritical() を外部から呼び出して使うため、挙動確認用に簡単なスクリプトを作ってみます。

  1. 再び Assets パネルで右クリック → Create → TypeScript を選択し、CriticalTestRunner.ts という名前のスクリプトを作成します。
  2. 以下のような簡単なテストコードを貼り付けます。

import { _decorator, Component } from 'cc';
import { CriticalHitter } from './CriticalHitter';
const { ccclass, property } = _decorator;

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

    @property({ tooltip: 'テスト用の基礎ダメージ値' })
    public baseDamage: number = 100;

    @property({ tooltip: '何回連続でテストするか' })
    public testCount: number = 10;

    start() {
        const ch = this.getComponent(CriticalHitter);
        if (!ch) {
            console.error('[CriticalTestRunner] 同じノードに CriticalHitter コンポーネントが見つかりません。');
            return;
        }

        console.log('--- CriticalTestRunner: テスト開始 ---');
        for (let i = 0; i < this.testCount; i++) {
            const result = ch.applyCritical(this.baseDamage);
            console.log(
                `#${i + 1}: base=${this.baseDamage}, final=${result.finalDamage}, ` +
                `critical=${result.isCritical}, rand=${result.randomValue.toFixed(3)}`
            );
        }
        console.log('--- CriticalTestRunner: テスト終了 ---');
    }
}

このテストスクリプトは必須ではありませんが、動作確認には便利です。
CriticalTester ノードに対して、次の操作を行います。

  1. Hierarchy で CriticalTester ノードを選択。
  2. Inspector の Add Component ボタン → Custom → CriticalTestRunner を追加。
  3. Inspector で baseDamage100testCount10 などに設定。

この状態でゲームを再生すると、コンソールに 10 回分のダメージ結果とクリティカル発生状況が出力されます。
CriticalHitter の criticalChance を変えて、クリティカルの出やすさが変わることを確認してみてください。

5. 実際の攻撃処理への組み込み例

実際のゲームでは、あなたの「攻撃スクリプト」から CriticalHitter を利用する形になります。
簡易的な例を示します(このスクリプトはあくまで例であり、本記事の必須コードではありません)。


import { _decorator, Component } from 'cc';
import { CriticalHitter } from './CriticalHitter';
const { ccclass, property } = _decorator;

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

    @property({ tooltip: 'この攻撃の基礎ダメージ' })
    public baseDamage: number = 50;

    private _criticalHitter: CriticalHitter | null = null;

    onLoad() {
        this._criticalHitter = this.getComponent(CriticalHitter);
        if (!this._criticalHitter) {
            console.warn('[SimpleAttack] CriticalHitter が見つかりません。この攻撃はクリティカルなしで動作します。');
        }
    }

    // 何らかのトリガーで呼ばれる想定の攻撃処理
    public performAttack(): void {
        let damage = this.baseDamage;
        let isCritical = false;

        if (this._criticalHitter) {
            const result = this._criticalHitter.applyCritical(this.baseDamage);
            damage = result.finalDamage;
            isCritical = result.isCritical;
        }

        // ここでダメージを敵に適用したり、UI に表示したりする
        // 例:
        // enemy.takeDamage(damage);
        // damageLabel.show(damage, isCritical);

        console.log(`[SimpleAttack] Attack! damage=${damage}, critical=${isCritical}`);
    }
}

このように、攻撃スクリプトは「基礎ダメージ」と「演出」に集中し、
クリティカル判定のロジックは CriticalHitter に任せることで、責務が明確になります。


まとめ

  • CriticalHitter は、攻撃処理に「クリティカル確率+倍率」を簡単に追加できる汎用コンポーネントです。
  • インスペクタから確率・倍率・シード・ログ出力などを調整できるため、ゲームバランス調整やデバッグがしやすくなります。
  • 他のカスタムスクリプトやシングルトンに依存せず、このコンポーネント単体で完結する設計になっているため、どのプロジェクトにも簡単に持ち込んで再利用できます。
  • 攻撃スクリプト側は applyCritical() を呼び出すだけで、クリティカル判定とダメージ計算を一括して行えるため、コードの見通しが良くなります。

この CriticalHitter をベースに、クリティカル時の追加効果(状態異常付与、専用エフェクト再生、SE 再生など)を別コンポーネントとして拡張していけば、
「アタッチするだけで派手なクリティカル演出が完成する」モジュール群に発展させることも可能です。

まずは本記事のコードをそのまま貼り付けて、CriticalHitter コンポーネントをプロジェクトに導入してみてください。

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をコピーしました!