【Cocos Creator】アタッチするだけ!PressurePlate (感圧スイッチ)の実装方法【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】PressurePlate(感圧スイッチ)の実装:アタッチするだけで「上に物が乗っている間だけONになるスイッチ」を実現する汎用スクリプト

このガイドでは、Cocos Creator 3.8.7 と TypeScript で、「上に物が乗っている間だけ ON になる感圧スイッチ」を実現する汎用コンポーネント PressurePlate を実装します。

このコンポーネントは、Plate 自身が 2D 物理のトリガー(センサー)として機能し、何かが乗っている間だけ ON になります。ON/OFF 状態はインスペクタから他のノードに通知できるようにしつつ、外部のカスタムスクリプトや GameManager に一切依存しない設計にします。

プレートの見た目の変更(押されているときに沈む/色が変わるなど)も、このコンポーネント単体で完結できるように、スケール・位置・色の変化をオプションとして組み込みます。


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

機能要件の整理

  • PressurePlate がアタッチされたノードは「感圧スイッチ」として動作する。
  • 上に 1 つ以上の物体(Collider2D を持つノード)が乗っている間は pressed = true
  • 誰も乗っていないときは pressed = false
  • ON/OFF 状態の変化時にのみ、インスペクタから設定した「ターゲットノード」に対して カスタムイベントを送信できる。
  • Plate 自身の見た目を、押されている間だけ変化させる(位置・スケール・色)。
  • 2D 物理(Collider2D)の トリガー(sensor) として動作させる。
  • 外部のカスタムスクリプトに依存しない。必要なものはすべてインスペクタの @property から設定する。
  • 必須コンポーネント(Collider2D, 必要に応じて Sprite)が無い場合は、エラーログを出して安全に動作を止める

動作イメージ

  • PressurePlate ノードに BoxCollider2D などの Collider を付け、Is Trigger(sensor) にしておく。
  • プレイヤーや箱などのノードがこの範囲に入ると pressedtrue になり、出ると false になる。
  • 状態が変わったタイミングで、ターゲットノードに対して plate-pressed / plate-released などのイベント名で通知する。
  • 同時に、プレート自身の見た目が変わる(沈む・縮む・色が変わるなど)。

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

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

基本設定

  • @property(Node) targetNode
    • 説明: ON/OFF の通知を送りたいノード。
    • 役割: 状態変化時に emit する先。未設定でも動作はするが、その場合はイベント送信を行わない。
  • @property({ tooltip: 'プレートが押されたときに送信するイベント名' }) pressedEventName: string
    • 説明: pressedfalse → true になったときに targetNode へ送るイベント名。
    • 例: "plate-pressed"
  • @property({ tooltip: 'プレートが離されたときに送信するイベント名' }) releasedEventName: string
    • 説明: pressedtrue → false になったときに targetNode へ送るイベント名。
    • 例: "plate-released"

フィルタ設定(何が乗ったら反応するか)

  • @property useTagFilter: boolean
    • 説明: true の場合、指定タグを持つ Collider2D のみをカウントする。
  • @property allowedTag: number
    • 説明: useTagFiltertrue のときに有効。Collider2D.tag がこの値と一致するオブジェクトだけがスイッチを押せる。
    • 例: プレイヤーだけ反応させたいなら、プレイヤー側の Collider2D.tag = 1 にし、ここも 1 に設定。

見た目の変化設定(オプション)

  • @property animatePosition: boolean
    • 説明: true の場合、押されている間だけローカル位置を変化させる。
  • @property(Vec3) pressedLocalOffset: Vec3
    • 説明: 押されたときに追加されるローカル位置オフセット。
    • 例: (0, -5, 0) で少し沈ませる。
  • @property animateScale: boolean
    • 説明: true の場合、押されている間だけローカルスケールを変化させる。
  • @property(Vec3) pressedScale: Vec3
    • 説明: 押されている間に適用されるスケール。
    • 例: (1, 0.8, 1) で縦方向に少しつぶす。
  • @property animateColor: boolean
    • 説明: true の場合、押されている間だけ Sprite の色を変える。
  • @property(Color) pressedColor: Color
    • 説明: 押されている間に適用される Sprite の色。
    • 注意: ノードに Sprite コンポーネントが必要。なければログで警告。

