【Cocos Creator】アタッチするだけ!ElementAffinity (属性耐性)の実装方法【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】ElementAffinity の実装:アタッチするだけで「炎・氷などの属性ダメージ倍率(弱点・半減・無効)」を一元管理できる汎用スクリプト

このガイドでは、RPG やアクションゲームでよく使う「属性耐性(炎に弱い/氷に強い/雷無効…)」を、1つのコンポーネントをノードにアタッチするだけで管理できる ElementAffinity を実装します。

このコンポーネントは、

  • 敵・プレイヤー・ギミックなど「ダメージを受けるノード」にアタッチ
  • インスペクタから「属性ごとの倍率」を設定
  • スクリプトから applyElementDamage / getElementMultiplier を呼ぶだけ

というシンプルな設計で、外部の GameManager やシングルトンに一切依存せず動作します。


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

要件整理

  • 炎・氷などの「属性タイプ」を識別できる。
  • 属性ごとに「被ダメージ倍率」を設定できる(例:炎 2.0 倍で弱点、氷 0.5 倍で半減、雷 0 倍で無効)。
  • スクリプトから簡単に呼べる API を提供する。
    • getElementMultiplier(element: ElementType): number … 属性に対する倍率を返す。
    • applyElementDamage(element: ElementType, baseDamage: number): number … 基礎ダメージに倍率をかけた最終ダメージを返す。
  • コンポーネント単体で完結し、他のカスタムスクリプトに依存しない。
  • インスペクタから属性リストと倍率を直感的に編集できる。

設計アプローチ

Cocos Creator 3.8 では、@propertyEnumArray を扱うことで、インスペクタから簡単に設定できる UI を作れます。これを利用して:

  1. 属性の種類enum ElementType として定義。
  2. 属性ごとの倍率テーブルを、インスペクタから編集可能な配列として持つ。
  3. 配列は「属性 & 倍率」のペア構造にする。

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

ElementAffinity に用意するプロパティと役割は次の通りです。

  • affinityTable: ElementAffinityEntry[]
    • 型:カスタムクラス ElementAffinityEntry の配列。
    • 役割:属性ごとの被ダメージ倍率を登録するテーブル。
    • インスペクタでの使い方:
      • +ボタンで要素を追加。
      • element で「炎 / 氷 / 雷 / 風 / 土 / 光 / 闇 / 無属性」などを選択。
      • multiplier に数値を入力(例:2.0, 1.0, 0.5, 0)。
  • defaultMultiplier: number
    • 役割:テーブルに存在しない属性に対して使う「デフォルト倍率」。
    • 例:1.0(等倍)を基本とし、未登録の属性は 1.0 倍で処理。
  • clampMultiplierMin: number / clampMultiplierMax: number
    • 役割:倍率を安全な範囲にクランプするための下限・上限。
    • 例:010 にして、誤入力で 9999 などを入れても暴走しないようにする。
  • logCalculation: boolean
    • 役割:applyElementDamage を呼んだとき、計算内容を console.log に出すかどうか。
    • デバッグ用。実際のゲームではオフにしておくとよい。

また、インスペクタに直接は出ませんが、倍率テーブルの 1 要素として以下のクラスを定義します。

  • ElementAffinityEntry
    • element: ElementType … 対象の属性。
    • multiplier: number … その属性に対する被ダメージ倍率。

TypeScriptコードの実装


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

/**
 * 属性の種類を表す列挙型
 * 必要に応じて要素を追加・削除してください。
 */
export enum ElementType {
    None = 0,
    Fire = 1,
    Ice = 2,
    Thunder = 3,
    Wind = 4,
    Earth = 5,
    Light = 6,
    Dark = 7,
}

/**
 * インスペクタで編集する「属性 & 倍率」の1エントリ
 */
@ccclass('ElementAffinityEntry')
export class ElementAffinityEntry {
    @property({
        type: Enum(ElementType),
        tooltip: 'このエントリが対象とする属性タイプ',
    })
    public element: ElementType = ElementType.Fire;

