【Cocos Creator 3.8】VisibilityToggle の実装:アタッチするだけで特定キーでメニューの表示/非表示を切り替える汎用スクリプト
ゲーム中に「Esc キーでメニューを開閉」「Tab でステータスウィンドウを表示/非表示」といった処理はよく登場します。本記事では、任意のキー入力で自分自身(=アタッチしたノード)の表示・非表示をトグルする汎用コンポーネント VisibilityToggle を実装します。
このコンポーネントは、対象ノードにアタッチするだけで動き、他のスクリプトやシングルトンに一切依存しません。インスペクタから「どのキーで切り替えるか」「初期状態を表示にするか」「トグル時に音を鳴らすか」などを柔軟に設定できます。
コンポーネントの設計方針
機能要件の整理
- 特定のキーを押したときに、アタッチされたノードの active を true/false で切り替える。
- 初期状態を「表示」「非表示」どちらにも設定できる。
- キー入力の監視は
systemEvent / inputを用いて 自前で完結 させる。 - 他のカスタムスクリプトには一切依存しない。
- 必要に応じて、トグル時に簡単なフィードバック(SE 再生)を付けられるようにするが、AudioSource が無い場合はエラーログを出して処理をスキップする防御的実装にする。
インスペクタで設定可能なプロパティ設計
VisibilityToggle コンポーネントに用意する @property は以下の通りです。
- toggleKey (KeyCode)
- 説明: 表示/非表示を切り替えるトリガーとなるキー。
- 型:
KeyCode(Cocos のキーコード列挙体)。 - 例:
KeyCode.ESCAPE,KeyCode.TAB,KeyCode.SPACEなど。
- initialVisible (boolean)
- 説明: ゲーム開始時にこのノードを表示状態にするかどうか。
- true:
this.node.active = true - false:
this.node.active = false
- listenWhenInactive (boolean)
- 説明: ノードが非表示(
active = false)の間もキー入力を受け付けるかどうか。 -
true:
- ノードが非アクティブでもグローバル入力を監視し続ける。
- 「Esc でメニューを開く」など、非表示から表示させたい UI に便利。
-
false:
- ノードが非アクティブになった時点で入力リスナーを解除する。
- 「表示中のときだけ閉じる操作を受け付けたい」用途に向く。
- 説明: ノードが非表示(
- useSound (boolean)
- 説明: トグル時に AudioSource を利用して SE を鳴らすかどうか。
- true: AudioSource を取得し、成功すれば
play()を実行。 - false: 音は鳴らさない。
- logDebug (boolean)
- 説明: キー入力検知や表示状態の変化を
console.logに出力するかどうか。 - デバッグ用。最終ビルド時は OFF にしてもよい。
- 説明: キー入力検知や表示状態の変化を
AudioSource は標準コンポーネントですが、必須ではないオプション扱いにします。useSound = true なのに AudioSource がない場合は、エラーログを出してその旨を知らせる防御的設計にします。
TypeScriptコードの実装
以下が完成版の VisibilityToggle.ts です。
import { _decorator, Component, Node, input, Input, EventKeyboard, KeyCode, AudioSource, log, warn, error } from 'cc';
const { ccclass, property } = _decorator;
/**
* VisibilityToggle
* 任意のキー入力で、このノードの active をトグルする汎用コンポーネント。
* 他スクリプトに依存せず、このスクリプト単体で完結します。
*/
@ccclass('VisibilityToggle')
export class VisibilityToggle extends Component {
@property({
type: KeyCode,
tooltip: '表示/非表示を切り替えるトリガーキー。\n例: ESCAPE, TAB, SPACE など。'
})
public toggleKey: KeyCode = KeyCode.ESCAPE;
@property({
tooltip: 'ゲーム開始時にこのノードを表示状態にするかどうか。\ntrue: 表示 / false: 非表示'
})
public initialVisible: boolean = false;
@property({
tooltip: 'ノードが非表示中もキー入力を受け付けるか。\ntrue: 非表示でもグローバルにキーを監視\nfalse: 非表示になった時点で入力リスナーを解除'
})
public listenWhenInactive: boolean = true;
@property({
tooltip: 'トグル時に AudioSource で効果音を再生するかどうか。\nAudioSource が見つからない場合は警告ログを出します。'
})
public useSound: boolean = false;
@property({
tooltip: 'デバッグ用ログをコンソールに出力するかどうか。'
})
public logDebug: boolean = false;
private _audioSource: AudioSource | null = null;
private _isListening: boolean = false;
onLoad() {
// 初期表示状態を設定
this.node.active = this.initialVisible;
// AudioSource を取得 (任意)
if (this.useSound) {
this._audioSource = this.getComponent(AudioSource);
if (!this._audioSource) {
warn('[VisibilityToggle] useSound が true ですが、AudioSource コンポーネントが見つかりません。効果音は再生されません。');
}
}
// 入力リスナーを登録
this._registerInput();
if (this.logDebug) {
log(`[VisibilityToggle] onLoad: initialVisible=${this.initialVisible}, listenWhenInactive=${this.listenWhenInactive}, toggleKey=${KeyCode[this.toggleKey]}`);
}
}
onEnable() {
// ノードが有効化されたときに、必要ならリスナーを再登録
if (!this._isListening && (this.listenWhenInactive || this.node.active)) {
this._registerInput();
}
}
onDisable() {
// listenWhenInactive が false の場合、非表示になったタイミングでリスナーを解除
if (!this.listenWhenInactive) {
this._unregisterInput();
}
}
onDestroy() {
// ノード破棄時には必ずリスナーを解除
this._unregisterInput();
}
/**
* キーボード入力リスナー登録
*/
private _registerInput() {
if (this._isListening) {
return;
}
input.on(Input.EventType.KEY_DOWN, this._onKeyDown, this);
this._isListening = true;
if (this.logDebug) {
log('[VisibilityToggle] キー入力リスナー登録');
}
}
/**
* キーボード入力リスナー解除
*/
private _unregisterInput() {
if (!this._isListening) {
return;
}
input.off(Input.EventType.KEY_DOWN, this._onKeyDown, this);
this._isListening = false;
if (this.logDebug) {
log('[VisibilityToggle] キー入力リスナー解除');
}
}
/**
* キー押下時のコールバック
*/
private _onKeyDown(event: EventKeyboard) {
if (event.keyCode !== this.toggleKey) {
return;
}
if (this.logDebug) {
log(`[VisibilityToggle] トグルキー押下: ${KeyCode[this.toggleKey]}`);
}
// 表示状態をトグル
const newActive = !this.node.active;
this.node.active = newActive;
if (this.logDebug) {
log(`[VisibilityToggle] node.active => ${newActive}`);
}
// 必要ならサウンド再生
if (this.useSound) {
this._playToggleSound();
}
// listenWhenInactive が false の場合、
// 非表示になったら入力リスナーを解除し、表示になったら再登録する。
if (!this.listenWhenInactive) {
if (newActive) {
this._registerInput();
} else {
this._unregisterInput();
}
}
}
/**
* トグル時に効果音を再生(任意)
*/
private _playToggleSound() {
if (!this._audioSource) {
if (this.useSound) {
// ここに来るのは AudioSource が見つからなかったケース
error('[VisibilityToggle] useSound は true ですが、AudioSource が取得できませんでした。ノードに AudioSource を追加してください。');
}
return;
}
// 再生中なら一度停止してから再生し直す
if (this._audioSource.playing) {
this._audioSource.stop();
}
this._audioSource.play();
if (this.logDebug) {
log('[VisibilityToggle] トグルサウンド再生');
}
}
}
コードのポイント解説
- onLoad
this.node.active = this.initialVisible;で初期表示状態を決定。useSoundが true のときだけAudioSourceをgetComponentで取得。見つからない場合はwarnを出力。_registerInput()でグローバルなキーボード入力を監視開始。
- onEnable / onDisable
listenWhenInactiveの設定に応じて、ノードの有効/無効化に合わせて入力リスナーを再登録・解除。- 「非表示中も Esc で開けるメニュー」のようなケースでは
listenWhenInactive = trueにする。
- _onKeyDown
- 押されたキーが
toggleKeyと一致したときだけ処理を行う。 this.node.activeを反転させて表示状態をトグル。- 必要なら
_playToggleSound()で SE を再生。 listenWhenInactive = falseの場合、非表示になったらリスナー解除、再度表示されたらリスナー再登録。
- 押されたキーが
- 防御的実装
- AudioSource が見つからない場合は
warn/errorを出して処理を中断。 - 入力リスナーの二重登録や二重解除を避けるために
_isListeningフラグを管理。
- AudioSource が見つからない場合は
使用手順と動作確認
1. スクリプトファイルの作成
- Editor の Assets パネルで右クリックします。
- Create → TypeScript を選択します。
- 作成されたスクリプトの名前を VisibilityToggle.ts に変更します。
- ダブルクリックして開き、上記の TypeScript コードを丸ごと貼り付けて保存します。
2. テスト用ノード(メニュー)を用意する
ここでは簡単なメニュー用ノードを例にします。
- Hierarchy パネルで右クリック → Create → UI → Canvas を作成(既にある場合はそれを使用)。
- Canvas を選択し、右クリック → Create → UI → Sprite でメニュー用の背景 Sprite を作成します。
- 名前を MenuPanel などに変更すると分かりやすいです。
- Sprite のサイズや色を調整して「メニューっぽい」見た目にしておきます。
3. VisibilityToggle コンポーネントをアタッチ
- Hierarchy で先ほど作成した MenuPanel ノードを選択します。
- Inspector の下部にある Add Component ボタンをクリックします。
- Custom Component → VisibilityToggle を選択して追加します。
4. プロパティを設定する
Inspector に表示された VisibilityToggle の各プロパティを設定します。
- Toggle Key
- ドロップダウンから ESCAPE を選択します(Esc キーでメニュー開閉)。
- Initial Visible
- false に設定すると、ゲーム開始時はメニューが非表示になります。
- ゲーム開始時から表示しておきたい場合は true にします。
- Listen When Inactive
- メニューが非表示のときでも Esc で開けるようにしたいので、true を推奨します。
- Use Sound
- トグル時に SE を鳴らしたい場合は true にします。
- その場合、MenuPanel ノードに AudioSource コンポーネントを追加し、Clip に任意の音声を設定してください。
- AudioSource を付け忘れると、コンソールに警告/エラーが出ます(動作には致命的ではありません)。
- Log Debug
- 動作確認中は true にしておくと、キー入力や表示状態の変化が Console に表示されて分かりやすいです。
- リリースビルドでは false にしておくとよいでしょう。
AudioSource の追加手順(任意)
Use Sound = true の場合のみ必要です。
- MenuPanel ノードを選択します。
- Inspector → Add Component → Audio → AudioSource を追加します。
- AudioSource コンポーネントの Clip に、Assets に用意した SE(例: click.wav)をドラッグ&ドロップします。
- Play On Awake は OFF にしておきます(トグル時だけ鳴らしたいため)。
5. ゲームを実行して動作確認
- Editor 上部の Play ボタンを押してゲームを実行します。
- ゲーム画面が表示されたら、設定したキー(例: Esc)を押します。
- 以下の挙動を確認します。
- Esc を押すたびに MenuPanel ノードの表示/非表示が切り替わる。
Log Debug = trueの場合、Console にログが出力される。Use Sound = trueかつ AudioSource が正しく設定されている場合、トグルの度に SE が鳴る。
もし動作しない場合は、次の点を確認してください。
- VisibilityToggle.ts のクラス名と
@ccclass('VisibilityToggle')の文字列が一致しているか。 - Inspector で Toggle Key が期待するキーに設定されているか。
- 他の UI などがフォーカスを奪っていないか(通常は
inputはグローバルなので問題ないはずです)。
まとめ
本記事では、Cocos Creator 3.8 / TypeScript を用いて、任意のキー入力でノードの表示/非表示を切り替える汎用コンポーネント VisibilityToggle を実装しました。
- アタッチしたノード自身の
activeをトグルするだけのシンプルな設計。 - トグルキー、初期表示状態、非表示中の入力監視、サウンド再生、ログ出力などを 全てインスペクタから設定可能。
- 他のカスタムスクリプトに一切依存せず、この 1 ファイルだけで完結するため、どのプロジェクトにも簡単に持ち込める。
- AudioSource が無い場合の警告、リスナーの二重登録防止など、防御的な実装で安定動作を確保。
メインメニュー、ポーズメニュー、ステータスウィンドウ、ヘルプ画面など、「キーで開閉する UI」 であれば、このコンポーネントをそのまま再利用できます。ノードを複製してそれぞれに VisibilityToggle を付け、別のキーに割り当てることで、複数種類のメニューを簡単に管理することも可能です。
プロジェクトのどこにでも持っていける小さな「便利パーツ」として、ぜひ活用してみてください。




