【Cocos Creator】アタッチするだけ!VisibilityToggle (表示切替)の実装方法【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】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 のときだけ AudioSourcegetComponent で取得。見つからない場合は warn を出力。
    • _registerInput() でグローバルなキーボード入力を監視開始。
  • onEnable / onDisable
    • listenWhenInactive の設定に応じて、ノードの有効/無効化に合わせて入力リスナーを再登録・解除。
    • 「非表示中も Esc で開けるメニュー」のようなケースでは listenWhenInactive = true にする。
  • _onKeyDown
    • 押されたキーが toggleKey と一致したときだけ処理を行う。
    • this.node.active を反転させて表示状態をトグル。
    • 必要なら _playToggleSound() で SE を再生。
    • listenWhenInactive = false の場合、非表示になったらリスナー解除、再度表示されたらリスナー再登録。
  • 防御的実装
    • AudioSource が見つからない場合は warn / error を出して処理を中断。
    • 入力リスナーの二重登録や二重解除を避けるために _isListening フラグを管理。

使用手順と動作確認

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

  1. Editor の Assets パネルで右クリックします。
  2. Create → TypeScript を選択します。
  3. 作成されたスクリプトの名前を VisibilityToggle.ts に変更します。
  4. ダブルクリックして開き、上記の TypeScript コードを丸ごと貼り付けて保存します。

2. テスト用ノード(メニュー)を用意する

ここでは簡単なメニュー用ノードを例にします。

  1. Hierarchy パネルで右クリック → Create → UI → Canvas を作成(既にある場合はそれを使用)。
  2. Canvas を選択し、右クリック → Create → UI → Sprite でメニュー用の背景 Sprite を作成します。
    • 名前を MenuPanel などに変更すると分かりやすいです。
    • Sprite のサイズや色を調整して「メニューっぽい」見た目にしておきます。

3. VisibilityToggle コンポーネントをアタッチ

  1. Hierarchy で先ほど作成した MenuPanel ノードを選択します。
  2. Inspector の下部にある Add Component ボタンをクリックします。
  3. 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 の場合のみ必要です。

  1. MenuPanel ノードを選択します。
  2. Inspector → Add Component → Audio → AudioSource を追加します。
  3. AudioSource コンポーネントの Clip に、Assets に用意した SE(例: click.wav)をドラッグ&ドロップします。
  4. Play On Awake は OFF にしておきます(トグル時だけ鳴らしたいため)。

5. ゲームを実行して動作確認

  1. Editor 上部の Play ボタンを押してゲームを実行します。
  2. ゲーム画面が表示されたら、設定したキー(例: Esc)を押します。
  3. 以下の挙動を確認します。
    • 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 を付け、別のキーに割り当てることで、複数種類のメニューを簡単に管理することも可能です。

プロジェクトのどこにでも持っていける小さな「便利パーツ」として、ぜひ活用してみてください。

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