    @property({
        tooltip: 'この属性に対する被ダメージ倍率。\n例: 2.0 = 弱点(2倍), 0.5 = 半減, 0 = 無効',
    })
    public multiplier: number = 1.0;
}

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

    @property({
        type: [ElementAffinityEntry],
        tooltip: '属性ごとの被ダメージ倍率テーブル。\n' +
                 '同じ属性を複数登録した場合、最初に見つかったエントリが使用されます。',
    })
    public affinityTable: ElementAffinityEntry[] = [];

    @property({
        tooltip: 'テーブルに存在しない属性に対して使用するデフォルト倍率。\n' +
                 '通常は 1.0 (等倍) を推奨します。',
    })
    public defaultMultiplier: number = 1.0;

    @property({
        tooltip: '倍率の下限値。\n' +
                 '0 にすると「これ以上は減らない(完全無効)」のような扱いができます。',
    })
    public clampMultiplierMin: number = 0.0;

    @property({
        tooltip: '倍率の上限値。\n' +
                 '極端な値(例: 9999)を防ぐための安全装置として利用します。',
    })
    public clampMultiplierMax: number = 10.0;

    @property({
        tooltip: 'true の場合、ダメージ計算時にログを出力します(デバッグ用)。',
    })
    public logCalculation: boolean = false;

    /**
     * ノードにアタッチされた直後に呼ばれます。
     * 主に設定値のバリデーションを行います。
     */
    onLoad() {
        // クランプ範囲の妥当性チェック
        if (this.clampMultiplierMax < this.clampMultiplierMin) {
            console.warn(
                `[ElementAffinity] clampMultiplierMax (${this.clampMultiplierMax}) ` +
                `is less than clampMultiplierMin (${this.clampMultiplierMin}). ` +
                '値を入れ替えます。'
            );
            const tmp = this.clampMultiplierMin;
            this.clampMultiplierMin = this.clampMultiplierMax;
            this.clampMultiplierMax = tmp;
        }

        // デフォルト倍率をクランプ
        this.defaultMultiplier = this._clampMultiplier(this.defaultMultiplier);

        // 各エントリの倍率もクランプしておく
        for (const entry of this.affinityTable) {
            entry.multiplier = this._clampMultiplier(entry.multiplier);
        }
    }

    /**
     * 属性に対する被ダメージ倍率を取得します。
     * テーブルに存在しない場合は defaultMultiplier を返します。
     * 
     * @param element - ダメージの属性タイプ
     * @returns 被ダメージ倍率
     */
    public getElementMultiplier(element: ElementType): number {
        for (const entry of this.affinityTable) {
            if (entry.element === element) {
                return this._clampMultiplier(entry.multiplier);
            }
        }
        return this.defaultMultiplier;
    }

    /**
     * 属性付きダメージを適用し、倍率をかけた「最終ダメージ値」を返します。
     * このコンポーネント自身は HP を持たず、計算のみを担当します。
     * 
     * @param element - ダメージの属性タイプ
     * @param baseDamage - 基礎ダメージ値(例: スキルの威力など)
     * @returns 倍率を適用した最終ダメージ値
     */
    public applyElementDamage(element: ElementType, baseDamage: number): number {
        const multiplier = this.getElementMultiplier(element);
        const finalDamage = baseDamage * multiplier;

        if (this.logCalculation) {
            console.log(
                `[ElementAffinity] element=${ElementType[element]}, ` +
                `baseDamage=${baseDamage}, multiplier=${multiplier}, ` +
                `finalDamage=${finalDamage}`
            );
        }

        return finalDamage;
    }

    /**
     * 倍率を設定された下限〜上限にクランプします。
     */
    private _clampMultiplier(value: number): number {
        return math.clamp(value, this.clampMultiplierMin, this.clampMultiplierMax);
    }
}

コードのポイント解説

  • enum ElementType
    • 炎・氷・雷・風・土・光・闇・無属性を定義。
    • 必要に応じて要素を増減して構いません(例:Poison, Holy など)。
  • ElementAffinityEntry
    • インスペクタで「属性」と「倍率」を 1 行として編集するためのクラス。
    • @ccclass('ElementAffinityEntry') を付けることで、配列の要素としてインスペクタに表示できます。
  • affinityTable
    • 複数の ElementAffinityEntry を持つ配列。
    • インスペクタで+ボタンを押して、属性と倍率をどんどん追加していきます。
  • onLoad()
    • コンポーネントが有効になったタイミングで、設定値の整合性チェックを行います。
    • clampMultiplierMinclampMultiplierMax の大小関係がおかしい場合に警告を出し、値を入れ替えます。
    • デフォルト倍率と各エントリの倍率を、クランプ範囲内に収めます。
  • getElementMultiplier()
    • 外部スクリプトから「この属性の倍率はいくつ?」と問い合わせるためのメソッド。
    • テーブルを先頭から走査し、最初に見つかった一致エントリの倍率を返します。
    • 見つからない場合は defaultMultiplier を返します。
  • applyElementDamage()
    • 「属性 × 基礎ダメージ」から最終ダメージを計算し、数値として返すだけの純粋関数的メソッド。
    • このコンポーネントは HP を持たず、計算ロジックだけに徹しているため、どんなゲームにも組み込みやすいです。
    • logCalculation が true のとき、計算過程をログ出力します(デバッグに便利)。

使用手順と動作確認

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

  1. Assets パネルで右クリック → Create → TypeScript を選択します。
  2. ファイル名を ElementAffinity.ts にします。
  3. 自動生成されたテンプレートコードをすべて削除し、このガイドの「TypeScriptコードの実装」セクションにあるコードを丸ごと貼り付けて保存します。

2. テスト用ノードの作成

ここでは、単純な Sprite ノードを例にしますが、プレイヤーや敵キャラのルートノードなど、どのノードでも構いません。

  1. Hierarchy パネルで右クリック → Create → 2D Object → Sprite を選択します。
  2. 作成されたノード名を TestTarget など分かりやすい名前に変更します。

このノードに属性耐性コンポーネントをアタッチします。

3. ElementAffinity コンポーネントのアタッチ

  1. Hierarchy で先ほど作成した TestTarget ノードを選択します。
  2. Inspector パネルの下部にある 「Add Component」 ボタンをクリックします。
  3. 「Custom」→「ElementAffinity」 を選択して追加します。

