【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()
- インスペクタで設定された
criticalChanceとcriticalMultiplierの値を防御的に補正しています。 _currentSeedを初期化し、シード乱数を使う準備を行います。
- インスペクタで設定された
- _nextRandom()
- 簡易的な LCG(線形合同法)で 0〜1 の擬似乱数を生成します。
useSeedRandomが true のときにのみ内部的に利用されます。
- _getRandom()
useSeedRandomの設定に応じてMath.random()か LCG を切り替える窓口です。
- applyCritical(baseDamage)
- このコンポーネントのメイン API です。他のスクリプトから呼び出して使います。
- 0〜100% の確率を 0〜1 に変換し、乱数と比較してクリティカル発生を判定します。
- クリティカル発生時のみ
criticalMultiplierを掛け算して最終ダメージを算出します。 roundDamageが true の場合は整数に丸めます。- 戻り値には、最終ダメージだけでなく
isCriticalとrandomValueも含まれているので、演出やデバッグに活用できます。
- resetSeed(seed)
- ゲーム中にシードを変更したい場合(例: ステージ開始時にプレイヤーIDベースのシードをセットするなど)に使用できます。
使用手順と動作確認
1. スクリプトファイルの作成
- Assets パネルで右クリックします。
Create → TypeScriptを選択します。- 新規スクリプトに
CriticalHitter.tsという名前を付けます。 - 作成された
CriticalHitter.tsをダブルクリックしてエディタ(VS Code など)で開き、先ほどのコード全文を貼り付けて保存します。
2. テスト用ノードの作成とコンポーネントのアタッチ
- Hierarchy パネルで右クリック →
Create → Empty Nodeを選択し、テスト用ノードを作成します。 - ノード名を
CriticalTesterなど分かりやすい名前に変更します。 CriticalTesterノードを選択した状態で、Inspector パネルを確認します。- Inspector 下部の
Add Componentボタンをクリックします。 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() を外部から呼び出して使うため、挙動確認用に簡単なスクリプトを作ってみます。
- 再び Assets パネルで右クリック →
Create → TypeScriptを選択し、CriticalTestRunner.tsという名前のスクリプトを作成します。 - 以下のような簡単なテストコードを貼り付けます。
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 ノードに対して、次の操作を行います。
- Hierarchy で
CriticalTesterノードを選択。 - Inspector の
Add Componentボタン →Custom → CriticalTestRunnerを追加。 - Inspector で
baseDamageを100、testCountを10などに設定。
この状態でゲームを再生すると、コンソールに 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 コンポーネントをプロジェクトに導入してみてください。




