【Cocos Creator 3.8】FallingPlatform の実装:アタッチするだけで「乗ると揺れて落ちる床」を実現する汎用スクリプト
このガイドでは、任意の床(プラットフォーム)ノードにアタッチするだけで、プレイヤーなどのオブジェクトが乗ってから一定時間後に「揺れ始め → 落下(物理挙動)」する FallingPlatform コンポーネントを実装します。
プレイヤー側のスクリプトや専用 GameManager に依存せず、床ノード単体に完結したコンポーネントとして設計するため、どのプロジェクトにも簡単に持ち込んで再利用できます。
コンポーネントの設計方針
機能要件の整理
- 床ノードに
FallingPlatformをアタッチするだけで動作する。 - 「誰か(RigidBody2D を持つ物体)」が床の上に乗ったときにカウント開始。
- 乗ってから 0.5 秒後に床が小刻みに揺れ始める。
- さらに 1 秒後(乗ってから合計 1.5 秒後)に床が落下を開始する。
- 落下は 物理挙動(RigidBody2D の Dynamic 化)で行う。
- 床に乗っているオブジェクトが離れた場合でも、一度カウントが始まったら中断せずに最後まで進む。
- 外部スクリプトやシングルトンには一切依存しない。
必要な標準コンポーネント
このコンポーネントは以下の 2D 物理コンポーネントを利用します。
- RigidBody2D:床の物理挙動(落下)を制御する。
- Collider2D(BoxCollider2D 推奨):他オブジェクトが「乗った」ことを検知する。
防御的実装として、これらがアタッチされていない場合は onLoad で getComponent により取得を試み、見つからなければ error ログを出して動作を停止します。
インスペクタで設定可能なプロパティ設計
コンポーネントを汎用的にするため、以下のようなパラメータを @property で公開します。
- triggerTag (string)
- 床に乗ったと判定するオブジェクトの「タグ」。
- 空文字の場合は「タグを問わず、どのオブジェクトが乗っても反応」させる。
- プレイヤーの Collider2D に設定した
tagと一致させることで、特定のオブジェクトのみに反応させられる。
- delayBeforeShake (number)
- 乗ってから揺れ始めるまでの時間(秒)。
- デフォルト:
0.5秒。
- delayBeforeFall (number)
- 揺れ始めてから落下開始までの時間(秒)。
- デフォルト:
1.0秒。
- shakeAngle (number)
- 揺れの最大角度(度)。
- この角度を中心に左右に振動する。
- デフォルト:
5度。
- shakeFrequency (number)
- 揺れの周波数(1 秒間に何回揺れるか)。
- デフォルト:
20(かなり細かく震える感じ)。
- autoDisableColliderOnFall (boolean)
- 落下開始時に床の Collider2D を無効化するかどうか。
- 有効にすると、床が落ちた後にプレイヤーが引っかからなくなる。
- デフォルト:
true。
- destroyAfterFall (boolean)
- 落下開始後、一定時間経過したら床ノードを自動で破棄するかどうか。
- ステージから消えてほしい場合に有効。
- デフォルト:
false。
- destroyDelay (number)
- 落下開始からノード破棄までの時間(秒)。
destroyAfterFallがtrueのときのみ有効。- デフォルト:
3.0秒。
また、物理挙動制御のために以下の内部状態を持ちます(インスペクタには出さない)。
- 初期の
RigidBody2D.type(Static など) - 初期の回転(
Node.eulerAngles) - 現在の状態(待機中 / カウント中 / 揺れ中 / 落下済み)
- 経過時間カウンタ
TypeScriptコードの実装
import { _decorator, Component, Node, RigidBody2D, ERigidBody2DType, Collider2D, IPhysics2DContact, Contact2DType, Vec3, math, warn, error, tween } from 'cc';
const { ccclass, property } = _decorator;
/**
* FallingPlatform
* 乗ってから一定時間後に揺れ始め、さらに一定時間後に落下する床コンポーネント
*/
@ccclass('FallingPlatform')
export class FallingPlatform extends Component {
@property({
tooltip: 'このタグを持つ Collider2D が乗ったときに作動します。\n空文字の場合はタグを問わず反応します。'
})
public triggerTag: string = '';
@property({
tooltip: '乗ってから揺れ始めるまでの時間(秒)。'
})
public delayBeforeShake: number = 0.5;
@property({
tooltip: '揺れ始めてから落下開始までの時間(秒)。'
})
public delayBeforeFall: number = 1.0;
@property({
tooltip: '揺れの最大角度(度)。この角度を中心に左右に振動します。'
})
public shakeAngle: number = 5;
@property({
tooltip: '揺れの周波数(1秒あたりの振動回数)。'
})
public shakeFrequency: number = 20;
@property({
tooltip: '落下開始時にこのノードの Collider2D を無効化するかどうか。'
})
public autoDisableColliderOnFall: boolean = true;
@property({
tooltip: '落下開始後、一定時間でノードを自動破棄するかどうか。'
})
public destroyAfterFall: boolean = false;
@property({
tooltip: '落下開始からノードを破棄するまでの時間(秒)。\n「自動破棄」が有効な場合のみ使用されます。'
})
public destroyDelay: number = 3.0;
// 内部用プロパティ
private _rigidBody: RigidBody2D | null = null;
private _collider: Collider2D | null = null;
private _originalBodyType: ERigidBody2DType | null = null;
private _originalRotation: Vec3 = new Vec3();
private _isTriggered: boolean = false;
private _isShaking: boolean = false;
private _hasFallen: boolean = false;
private _elapsedSinceTrigger: number = 0;
onLoad() {
// 必要なコンポーネントを取得
this._rigidBody = this.getComponent(RigidBody2D);
if (!this._rigidBody) {
error('[FallingPlatform] このノードに RigidBody2D がアタッチされていません。動作しません。');
return;
}
this._collider = this.getComponent(Collider2D);
if (!this._collider) {
error('[FallingPlatform] このノードに Collider2D (例: BoxCollider2D) がアタッチされていません。動作しません。');
return;
}
// 初期設定を記録
this._originalBodyType = this._rigidBody.type;
this._originalRotation.set(this.node.eulerAngles);
// 初期状態では落下しないように Static か Kinematic を推奨
// ここでは、ユーザー設定を尊重し、type は変更しない。
// ただし Dynamic の場合、落ちないようにすることはできないので警告を出す。
if (this._rigidBody.type === ERigidBody2DType.Dynamic) {
warn('[FallingPlatform] RigidBody2D のタイプが Dynamic に設定されています。開始時から落下する可能性があります。通常は Static か Kinematic を推奨します。');
}
// 衝突コールバック登録
this._collider.on(Contact2DType.BEGIN_CONTACT, this._onBeginContact, this);
}
start() {
// パラメータの防御的チェック
if (this.delayBeforeShake < 0) {
warn('[FallingPlatform] delayBeforeShake が負の値です。0 に補正します。');
this.delayBeforeShake = 0;
}
if (this.delayBeforeFall < 0) {
warn('[FallingPlatform] delayBeforeFall が負の値です。0 に補正します。');
this.delayBeforeFall = 0;
}
if (this.destroyDelay < 0) {
warn('[FallingPlatform] destroyDelay が負の値です。0 に補正します。');
this.destroyDelay = 0;
}
}
update(deltaTime: number) {
if (!this._rigidBody || !this._collider) {
return;
}
// 既に落下済みなら何もしない
if (this._hasFallen) {
return;
}
if (this._isTriggered) {
this._elapsedSinceTrigger += deltaTime;
// 揺れ開始判定
if (!this._isShaking && this._elapsedSinceTrigger >= this.delayBeforeShake) {
this._startShaking();
}
// 落下開始判定
const totalDelayBeforeFall = this.delayBeforeShake + this.delayBeforeFall;
if (this._elapsedSinceTrigger >= totalDelayBeforeFall) {
this._startFalling();
}
}
// 揺れ中は角度を更新
if (this._isShaking && !this._hasFallen) {
this._updateShake();
}
}
private _onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: IPhysics2DContact | null) {
if (this._isTriggered) {
// 既にトリガー済みなら無視
return;
}
// タグによるフィルタリング
if (this.triggerTag !== '') {
if (otherCollider.tag.toString() !== this.triggerTag) {
// タグが一致しない場合は無視
return;
}
}
// ここでは「上から乗ったかどうか」の厳密な判定は行わず、
// 指定タグ(または任意オブジェクト)との接触でトリガーとする。
this._isTriggered = true;
this._elapsedSinceTrigger = 0;
// 念のため、初期回転を保持
this._originalRotation.set(this.node.eulerAngles);
}
private _startShaking() {
if (this._isShaking) {
return;
}
this._isShaking = true;
}
private _startFalling() {
if (this._hasFallen) {
return;
}
this._hasFallen = true;
this._isShaking = false;
// 回転を初期値に戻す(揺れをリセット)
this.node.eulerAngles = this._originalRotation.clone();
// Rigidbody を Dynamic にして物理落下させる
if (this._rigidBody) {
this._rigidBody.type = ERigidBody2DType.Dynamic;
}
// コライダを無効化して、落下中にプレイヤーが引っかからないようにする(オプション)
if (this.autoDisableColliderOnFall && this._collider) {
this._collider.enabled = false;
}
// 自動破棄(オプション)
if (this.destroyAfterFall) {
tween(this.node)
.delay(this.destroyDelay)
.call(() => {
if (this.node && this.node.isValid) {
this.node.destroy();
}
})
.start();
}
}
private _updateShake() {
// 経過時間から揺れ角度を算出する
// 角速度 = 2π * 周波数
const t = this._elapsedSinceTrigger - this.delayBeforeShake;
const omega = 2 * Math.PI * this.shakeFrequency;
const angleOffset = Math.sin(omega * t) * this.shakeAngle;
const newEuler = this._originalRotation.clone();
// Z軸回転のみを揺らす(2Dなので)
newEuler.z += angleOffset;
this.node.eulerAngles = newEuler;
}
onDestroy() {
// イベント登録解除
if (this._collider) {
this._collider.off(Contact2DType.BEGIN_CONTACT, this._onBeginContact, this);
}
}
}
主要な処理の解説
onLoadRigidBody2DとCollider2Dを取得し、存在しなければerrorログを出して以降の処理を事実上無効にします。- 初期の
RigidBody2D.typeとnode.eulerAnglesを保存します。 Collider2DのBEGIN_CONTACTイベントに_onBeginContactを登録します。
start- タイミング系プロパティが負の値になっていた場合、0 に補正して警告を出します。
_onBeginContact- 誰かが床に接触したときに呼ばれます。
triggerTagが設定されている場合、そのタグと一致するotherCollider.tagのときだけトリガーします。- トリガー済みでなければ
_isTriggered = trueとし、経過時間カウンタをリセットします。
update- トリガー後の経過時間を積算し、
delayBeforeShakeを過ぎたら_startShaking()を呼びます。 delayBeforeShake + delayBeforeFallを過ぎたら_startFalling()を呼びます。- 揺れ中であれば
_updateShake()でフレームごとに角度を更新します。
- トリガー後の経過時間を積算し、
_updateShake- シンプルなサイン波(
sin)を使って Z 軸回転を揺らしています。 shakeFrequencyにより 1 秒あたりの揺れ回数、shakeAngleにより最大振れ角を制御します。
- シンプルなサイン波(
_startFalling- 揺れを停止し、回転を初期角度に戻します。
RigidBody2D.typeをDynamicに変えて物理落下を開始します。autoDisableColliderOnFallが有効ならCollider2D.enabled = falseにして、落下中にプレイヤーが引っかからないようにします。destroyAfterFallが有効なら、tweenを使ってdestroyDelay秒後にnode.destroy()します。
使用手順と動作確認
1. スクリプトファイルの作成
- エディタの Assets パネルで任意のフォルダ(例:
assets/scripts)を右クリックします。 - Create > TypeScript を選択し、ファイル名を
FallingPlatform.tsにします。 - 作成された
FallingPlatform.tsをダブルクリックしてエディタで開き、先ほどのコード全文を貼り付けて保存します。
2. テスト用の床ノードを作成
- Hierarchy パネルで右クリックし、Create > 2D Object > Sprite を選択します。
- 名前を
FallingPlatformTestなどに変更しておきます。
- 名前を
- 作成したノードを選択し、Inspector で以下を設定します。
- Sprite の
SpriteFrameに適当な床画像を設定するか、単色でも構いません。 - サイズ(
ContentSize)をお好みの床サイズに調整します。
- Sprite の
3. 物理コンポーネントの追加
FallingPlatform が正しく動作するために、床ノードには RigidBody2D と Collider2D が必要です。
- 床ノード(
FallingPlatformTest)を選択した状態で、Inspector > Add Component をクリックします。 - Physics 2D > RigidBody2D を追加します。
Typeは Static または Kinematic を推奨します(初期状態で落ちないようにするため)。
- 再度 Add Component をクリックし、Physics 2D > BoxCollider2D を追加します。
Sizeを床スプライトの大きさに合わせて調整してください。- プレイヤーのタグで反応させたい場合は
Tagに任意の数値(例:1)を設定し、後でプレイヤー側の Collider2D にも同じタグを設定します。
4. FallingPlatform コンポーネントのアタッチ
- 床ノードを選択したまま、Inspector > Add Component > Custom > FallingPlatform を選択して追加します。
- Inspector で
FallingPlatformの各プロパティを設定します。- Trigger Tag:
- プレイヤーの
Collider2D.tagを1にしている場合、ここも1と入力します(文字列ですが数値をそのまま書いて構いません)。 - 「誰が乗っても落ちてよい」場合は空のままにしておきます。
- プレイヤーの
- Delay Before Shake:
0.5(仕様通り 0.5 秒後に揺れ開始)。 - Delay Before Fall:
1.0(揺れ始めてから 1 秒後に落下)。 - Shake Angle:
5(揺れが小さすぎる場合は 10〜15 に上げてみてください)。 - Shake Frequency:
20(細かく震える感じ。ゆっくり揺らしたいなら 5〜10 に)。 - Auto Disable Collider On Fall:
true(落下後に床に引っかからないようにする)。 - Destroy After Fall:テスト時は
falseのままで構いません。 - Destroy Delay:
3.0(Destroy After Fallを有効にしたときに使用されます)。
- Trigger Tag:
5. プレイヤー(またはテスト用オブジェクト)の準備
すでにプレイヤーキャラクターがいる場合は、そのノードに RigidBody2D と Collider2D が付いていることを確認し、必要なら tag を設定してください。
簡易テストだけしたい場合は、次のようにします。
- Hierarchy で右クリック → Create > 2D Object > Sprite を選択し、
TestPlayerなどの名前を付けます。 - Inspector で Add Component > Physics 2D > RigidBody2D を追加し、
Typeを Dynamic に設定します。 - 同じく Add Component > Physics 2D > CircleCollider2D などを追加し、サイズを調整します。
- 床の上に乗るように、
TestPlayerの位置を少し上(Y 座標を高く)に配置します。 - 床の
FallingPlatform.triggerTagに何も入れていない場合はそのままで OK です。- タグで制限したい場合は、プレイヤー側の Collider2D の
Tagを1にし、床側のTrigger Tagにも1を入力します。
- タグで制限したい場合は、プレイヤー側の Collider2D の
6. 再生して動作確認
- エディタ右上の Play ボタンを押してシミュレーションを開始します。
- プレイヤー(またはテストオブジェクト)が床に落ちて接触すると、内部的にカウントが始まります。
- 約 0.5 秒後:床が小刻みに揺れ始めるのを確認します。
- さらに 1 秒後(合計 1.5 秒後):床の
RigidBody2D.typeがDynamicになり、重力で落下し始めます。 Auto Disable Collider On Fallがtrueの場合、落下後は床に乗れなくなります。Destroy After Fallをtrueにしている場合、Destroy Delay秒後に床ノードが自動で削除されます。
揺れが目立たない場合は Shake Angle を 10〜15 程度に上げてみてください。揺れが速すぎる / 遅すぎる場合は Shake Frequency を調整します。
まとめ
この FallingPlatform コンポーネントは、
- 床ノードにアタッチ
- RigidBody2D と Collider2D を付ける
- 必要なら
triggerTagを合わせる
という最小限の手順だけで、「乗ってしばらくすると揺れて落ちる床」を実現できます。外部の GameManager やプレイヤースクリプトに依存しないため、
- 別プロジェクトへのコピペ導入
- ステージエディタ的なシーン上での量産
- プロトタイプ制作時の素早いギミック追加
といった場面で非常に扱いやすくなっています。
応用としては、
- タグを「敵専用」にして、「敵が乗ったときだけ崩れる床」にする。
destroyAfterFallを有効にして、落ちた床が一定時間後に消える「一度きりの足場」を作る。- 揺れの角度や周波数を変えたプリセットを複数用意し、ステージごとにバリエーションを持たせる。
といった拡張も簡単です。プロパティだけで挙動を調整できる「独立コンポーネント」として設計しておくことで、ゲーム全体の設計に縛られず、必要な場所に必要なギミックを素早く追加できるようになります。