デバッグ/状態確認用

  • @property({ readonly: true }) pressed: boolean
    • 説明: 現在の ON/OFF 状態を Inspector から確認できるようにする読み取り専用フラグ。
  • @property logDebug: boolean
    • 説明: true のとき、状態変化やコライダー検知を console.log に出力する。

外部依存をなくすためのアプローチ

  • 状態管理は PressurePlate 内部で完結させる。
  • 他のノードへの通知は イベント名+ターゲットノード で行い、特定のクラスやシングルトンを前提にしない。
  • ターゲット側は、任意の方法でイベントを受け取れる:
    • targetNode.on('plate-pressed', ...) で受信する。
    • もしくは EventHandler 等を自分で組み合わせる。
  • 必要な標準コンポーネント(Collider2D, Sprite)は onLoad で取得し、無い場合はエラー/警告ログを出す。

TypeScriptコードの実装


import { _decorator, Component, Node, Collider2D, Contact2DType, IPhysics2DContact, Vec3, Color, Sprite, log, warn, error } from 'cc';
const { ccclass, property } = _decorator;

/**
 * PressurePlate
 * 上に物が乗っている間だけ pressed = true になる感圧スイッチ。
 * Collider2D (トリガー) を利用して 2D 物理オブジェクトを検知します。
 */
@ccclass('PressurePlate')
export class PressurePlate extends Component {

    // === 基本設定 ===

    @property({
        tooltip: '状態変化イベントを送信するターゲットノード。\n未設定の場合、イベント送信は行いません。'
    })
    public targetNode: Node | null = null;

    @property({
        tooltip: 'プレートが押されたとき (false → true) に targetNode に送信するイベント名。\n空文字の場合、送信しません。'
    })
    public pressedEventName: string = 'plate-pressed';

    @property({
        tooltip: 'プレートが離されたとき (true → false) に targetNode に送信するイベント名。\n空文字の場合、送信しません。'
    })
    public releasedEventName: string = 'plate-released';

    // === フィルタ設定 ===

    @property({
        tooltip: '有効にすると、Collider2D.tag が allowedTag と一致するものだけをカウントします。'
    })
    public useTagFilter: boolean = false;

    @property({
        tooltip: 'useTagFilter が true のときのみ有効。\nこのタグ値を持つ Collider2D だけがプレートを押せます。'
    })
    public allowedTag: number = 0;

    // === 見た目の変化設定 ===

    @property({
        tooltip: '押されている間、ローカル位置を変化させます。'
    })
    public animatePosition: boolean = true;

    @property({
        tooltip: '押されている間に追加されるローカル位置オフセット。\n例: (0, -5, 0) で少し沈ませる。'
    })
    public pressedLocalOffset: Vec3 = new Vec3(0, -5, 0);

    @property({
        tooltip: '押されている間、ローカルスケールを変化させます。'
    })
    public animateScale: boolean = false;

    @property({
        tooltip: '押されている間に適用されるスケール。\n例: (1, 0.8, 1) で縦方向に少し潰す。'
    })
    public pressedScale: Vec3 = new Vec3(1, 0.8, 1);

    @property({
        tooltip: '押されている間、Sprite の色を変化させます。\nノードに Sprite が無い場合は警告を出します。'
    })
    public animateColor: boolean = false;

    @property({
        tooltip: '押されている間に適用される色。'
    })
    public pressedColor: Color = new Color(200, 200, 200, 255);

    // === デバッグ / 状態確認 ===

    @property({
        tooltip: '現在プレートが押されているかどうか (読み取り専用)。',
        readonly: true
    })
    public pressed: boolean = false;

    @property({
        tooltip: 'true にすると、状態変化やコライダー検知をコンソールログに出力します。'
    })
    public logDebug: boolean = false;

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

    private _collider: Collider2D | null = null;
    private _sprite: Sprite | null = null;

