【Cocos Creator 3.8】HiddenWall の実装:アタッチするだけで「触れたら透ける隠し通路」を実現する汎用スクリプト
このコンポーネントは、プレイヤーなどのオブジェクトが触れたタイミングで壁の透明度を下げ、奥が見えるようにする「隠し通路」演出を簡単に実装できるスクリプトです。
ノードにアタッチし、あとはインスペクタで透明度やフェード速度を調整するだけで使えるように設計します。
コンポーネントの設計方針
今回の HiddenWall コンポーネントの要件を整理します。
- 壁として表示されているノードにアタッチするだけで動作する。
- 「何か」が当たったら透明になり、「離れたら」元の不透明度に戻る。
- フェード(徐々に透明/不透明)を行い、カクッと切り替わらない。
- 外部の GameManager やプレイヤー管理スクリプトに依存しない。
- インスペクタから以下を調整可能にする:
- 最初の透明度(初期アルファ値)
- 触れたときの透明度(最低アルファ値)
- フェード速度
- トリガー対象のグループ名 or レイヤー(簡易フィルタ)
- 2D 物理コリジョンを使うか、単純な AABB 当たり判定を使うか
また、防御的な実装として:
- 必須コンポーネント:
SpriteまたはUIOpacityを持つことを推奨(どちらも無い場合はエラーログ)。- 物理判定を使う場合は
Collider2Dが必要(無い場合は警告ログ)。
- 利用側が最小限の設定ミスで済むよう、可能な限り自動取得・自動補完を行う。
インスペクタで設定可能なプロパティ
usePhysicsTrigger: boolean
– ツールチップ: 「2D 物理コリジョンのトリガーイベントを使用して判定するかどうか。」
– 説明: ON の場合はCollider2DのonTriggerEnter/onTriggerExitを使用。OFF の場合は、指定ノードとの距離による簡易判定を使う。targetNode: Node | null
– ツールチップ: 「物理を使わない場合に、接触判定の対象とするノード(例: プレイヤー)。」
– 説明:usePhysicsTrigger = falseのときだけ使用。ここで指定したノードが壁に近づいた/離れたタイミングで透過を切り替える。nonPhysicsTriggerDistance: number
– ツールチップ: 「物理を使わない場合に、targetNode との距離がこの値以下になったら接触とみなす。」
– 説明: 単純な距離ベース判定。2D ゲームであれば XZ か XY かはゲーム設計に依存しますが、ここでは 2D を想定して XY 平面で距離を計算する。initialAlpha: number
– ツールチップ: 「開始時の透明度(0〜255)。255 で完全不透明。」
– 説明: 壁の通常状態のアルファ値。実行開始時に強制的にこの値に設定される。hitAlpha: number
– ツールチップ: 「プレイヤーが触れている間の透明度(0〜255)。0 に近いほど透ける。」
– 説明: 触れたときに目指す透明度。0〜255 の範囲で指定。fadeSpeed: number
– ツールチップ: 「1秒あたりに変化する透明度の量。大きいほど速くフェードする。」
– 説明:deltaTime * fadeSpeedだけアルファ値を変化させる。filterGroupName: string
– ツールチップ: 「物理トリガーで有効。指定したグループ名のオブジェクトと接触したときのみ反応する(空文字なら全てに反応)。」
– 説明: 物理トリガー時に、otherCollider.node.layerではなく、otherCollider.node.group(Cocos 3.x ではタグ付け用途に自前プロパティを使うケースも多いですが、ここでは単純な groupName を想定)をチェックする簡易フィルタ。debugLog: boolean
– ツールチップ: 「true にすると、トリガーの開始/終了などのログをコンソールに出力します。」
– 説明: 動作確認やデバッグ時に有用。
TypeScriptコードの実装
import { _decorator, Component, Node, Sprite, UIOpacity, Collider2D, ITriggerEvent, Vec3, math, warn, error } from 'cc';
const { ccclass, property } = _decorator;
/**
* HiddenWall
* - プレイヤーなどが触れている間だけ透明度を下げる「隠し通路」用コンポーネント。
* - 他のカスタムスクリプトに依存しない独立コンポーネント。
*/
@ccclass('HiddenWall')
export class HiddenWall extends Component {
@property({
tooltip: '2D 物理コリジョンのトリガーイベントを使用して判定するかどうか。\nON: Collider2D の onTriggerEnter/Exit を使用\nOFF: targetNode との距離で簡易判定',
})
public usePhysicsTrigger: boolean = true;
@property({
tooltip: 'usePhysicsTrigger が false の場合に使用。\nこのノードとの距離が nonPhysicsTriggerDistance 以下になったら「接触」とみなすターゲット(例: プレイヤー)。',
})
public targetNode: Node | null = null;
@property({
tooltip: 'usePhysicsTrigger が false の場合に使用。\ntargetNode との距離がこの値以下になったら接触とみなす(単位: ワールド座標の長さ)。',
min: 0,
})
public nonPhysicsTriggerDistance: number = 50;
@property({
tooltip: '開始時の透明度(0〜255)。255 で完全不透明。\n実行開始時に壁の透明度をこの値にリセットします。',
min: 0,
max: 255,
})
public initialAlpha: number = 255;
@property({
tooltip: 'プレイヤーが触れている間の透明度(0〜255)。\n0 に近いほど透けて見えるようになります。',
min: 0,
max: 255,
})
public hitAlpha: number = 80;
@property({
tooltip: '1秒あたりに変化する透明度の量。\n大きいほど速くフェードします。',
min: 1,
})
public fadeSpeed: number = 300;
@property({
tooltip: '物理トリガー使用時のみ有効。\n指定したグループ名のオブジェクトと接触したときだけ反応します。\n空文字のままなら全てのオブジェクトに反応します。',
})
public filterGroupName: string = '';
@property({
tooltip: 'true にすると、トリガー開始/終了やエラーなどのログをコンソールに出力します。',
})
public debugLog: boolean = false;
// 内部状態
private _sprite: Sprite | null = null;
private _uiOpacity: UIOpacity | null = null;
private _collider: Collider2D | null = null;
private _isTriggered: boolean = false; // 現在プレイヤー等が触れているか
private _currentAlpha: number = 255; // 実際に適用しているアルファ値
private _targetAlpha: number = 255; // この値に向かってフェードする
onLoad() {
// Sprite / UIOpacity の取得
this._sprite = this.getComponent(Sprite);
this._uiOpacity = this.getComponent(UIOpacity);
if (!this._sprite && !this._uiOpacity) {
error('[HiddenWall] Sprite か UIOpacity コンポーネントが見つかりません。このノードにどちらかを追加してください。');
}
// Collider2D の取得(物理トリガー使用時のみ)
if (this.usePhysicsTrigger) {
this._collider = this.getComponent(Collider2D);
if (!this._collider) {
warn('[HiddenWall] usePhysicsTrigger が true ですが、Collider2D が見つかりません。トリガー判定が行えません。');
}
}
// アルファ値の初期化
this._currentAlpha = math.clamp(this.initialAlpha, 0, 255);
this._targetAlpha = this._currentAlpha;
this._applyAlpha(this._currentAlpha);
if (this.debugLog) {
console.log('[HiddenWall] onLoad - initialAlpha:', this._currentAlpha, 'usePhysicsTrigger:', this.usePhysicsTrigger);
}
}
start() {
// 物理トリガーイベントの登録
if (this.usePhysicsTrigger && this._collider) {
this._collider.on('onTriggerEnter', this._onTriggerEnter, this);
this._collider.on('onTriggerExit', this._onTriggerExit, this);
}
}
onDestroy() {
// イベントの解除(メモリリーク防止)
if (this.usePhysicsTrigger && this._collider) {
this._collider.off('onTriggerEnter', this._onTriggerEnter, this);
this._collider.off('onTriggerExit', this._onTriggerExit, this);
}
}
update(dt: number) {
// 物理を使わない簡易判定モード
if (!this.usePhysicsTrigger && this.targetNode) {
this._updateNonPhysicsTrigger();
}
// 現在のトリガー状態に応じて、目標アルファ値を設定
const desiredAlpha = this._isTriggered
? math.clamp(this.hitAlpha, 0, 255)
: math.clamp(this.initialAlpha, 0, 255);
if (this._targetAlpha !== desiredAlpha) {
this._targetAlpha = desiredAlpha;
}
// フェード処理
if (this._currentAlpha !== this._targetAlpha) {
const direction = this._targetAlpha > this._currentAlpha ? 1 : -1;
const deltaAlpha = this.fadeSpeed * dt * direction;
// 目標を超えないように clamp
if ((direction > 0 && this._currentAlpha + deltaAlpha > this._targetAlpha) ||
(direction ', ok);
}
return ok;
}
/**
* 実際に Sprite / UIOpacity にアルファ値を適用する。
*/
private _applyAlpha(alpha: number) {
const a = math.clamp(Math.round(alpha), 0, 255);
if (this._uiOpacity) {
this._uiOpacity.opacity = a;
}
if (this._sprite) {
// Sprite の color を通じてアルファを変更
const color = this._sprite.color;
color.a = a;
this._sprite.color = color;
}
}
}
コードの主要ポイント解説
- onLoad
SpriteとUIOpacityを取得し、どちらも無い場合はerrorを出して開発者に追加を促します。usePhysicsTriggerが true の場合はCollider2Dを取得し、見つからなければwarnを出力します。initialAlphaを現在値と目標値に設定し、即座に反映します。
- start
- 物理トリガーモードのときのみ、
Collider2DにonTriggerEnter/onTriggerExitを登録します。
- 物理トリガーモードのときのみ、
- update
usePhysicsTrigger = falseの場合、_updateNonPhysicsTriggerでtargetNodeとの距離を計算し、接触状態を更新します。- 現在のトリガー状態に応じて目標アルファ値(
initialAlphaorhitAlpha)を決定します。 fadeSpeedとdtを使って_currentAlphaを_targetAlphaに向かって徐々に変化させ、_applyAlphaで実際の見た目に反映します。
- _updateNonPhysicsTrigger
- ワールド座標で自分と
targetNodeの距離を計算し、nonPhysicsTriggerDistance以下なら接触中とみなします。 - 接触状態が変わったときだけログを出力(
debugLogが true の場合)。
- ワールド座標で自分と
- _onTriggerEnter / _onTriggerExit
- 物理トリガーモードで呼ばれるイベントハンドラ。
_shouldReactToColliderでフィルタした上で、_isTriggeredを true/false に切り替えます。
- 物理トリガーモードで呼ばれるイベントハンドラ。
- _applyAlpha
UIOpacityがあればopacityに直接アルファ値を設定。Spriteがあればcolor.aにアルファ値を設定。- どちらにも対応することで、UI レイヤーでも通常の 2D スプライトでも使えるようにしています。
使用手順と動作確認
1. スクリプトファイルの作成
- エディタ左下の Assets パネルで、スクリプトを置きたいフォルダ(例:
assets/scripts)を選択します。 - そのフォルダ上で右クリック → Create → TypeScript を選択します。
- ファイル名を
HiddenWall.tsに変更します。 - 作成された
HiddenWall.tsをダブルクリックして開き、既存のテンプレートコードをすべて削除し、前述の TypeScript コードを丸ごと貼り付けて保存します。
2. テスト用の壁ノードを作成
- Hierarchy パネルで右クリック → Create → 2D オブジェクト(例: Sprite)を選択します。
- 作成されたノードの名前を分かりやすく
HiddenWallTestなどに変更します。 - 選択したノードの Inspector を確認し、Sprite コンポーネントが付いていることを確認します。
- もし付いていなければ、Add Component → UI → Sprite で追加してください。
- UI レイヤーで使う場合は、代わりに UIOpacity を使用しても構いません(Add Component → UI → UIOpacity)。
3. Collider2D の設定(物理トリガーモードを使う場合)
物理コリジョンを使って「プレイヤーがぶつかったら透ける」動作をさせたい場合:
- HiddenWallTest ノードを選択します。
- Add Component → Physics 2D → 適切なコライダー(例: BoxCollider2D)を追加します。
- 追加した
BoxCollider2Dの Is Trigger(トリガー)にチェックを入れて、物理的な衝突ではなくトリガー判定のみ行うようにします。
プレイヤー側にも Collider2D と RigidBody2D を設定しておくと、2D 物理シミュレーションと連動した自然な動きになります(ただし本コンポーネント自体はプレイヤー側の実装には依存しません)。
4. HiddenWall コンポーネントをアタッチ
- HiddenWallTest ノードを選択した状態で、Inspector の下部にある Add Component ボタンをクリックします。
- Custom → HiddenWall を選択して追加します。
5. プロパティの設定例(物理トリガーモード)
物理トリガーを使って「プレイヤーがぶつかったら透ける」ケースの設定例です。
- usePhysicsTrigger:
true(デフォルトのまま) - targetNode:未設定のままで OK(物理モードでは使用しません)
- nonPhysicsTriggerDistance:デフォルトのままで OK(物理モードでは使用しません)
- initialAlpha:
255(完全不透明な壁) - hitAlpha:
60〜100くらい(好みに応じて調整) - fadeSpeed:
300〜600くらい(数値が大きいほど素早くフェード) - filterGroupName:
- 特定のノード名にだけ反応させたい場合:例としてプレイヤーノード名が
PlayerならPlayerと入力。 - どのオブジェクトと当たっても透けてほしい場合:空文字のままにしておく。
- 特定のノード名にだけ反応させたい場合:例としてプレイヤーノード名が
- debugLog:動作確認中だけ
trueにして、ログを見ながら調整するのが便利です。
その後、シーンを再生し、プレイヤー(またはコライダーを持ったオブジェクト)を HiddenWallTest にぶつけてみてください。
接触中は壁が指定した hitAlpha までフェードアウトし、離れると initialAlpha までフェードインして戻るはずです。
6. プロパティの設定例(非物理・距離ベース判定モード)
物理を使わず、「プレイヤーが近づいたら透ける」ようにしたい場合:
- HiddenWallTest ノードの HiddenWall コンポーネントで、次のように設定します。
- usePhysicsTrigger:
false - targetNode:プレイヤーノードを Hierarchy からドラッグ&ドロップして割り当て。
- nonPhysicsTriggerDistance:
50〜150くらい(ゲームスケールに応じて調整)。 - initialAlpha:
255 - hitAlpha:
60 - fadeSpeed:
300 - filterGroupName:未使用なので空のままで OK。
- debugLog:
trueにすると、距離とトリガー状態がログに出るので調整しやすくなります。
このモードでは、プレイヤーにコライダーや物理ボディが無くても「近づいたら透ける隠し通路」を実現できます。
まとめ
HiddenWall コンポーネントは:
- 壁ノードにアタッチするだけで、「触れたら透ける隠し通路」演出を簡単に追加できる。
- 物理トリガー/距離ベース判定の両方に対応しており、ゲームの設計に合わせて選択可能。
initialAlpha,hitAlpha,fadeSpeedを変えるだけで、さまざまな雰囲気の壁(うっすら透ける、急に消える、ゆっくりフェードするなど)を表現できる。- 外部の GameManager やプレイヤー管理スクリプトに依存せず、完全に独立して動作するため、どのプロジェクトにも簡単に移植可能。
例えば:
- ダンジョンの隠し通路やシークレットルームの入口。
- プレイヤーが近づくと姿を現す「幻の壁」。
- 敵が近づくと透明になり、プレイヤーだけが通れる安全地帯。
といったギミックを、シーン上に壁ノードを配置して HiddenWall をアタッチするだけで量産できます。
一度このコンポーネントをプロジェクトの共通ライブラリとして整備しておけば、以後のゲームでも再利用でき、レベルデザインのスピードアップに大きく貢献します。




