【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) にしておく。 - プレイヤーや箱などのノードがこの範囲に入ると
pressedがtrueになり、出るとfalseになる。 - 状態が変わったタイミングで、ターゲットノードに対して
plate-pressed/plate-releasedなどのイベント名で通知する。 - 同時に、プレート自身の見た目が変わる(沈む・縮む・色が変わるなど)。
インスペクタで設定可能なプロパティ設計
以下のようなプロパティを設計します。
基本設定
@property(Node)targetNode- 説明: ON/OFF の通知を送りたいノード。
- 役割: 状態変化時に
emitする先。未設定でも動作はするが、その場合はイベント送信を行わない。
@property({ tooltip: 'プレートが押されたときに送信するイベント名' })pressedEventName: string- 説明:
pressedがfalse → trueになったときにtargetNodeへ送るイベント名。 - 例:
"plate-pressed"
- 説明:
@property({ tooltip: 'プレートが離されたときに送信するイベント名' })releasedEventName: string- 説明:
pressedがtrue → falseになったときにtargetNodeへ送るイベント名。 - 例:
"plate-released"
- 説明:
フィルタ設定(何が乗ったら反応するか)
@propertyuseTagFilter: boolean- 説明:
trueの場合、指定タグを持つ Collider2D のみをカウントする。
- 説明:
@propertyallowedTag: number- 説明:
useTagFilterがtrueのときに有効。Collider2D.tagがこの値と一致するオブジェクトだけがスイッチを押せる。 - 例: プレイヤーだけ反応させたいなら、プレイヤー側の
Collider2D.tag = 1にし、ここも1に設定。
- 説明:
見た目の変化設定(オプション)
@propertyanimatePosition: boolean- 説明:
trueの場合、押されている間だけローカル位置を変化させる。
- 説明:
@property(Vec3)pressedLocalOffset: Vec3- 説明: 押されたときに追加されるローカル位置オフセット。
- 例:
(0, -5, 0)で少し沈ませる。
@propertyanimateScale: boolean- 説明:
trueの場合、押されている間だけローカルスケールを変化させる。
- 説明:
@property(Vec3)pressedScale: Vec3- 説明: 押されている間に適用されるスケール。
- 例:
(1, 0.8, 1)で縦方向に少しつぶす。
@propertyanimateColor: boolean- 説明:
trueの場合、押されている間だけSpriteの色を変える。
- 説明:
@property(Color)pressedColor: Color- 説明: 押されている間に適用される
Spriteの色。 - 注意: ノードに
Spriteコンポーネントが必要。なければログで警告。
- 説明: 押されている間に適用される
デバッグ/状態確認用
@property({ readonly: true })pressed: boolean- 説明: 現在の ON/OFF 状態を Inspector から確認できるようにする読み取り専用フラグ。
@propertylogDebug: 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. スクリプトファイルの作成
- Assets パネルで右クリック → Create → TypeScript を選択します。
- ファイル名を
PressurePlate.tsにします。 - 自動生成された中身をすべて削除し、このガイドの「TypeScriptコードの実装」にあるコードを丸ごと貼り付けて保存します。
2. テスト用の PressurePlate ノードを作成
- Hierarchy パネルで右クリック → Create → 2D Object → Sprite などを選択し、テスト用ノードを作成します。
- 作成したノードの名前を
PressurePlateなど分かりやすい名前に変更します。 - そのノードを選択し、Inspector の Add Component → Physics 2D → BoxCollider2D(もしくは他の 2D Collider)を追加します。
- 追加した Collider2D の設定で:
- Enabled を ON にする。
- Is Trigger(または Sensor)にチェックを入れて、トリガーとして動作させます。
- 必要に応じて Tag を設定します(後でフィルタに使う場合)。
- 同じノードの Inspector で Add Component → Custom → PressurePlate を選択し、スクリプトをアタッチします。
3. 押す側(プレイヤーや箱)の準備
- Hierarchy で右クリック → Create → 2D Object → Sprite などで、プレイヤーや箱のノードを作成します。
- そのノードに RigidBody2D と BoxCollider2D(または他の 2D Collider)を追加します。
- プレイヤーを動かしたい場合は、RigidBody2D の Type を Dynamic にします。
- PressurePlate が 特定のオブジェクトだけに反応してほしい場合:
- プレイヤー側の
Collider2D.tagを例として 1 に設定します。 - PressurePlate 側の
useTagFilterを true にし、allowedTagを 1 に設定します。
- プレイヤー側の
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. ドア用ノードとイベント受信の簡易実装例
このコンポーネント自体は完全に独立ですが、「イベントをどう受け取るか」の一例として、最小限のドアスクリプトを紹介します(任意)。
- Assets パネルで右クリック → Create → TypeScript → ファイル名を
SimpleDoor.tsにします。 - 以下のような簡単なスクリプトを貼り付けます。
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);
}
}
このドアを使う場合の手順:
- Hierarchy で右クリック → Create → 2D Object → Sprite などでドア用ノードを作成し、名前を Door にします。
- Door ノードに SimpleDoor コンポーネントをアタッチします。
- PressurePlate の targetNode に、この Door ノードをドラッグ&ドロップします。
- PressurePlate の pressedEventName を
door-open、releasedEventName をdoor-closeに設定します。
これで、プレイヤーがプレートに乗るとドアが開き、離れると閉じる動作を確認できます。
6. シミュレーションで動作確認
- 2D シーンに Main Camera と Canvas があることを確認します(2D テンプレートなら最初からあります)。
- PressurePlate ノードとプレイヤー(または箱)ノードが視界内にあるように配置します。
- 上メニューから Project → Project Settings → Physics 2D を開き、2D 物理が有効になっていることを確認します。
- 再生ボタン(シミュレーション)を押し、プレイヤーや箱をプレートの上に落としたり、移動させたりしてみます。
- 期待される動作:
- プレイヤーがプレートの上に乗ると、プレートが少し沈み、色が変わる。
- Inspector の pressed プロパティが
false → trueに変化する。 - logDebug が ON なら、コンソールに
BEGIN_CONTACTと状態変化ログが出る。 - Door ノードを設定している場合、ドアが開閉する。
まとめ
このガイドでは、Cocos Creator 3.8.7 用に、「上に物が乗っている間だけ ON になる感圧スイッチ」を実現する汎用コンポーネント PressurePlate を実装しました。
主なポイント:
- 完全に独立したコンポーネントとして設計し、外部の GameManager やシングルトンに依存しない。
- 必要な設定はすべてインスペクタの
@propertyから行える:- イベント送信先の
targetNodeとイベント名。 - どのオブジェクトに反応するかを制御するタグフィルタ。
- 押されている間の見た目の変化(位置・スケール・色)。
- イベント送信先の
- 必須コンポーネント(
Collider2D)が無い場合は、エラーログで開発者に明示する防御的実装。 - イベント駆動で他のノードと連携できるため、ドアだけでなく、エレベーター・トラップ・ギミックの起動など、幅広い用途に再利用可能。
この PressurePlate をプロジェクトの「共通ギミック」としてライブラリ化しておけば、シーン内の任意の場所に配置して、Inspector で数値とターゲットを変えるだけで、さまざまなスイッチギミックを素早く構築できます。
必要に応じて、押されるまでの時間遅延や、一定時間だけ ON を維持するタイマー機能などを追加していくことで、さらに高度なギミックへ発展させることも容易です。