    private _baseLocalPosition: Vec3 = new Vec3();
    private _baseLocalScale: Vec3 = new Vec3(1, 1, 1);
    private _baseColor: Color | null = null;

    // 現在プレート上に乗っている有効なコライダー数
    private _activeContacts: number = 0;

    onLoad() {
        // 必須コンポーネント: Collider2D
        this._collider = this.getComponent(Collider2D);
        if (!this._collider) {
            error('[PressurePlate] Collider2D がアタッチされていません。このノードに BoxCollider2D などを追加してください。');
            return;
        }

        // センサーとして動作させることを推奨
        if (!this._collider.sensor) {
            warn('[PressurePlate] Collider2D.sensor が false です。トリガーとして動作させたい場合は Inspector で「Is Trigger」を有効にしてください。');
        }

        // Sprite (任意)
        this._sprite = this.getComponent(Sprite);
        if (!this._sprite && this.animateColor) {
            warn('[PressurePlate] animateColor が有効ですが、このノードに Sprite コンポーネントがありません。色変化は行われません。');
        }

        // 基本状態の保存
        this._baseLocalPosition = this.node.position.clone();
        this._baseLocalScale = this.node.scale.clone();
        if (this._sprite) {
            this._baseColor = this._sprite.color.clone();
        }

        // 物理イベントの登録
        this._collider.on(Contact2DType.BEGIN_CONTACT, this._onBeginContact, this);
        this._collider.on(Contact2DType.END_CONTACT, this._onEndContact, this);
    }

    onDestroy() {
        if (this._collider) {
            this._collider.off(Contact2DType.BEGIN_CONTACT, this._onBeginContact, this);
            this._collider.off(Contact2DType.END_CONTACT, this._onEndContact, this);
        }
    }

    /**
     * 何かがプレート上に乗り始めたとき
     */
    private _onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
        if (!this._isValidPusher(otherCollider)) {
            return;
        }

        this._activeContacts++;
        if (this.logDebug) {
            log(`[PressurePlate] BEGIN_CONTACT: active=${this._activeContacts}, other=${otherCollider.node.name}`);
        }

        if (!this.pressed) {
            this._setPressed(true);
        }
    }

    /**
     * 何かがプレートから離れたとき
     */
    private _onEndContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
        if (!this._isValidPusher(otherCollider)) {
            return;
        }

        this._activeContacts = Math.max(0, this._activeContacts - 1);
        if (this.logDebug) {
            log(`[PressurePlate] END_CONTACT: active=${this._activeContacts}, other=${otherCollider.node.name}`);
        }

        if (this._activeContacts === 0 && this.pressed) {
            this._setPressed(false);
        }
    }

    /**
     * この Collider2D がプレートを押せる対象かどうかを判定
     */
    private _isValidPusher(other: Collider2D): boolean {
        if (!other) {
            return false;
        }
        if (!this.useTagFilter) {
            return true;
        }
        return other.tag === this.allowedTag;
    }

    /**
     * pressed 状態を変更し、見た目とイベントを更新
     */
    private _setPressed(value: boolean) {
        if (this.pressed === value) {
            return;
        }

        this.pressed = value;

        // 見た目の更新
        this._updateVisual();

        // イベント送信
        this._emitStateEvent();

        if (this.logDebug) {
            log(`[PressurePlate] State changed: pressed=${this.pressed}`);
        }
    }

    /**
     * 押されているかどうかに応じて見た目を更新
     */
    private _updateVisual() {
        // 位置
        if (this.animatePosition) {
            if (this.pressed) {
                this.node.setPosition(
                    this._baseLocalPosition.x + this.pressedLocalOffset.x,
                    this._baseLocalPosition.y + this.pressedLocalOffset.y,
                    this._baseLocalPosition.z + this.pressedLocalOffset.z
                );
            } else {
                this.node.setPosition(this._baseLocalPosition);
            }
        }

        // スケール
        if (this.animateScale) {
            if (this.pressed) {
                this.node.setScale(this.pressedScale);
            } else {
                this.node.setScale(this._baseLocalScale);
            }
        }

        // 色
        if (this.animateColor && this._sprite) {
            if (this.pressed) {
                this._sprite.color = this.pressedColor;
            } else if (this._baseColor) {
                this._sprite.color = this._baseColor;
            }
        }
    }

    /**
     * 状態変化イベントを targetNode に送信
     */
    private _emitStateEvent() {
        if (!this.targetNode) {
            return;
        }

        if (this.pressed) {
            if (this.pressedEventName && this.pressedEventName.length > 0) {
                this.targetNode.emit(this.pressedEventName, this.node);
            }
        } else {
            if (this.releasedEventName && this.releasedEventName.length > 0) {
                this.targetNode.emit(this.releasedEventName, this.node);
            }
        }
    }
}