これで ElementAffinity がノードにアタッチされ、インスペクタに以下のプロパティが表示されます。

  • Affinity Table
  • Default Multiplier
  • Clamp Multiplier Min
  • Clamp Multiplier Max
  • Log Calculation

4. 属性倍率の設定例

RPG 風の設定例として、次のように入力してみましょう。

  1. Default Multiplier1.0
    • 特に設定していない属性は等倍ダメージ。
  2. Clamp Multiplier Min0.0
  3. Clamp Multiplier Max10.0
  4. Log Calculationtrue(動作確認のため一旦オン)
  5. Affinity Table
    1. サイズを「3」に設定(要素数 3)。
    2. 各要素を次のように設定:
      • 要素 0:
        • elementFire
        • multiplier2.0(炎が弱点で 2 倍ダメージ)
      • 要素 1:
        • elementIce
        • multiplier0.5(氷に強く、半減)
      • 要素 2:
        • elementThunder
        • multiplier0.0(雷完全無効)

この設定により、

  • 炎属性:2 倍
  • 氷属性:0.5 倍
  • 雷属性:0 倍(無効)
  • その他の属性(風・土・光・闇・None など):1 倍(等倍)

という耐性を持ったノードになります。

5. スクリプトからの呼び出しテスト

次に、別の簡単なスクリプトから ElementAffinity を呼び出して、動作を確認します。

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

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

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

    @property({
        type: ElementAffinity,
        tooltip: 'テスト対象の ElementAffinity コンポーネントを持つノードを指定します。',
    })
    public targetAffinity: ElementAffinity | null = null;

    start() {
        if (!this.targetAffinity) {
            console.error('[ElementAffinityTester] targetAffinity が設定されていません。');
            return;
        }

        // 基礎ダメージ 100 を想定して、各属性での最終ダメージを計算
        const baseDamage = 100;

        const fireDamage = this.targetAffinity.applyElementDamage(ElementType.Fire, baseDamage);
        const iceDamage = this.targetAffinity.applyElementDamage(ElementType.Ice, baseDamage);
        const thunderDamage = this.targetAffinity.applyElementDamage(ElementType.Thunder, baseDamage);
        const windDamage = this.targetAffinity.applyElementDamage(ElementType.Wind, baseDamage);

        console.log('=== ElementAffinity Test Result ===');
        console.log(`Fire   : base=${baseDamage}, final=${fireDamage}`);
        console.log(`Ice    : base=${baseDamage}, final=${iceDamage}`);
        console.log(`Thunder: base=${baseDamage}, final=${thunderDamage}`);
        console.log(`Wind   : base=${baseDamage}, final=${windDamage}`);
    }
}

テスターのセットアップ

  1. Hierarchy で右クリック → Create → Empty Node を選択し、Tester という名前にします。
  2. Tester ノードを選択し、Inspector の Add ComponentCustom → ElementAffinityTester を追加します。
  3. ElementAffinityTesterTarget Affinity プロパティに、先ほど作成した TestTarget ノード上の ElementAffinity をドラッグ&ドロップします。

再生してログを確認

  1. エディタ上部の「▶」ボタンでゲームを再生します。
  2. Console パネルを開き、出力を確認します。

先ほどの設定(炎 2.0, 氷 0.5, 雷 0.0, その他 1.0, 基礎ダメージ 100)であれば、ログは次のようになります。


=== ElementAffinity Test Result ===
Fire   : base=100, final=200
Ice    : base=100, final=50
Thunder: base=100, final=0
Wind   : base=100, final=100

さらに ElementAffinityLog Calculation を true にしている場合、applyElementDamage を呼んだタイミングで、個別の計算ログも出力されます。


まとめ

このガイドでは、属性耐性(ElementAffinity) を管理する汎用コンポーネントを、Cocos Creator 3.8 + TypeScript で実装しました。

  • ノードに ElementAffinity をアタッチし、インスペクタから属性ごとの倍率を設定するだけで、「弱点・半減・無効」などの耐性を簡単に表現できます。
  • 外部の GameManager や HP 管理クラスに依存せず、「ダメージ倍率の計算」だけに特化した独立コンポーネントなので、どんなプロジェクトにも組み込みやすい構造になっています。
  • getElementMultiplier / applyElementDamage の 2 つのメソッドを呼ぶだけで、他のスクリプトからシンプルに利用できます。

応用例として:

  • スキルや攻撃スクリプト側で ElementType を持たせ、命中時に applyElementDamage で最終ダメージを算出する。
  • UI 側で「炎耐性 50%」などを表示する際に、getElementMultiplier の値から自動的にパーセント表記を生成する。
  • ボス戦などで、フェーズごとに affinityTable を動的に書き換え、弱点が変化するギミックを実装する。

このように、「計算ロジックを 1 コンポーネントに閉じ込める」ことで、ゲーム全体のコードが見通しやすくなり、再利用性も高まります。必要に応じて属性の種類を増やしたり、状態異常用の別 Enum を追加するなど、プロジェクトに合わせて拡張してみてください。

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