コードの主要部分の解説

  • onLoad()
    • Collider2D を取得し、無ければ error ログを出して終了します。
    • Sprite を取得し、animateColor が有効なのに Sprite がない場合は warn を出します。
    • プレートの初期位置・スケール・色を保存します。
    • BEGIN_CONTACT / END_CONTACT イベントを登録して、乗った/降りたを検知します。
  • _onBeginContact(), _onEndContact()
    • _isValidPusher() でタグフィルタを通過したコライダーだけを対象にします。
    • 有効な接触数 _activeContacts をカウントし、0 → 1 になったタイミングで pressed = true、1 → 0 になったタイミングで pressed = false にします。
  • _setPressed()
    • 内部の pressed フラグを更新し、見た目の更新とイベント送信を行います。
    • 同じ値が連続して設定される場合は何もしません(不要なイベント発火を防ぐ)。
  • _updateVisual()
    • animatePosition, animateScale, animateColor の各フラグに応じて、押されている間の見た目を変更します。
    • いずれも onLoad で保存した「元の状態」に必ず戻せるようにしています。
  • _emitStateEvent()
    • targetNode が設定されている場合のみ、Node.emit でイベントを送信します。
    • 押されたとき: pressedEventName(例: "plate-pressed"
    • 離されたとき: releasedEventName(例: "plate-released"
    • イベントの第一引数として「このプレート自身の Node」を渡しているので、受け側でどのプレートか判別できます。

使用手順と動作確認

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

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

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

  1. Hierarchy パネルで右クリック → Create → 2D Object → Sprite などを選択し、テスト用ノードを作成します。
  2. 作成したノードの名前を PressurePlate など分かりやすい名前に変更します。
  3. そのノードを選択し、Inspector の Add Component → Physics 2D → BoxCollider2D(もしくは他の 2D Collider)を追加します。
  4. 追加した Collider2D の設定で:
    • Enabled を ON にする。
    • Is Trigger(または Sensor)にチェックを入れて、トリガーとして動作させます。
    • 必要に応じて Tag を設定します(後でフィルタに使う場合)。
  5. 同じノードの Inspector で Add Component → Custom → PressurePlate を選択し、スクリプトをアタッチします。

3. 押す側(プレイヤーや箱)の準備

  1. Hierarchy で右クリック → Create → 2D Object → Sprite などで、プレイヤーや箱のノードを作成します。
  2. そのノードに RigidBody2DBoxCollider2D(または他の 2D Collider)を追加します。
    • プレイヤーを動かしたい場合は、RigidBody2D の Type を Dynamic にします。
  3. PressurePlate が 特定のオブジェクトだけに反応してほしい場合:
    • プレイヤー側の Collider2D.tag を例として 1 に設定します。
    • PressurePlate 側の useTagFiltertrue にし、allowedTag1 に設定します。

4. PressurePlate の Inspector 設定例

PressurePlate ノードを選択し、Inspector で以下のように設定してみます。

  • targetNode: ドア用のノード(後で作る)をドラッグ&ドロップ。
  • pressedEventName: door-open
  • releasedEventName: door-close
  • useTagFilter: 必要に応じて ON(プレイヤーだけ反応させたいなど)。
  • allowedTag: プレイヤーの Collider2D に設定したタグ値(例: 1)。
  • animatePosition: ON
  • pressedLocalOffset: (0, -5, 0)
  • animateScale: OFF(まずはシンプルに)
  • animateColor: ON(ノードに Sprite が付いている場合)
  • pressedColor: 少し暗い色に設定(例: RGBA = 180, 180, 180, 255)
  • logDebug: 動作確認中は ON にしてログを確認すると便利です。

5. ドア用ノードとイベント受信の簡易実装例

このコンポーネント自体は完全に独立ですが、「イベントをどう受け取るか」の一例として、最小限のドアスクリプトを紹介します(任意)。

  1. Assets パネルで右クリック → Create → TypeScript → ファイル名を SimpleDoor.ts にします。
  2. 以下のような簡単なスクリプトを貼り付けます。

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

@ccclass('SimpleDoor')
export class SimpleDoor extends Component {
    @property
    public openOffsetY: number = 100;

    private _closedPos: Vec3 = new Vec3();
    private _openPos: Vec3 = new Vec3();

    onLoad() {
        this._closedPos = this.node.position.clone();
        this._openPos = this._closedPos.clone();
        this._openPos.y += this.openOffsetY;

        // PressurePlate からのイベント名に合わせておく
        this.node.on('door-open', this._onOpen, this);
        this.node.on('door-close', this._onClose, this);
    }

    private _onOpen() {
        this.node.setPosition(this._openPos);
    }

    private _onClose() {
        this.node.setPosition(this._closedPos);
    }
}

このドアを使う場合の手順:

  1. Hierarchy で右クリック → Create → 2D Object → Sprite などでドア用ノードを作成し、名前を Door にします。
  2. Door ノードに SimpleDoor コンポーネントをアタッチします。
  3. PressurePlate の targetNode に、この Door ノードをドラッグ&ドロップします。
  4. PressurePlate の pressedEventNamedoor-openreleasedEventNamedoor-close に設定します。

これで、プレイヤーがプレートに乗るとドアが開き、離れると閉じる動作を確認できます。

6. シミュレーションで動作確認

  1. 2D シーンに Main CameraCanvas があることを確認します(2D テンプレートなら最初からあります)。
  2. PressurePlate ノードとプレイヤー(または箱)ノードが視界内にあるように配置します。
  3. 上メニューから Project → Project Settings → Physics 2D を開き、2D 物理が有効になっていることを確認します。
  4. 再生ボタン(シミュレーション)を押し、プレイヤーや箱をプレートの上に落としたり、移動させたりしてみます。
  5. 期待される動作:
    • プレイヤーがプレートの上に乗ると、プレートが少し沈み、色が変わる。
    • Inspector の pressed プロパティが false → true に変化する。
    • logDebug が ON なら、コンソールに BEGIN_CONTACT と状態変化ログが出る。
    • Door ノードを設定している場合、ドアが開閉する。

まとめ

このガイドでは、Cocos Creator 3.8.7 用に、「上に物が乗っている間だけ ON になる感圧スイッチ」を実現する汎用コンポーネント PressurePlate を実装しました。

主なポイント:

  • 完全に独立したコンポーネントとして設計し、外部の GameManager やシングルトンに依存しない。
  • 必要な設定はすべてインスペクタの @property から行える:
    • イベント送信先の targetNode とイベント名。
    • どのオブジェクトに反応するかを制御するタグフィルタ。
    • 押されている間の見た目の変化(位置・スケール・色)。
  • 必須コンポーネント(Collider2D)が無い場合は、エラーログで開発者に明示する防御的実装。
  • イベント駆動で他のノードと連携できるため、ドアだけでなく、エレベーター・トラップ・ギミックの起動など、幅広い用途に再利用可能。

この PressurePlate をプロジェクトの「共通ギミック」としてライブラリ化しておけば、シーン内の任意の場所に配置して、Inspector で数値とターゲットを変えるだけで、さまざまなスイッチギミックを素早く構築できます。

必要に応じて、押されるまでの時間遅延や、一定時間だけ ON を維持するタイマー機能などを追加していくことで、さらに高度なギミックへ発展させることも容易です。

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