【Cocos Creator 3.8】TabSwitcher の実装:アタッチするだけで「装備/アイテム/設定」などのタブボタンに応じてパネル表示を切り替える汎用スクリプト
UI でよくある「タブ切り替え(装備・アイテム・設定…)」を、1 つのコンポーネントをアタッチするだけで実現できるようにします。
この TabSwitcher は、タブボタンと対応パネルをインスペクタで登録するだけで、クリック時に自動で表示パネルを切り替えてくれる汎用スクリプトです。
特定の GameManager やシングルトンに依存せず、どのシーン・どの UI でも再利用できます。
コンポーネントの設計方針
要件整理
- 複数の「タブボタン」と、それぞれに対応する「パネル(コンテンツ)」を持つ。
- タブボタンをクリック(またはタップ)すると、対応パネルだけを表示し、他のパネルは非表示にする。
- 「選択中タブ」と「非選択タブ」で、見た目を簡易的に切り替えられるようにする(任意)。
- 外部のカスタムスクリプトに依存せず、このコンポーネント単体で完結する。
- 必要なノードやコンポーネント(Button, Sprite など)は
@propertyでインスペクタから指定する。 - 不足している標準コンポーネントは
getComponentで取得を試み、なければエラーログを出す。
基本アーキテクチャ
- TabSwitcher を 1 つの親ノード(例:タブ全体のルートノード)にアタッチする。
- インスペクタで以下を設定する:
- タブボタンと対応パネルのペア一覧
- 初期状態で選択するタブのインデックス
- 選択中タブに適用する色(任意)
- 非選択タブに適用する色(任意)
- ボタンには
Buttonコンポーネントが付いている前提で、onLoad 時にクリックイベントを登録する。 - クリックされたタブのインデックスをもとに、全パネルの表示/非表示とタブ見た目の更新を行う。
インスペクタで設定可能なプロパティ設計
tabs(TabConfig[])- 要素ごとに「タブボタンノード」と「対応パネルノード」をセットで登録する配列。
- 例:0 番目 = 装備タブボタン & 装備パネル、1 番目 = アイテムタブボタン & アイテムパネル …
- 各要素の構造:
buttonNode: Node… タブとしてクリックされるボタンのノード(Button コンポーネント必須)panelNode: Node… 表示を切り替えるコンテンツパネルのノード
defaultTabIndex: number- ゲーム開始時(
start)に選択状態にするタブのインデックス。 - 0 以上
tabs.length - 1以下の値。 - 範囲外の場合は 0 にフォールバックする。
- ゲーム開始時(
useTabColorHighlight: booleantrueのとき、選択中タブと非選択タブの色を自動で切り替える。falseのとき、色変更は行わない(見た目は自前で制御したい場合)。
selectedColor: ColoruseTabColorHighlightがtrueのときに使用。- 選択中タブの
SpriteまたはLabelに適用する色。
unselectedColor: Color- 非選択タブに適用する色。
logWarnings: booleantrueのとき、不足している Button / Sprite / Label などをコンソール警告として出力する。- 完成後にログを減らしたい場合は
falseにしてサイレントにできる。
TypeScriptコードの実装
import { _decorator, Component, Node, Button, Color, Sprite, Label, UIOpacity, warn, log } from 'cc';
const { ccclass, property } = _decorator;
/**
* 個々のタブ設定(タブボタンと対応パネルのペア)
*/
@ccclass('TabConfig')
export class TabConfig {
@property({
tooltip: 'タブとしてクリックされるボタンのノード。\nButton コンポーネントが付いている必要があります。'
})
public buttonNode: Node | null = null;
@property({
tooltip: 'このタブが選択されたときに表示するパネル(コンテンツ)のルートノード。'
})
public panelNode: Node | null = null;
}
@ccclass('TabSwitcher')
export class TabSwitcher extends Component {
@property({
type: [TabConfig],
tooltip: 'タブボタンと対応パネルのペアを配列で登録します。\n' +
'例: 0=装備タブ & 装備パネル, 1=アイテムタブ & アイテムパネル, ...'
})
public tabs: TabConfig[] = [];
@property({
tooltip: '起動時に選択状態にするタブのインデックス。\n' +
'範囲外の場合は 0 番が選択されます。'
})
public defaultTabIndex: number = 0;
@property({
tooltip: 'タブ選択状態で色を自動変更するかどうか。\n' +
'true の場合、タブの Sprite または Label の色を切り替えます。'
})
public useTabColorHighlight: boolean = true;
@property({
tooltip: '選択中タブの色(useTabColorHighlight が true のときに使用)。'
})
public selectedColor: Color = new Color(255, 255, 255, 255);
@property({
tooltip: '非選択タブの色(useTabColorHighlight が true のときに使用)。'
})
public unselectedColor: Color = new Color(180, 180, 180, 255);
@property({
tooltip: 'true のとき、不足している Button / Sprite / Label などを警告ログとして出力します。'
})
public logWarnings: boolean = true;
/** 現在選択されているタブのインデックス。-1 の場合は未選択。 */
private _currentIndex: number = -1;
onLoad() {
// タブ配列が空の場合は警告
if (this.tabs.length === 0) {
if (this.logWarnings) {
warn('[TabSwitcher] tabs が設定されていません。インスペクタでタブを追加してください。', this.node);
}
return;
}
// 各タブボタンにクリックイベントを登録
for (let i = 0; i < this.tabs.length; i++) {
const config = this.tabs[i];
if (!config) {
continue;
}
const btnNode = config.buttonNode;
if (!btnNode) {
if (this.logWarnings) {
warn(`[TabSwitcher] tabs[${i}].buttonNode が設定されていません。`, this.node);
}
continue;
}
const buttonComp = btnNode.getComponent(Button);
if (!buttonComp) {
if (this.logWarnings) {
warn(`[TabSwitcher] ノード "${btnNode.name}" に Button コンポーネントがありません。タブとして動作しません。`, btnNode);
}
continue;
}
// 既存のクリックイベントに追加する形で登録
buttonComp.node.on(Button.EventType.CLICK, () => {
this.selectTab(i);
}, this);
}
}
start() {
if (this.tabs.length === 0) {
return;
}
// defaultTabIndex の範囲をクランプ
let index = this.defaultTabIndex;
if (index < 0 || index >= this.tabs.length) {
if (this.logWarnings) {
warn(`[TabSwitcher] defaultTabIndex (${index}) が範囲外です。0 にフォールバックします。`, this.node);
}
index = 0;
}
// 起動時にデフォルトタブを選択
this.selectTab(index, true);
}
/**
* 指定インデックスのタブを選択状態にし、対応パネルを表示する。
* @param index 選択するタブのインデックス
* @param force 同じインデックスでも強制的に更新する場合 true
*/
public selectTab(index: number, force: boolean = false): void {
if (this.tabs.length === 0) {
return;
}
if (index < 0 || index >= this.tabs.length) {
if (this.logWarnings) {
warn(`[TabSwitcher] selectTab: index (${index}) が範囲外です。`, this.node);
}
return;
}
if (!force && index === this._currentIndex) {
// すでに選択中のタブ
return;
}
this._currentIndex = index;
// 各タブとパネルの状態を更新
for (let i = 0; i < this.tabs.length; i++) {
const config = this.tabs[i];
if (!config) {
continue;
}
// パネル表示/非表示
const panelNode = config.panelNode;
if (panelNode) {
const isActive = (i === index);
panelNode.active = isActive;
// UIOpacity があればフェード制御などにも応用可能(ここでは単純に 255 / 0)
const uiOpacity = panelNode.getComponent(UIOpacity);
if (uiOpacity) {
uiOpacity.opacity = isActive ? 255 : 0;
}
} else if (this.logWarnings) {
warn(`[TabSwitcher] tabs[${i}].panelNode が設定されていません。`, this.node);
}
// タブの見た目更新(色変更)
this.updateTabAppearance(i, i === index);
}
if (this.logWarnings) {
log(`[TabSwitcher] タブ ${index} を選択しました。`, this.node);
}
}
/**
* タブの見た目(色など)を更新する。
* @param tabIndex 対象タブのインデックス
* @param selected 選択状態かどうか
*/
private updateTabAppearance(tabIndex: number, selected: boolean): void {
if (!this.useTabColorHighlight) {
return;
}
const config = this.tabs[tabIndex];
if (!config || !config.buttonNode) {
return;
}
const node = config.buttonNode;
const colorToApply = selected ? this.selectedColor : this.unselectedColor;
// Sprite があれば Sprite に色を適用
const sprite = node.getComponent(Sprite);
if (sprite) {
sprite.color = colorToApply;
} else {
// Label があれば Label に色を適用
const label = node.getComponent(Label);
if (label) {
label.color = colorToApply;
} else if (this.logWarnings) {
// どちらもない場合は一度だけ注意喚起(タブ全般の見た目を自前で制御している場合は無視してよい)
warn(`[TabSwitcher] タブノード "${node.name}" に Sprite / Label がありません。色変更は行われません。`, node);
}
}
}
/**
* 現在選択されているタブのインデックスを返します。未選択の場合は -1。
*/
public get currentIndex(): number {
return this._currentIndex;
}
}
コードのポイント解説
TabConfigクラス- 各タブの「ボタンノード」と「パネルノード」を 1 セットとして管理するための単純なデータクラス。
@propertyを付けることで、インスペクタで配列として編集可能になります。
tabs: TabConfig[]- タブのペアをまとめて管理する配列。インスペクタで「Size」を増やして、要素ごとにボタンとパネルを指定します。
onLoad()- 各タブの
buttonNodeからButtonコンポーネントを取得し、クリックイベントを登録しています。 - Button がない、buttonNode が未設定などの場合は
warnで警告(logWarningsが true のとき)。
- 各タブの
start()defaultTabIndexを範囲チェックし、問題なければselectTab()で初期タブを選択します。
selectTab(index, force)- タブ切り替えの中核ロジック。
- 全タブをループし、選択中インデックスかどうかで:
- 対応パネルの
activeを ON/OFF UIOpacityがあれば 255/0 をセット(任意の演出拡張のベース)updateTabAppearance()でボタン側の色を更新
- 対応パネルの
updateTabAppearance()useTabColorHighlightが true の場合のみ実行。- タブボタンノードに
Spriteがあればその色を、なければLabelの色を変更。 - どちらもない場合は警告を出し、色変更はスキップします。
- 完全な独立性
- 他のカスタムスクリプト(GameManager など)には一切依存していません。
- 必要な情報はすべて
@property経由でインスペクタから設定する設計になっています。
使用手順と動作確認
1. スクリプトファイルの作成
- Assets パネルで右クリックします。
Create > TypeScriptを選択します。- ファイル名を
TabSwitcher.tsにします。 - 作成された
TabSwitcher.tsをダブルクリックして開き、上記のコード全体を貼り付けて保存します。
2. テスト用 UI ノードの用意
ここでは例として、「装備」「アイテム」「設定」の 3 タブを作る手順を示します。
- Hierarchy パネルで右クリック →
Create > Canvas(まだ Canvas がない場合)。 - Canvas の下に、右クリック →
Create > UI > Nodeで空ノードを作成し、名前をTabsRootなどに変更します。- この
TabsRootにTabSwitcherコンポーネントをアタッチします。
- この
TabsRootの子として、タブボタンを 3 つ作成します:TabsRootを右クリック →Create > UI > Buttonでボタンを作成し、名前をTab_Equipに変更。- 同様に
Tab_Item、Tab_Settingsを作成。 - 各ボタンの子に Label がある場合は、テキストを「装備」「アイテム」「設定」などに変更します。
- Canvas の下(または TabsRoot の下)に、パネル用ノードを 3 つ作成します:
- Canvas を右クリック →
Create > UI > Nodeでノードを作成し、名前をPanel_Equipに変更。 - 同様に
Panel_Item、Panel_Settingsを作成。 - それぞれのパネルの中に、適当な Label や Sprite を置いて内容を区別できるようにしておきます。
- 例:
Panel_Equip内の Label に「装備パネル」、Panel_Itemに「アイテムパネル」など。
- 例:
- Canvas を右クリック →
3. TabSwitcher コンポーネントをアタッチ
- Hierarchy で
TabsRootノードを選択します。 - Inspector パネルの下部で
Add Component→Custom→TabSwitcherを選択して追加します。
4. インスペクタでプロパティを設定
- tabs 配列の設定
Tabsプロパティの「Size」を3に設定します(装備/アイテム/設定の 3 タブ)。- 要素 0(Element 0):
Button NodeにTab_Equipをドラッグ&ドロップ。Panel NodeにPanel_Equipをドラッグ&ドロップ。
- 要素 1(Element 1):
Button NodeにTab_Itemを設定。Panel NodeにPanel_Itemを設定。
- 要素 2(Element 2):
Button NodeにTab_Settingsを設定。Panel NodeにPanel_Settingsを設定。
- defaultTabIndex の設定
Default Tab Indexを0に設定すると、ゲーム開始時に「装備」タブが選択されます。- 「アイテム」から始めたい場合は
1、「設定」からなら2にします。
- 色ハイライトの設定(任意)
Use Tab Color Highlightをtrueにします。Selected Colorを(255, 255, 255, 255)(白)、Unselected Colorを(160, 160, 160, 255)など、少し暗い色にすると分かりやすいです。- ボタンノードに
SpriteかLabelが付いていることを確認してください。- なければ、色変更はスキップされ、警告ログが出ます(logWarnings が true のとき)。
- ログ出力の設定
- 開発中は
Log Warningsをtrueにしておくと、不足設定があればコンソールに警告が出ます。 - 完成後ログを抑えたい場合は
falseにしても構いません。
- 開発中は
5. 再生して動作確認
- エディタ右上の「▶」ボタンを押してゲームを再生します。
- 初期状態で
defaultTabIndexで指定したタブのパネルのみが表示されていることを確認します。- 例:
defaultTabIndex = 0ならPanel_Equipだけが active。
- 例:
- ゲーム画面上で「アイテム」「設定」などのタブボタンをクリックしてみます。
- クリックしたタブに対応するパネルだけが表示され、他のパネルは非表示になること。
Use Tab Color Highlight = trueの場合、選択中タブの色がSelected Colorに変わり、他はUnselected Colorになっていること。
- Console パネルで、設定ミスがあれば
[TabSwitcher]から始まる警告ログが出ていないか確認します。
まとめ
TabSwitcher コンポーネントを使うことで、
- 「装備」「アイテム」「設定」などの典型的なタブ UI を、ボタンとパネルをインスペクタで登録するだけで実装できるようになりました。
- 外部の GameManager やシングルトンに依存しないため、どのシーン・どのプロジェクトにも簡単に持ち込めます。
- 色ハイライトやログ出力もオプションとして用意しているので、UI デザインや開発フェーズに合わせて柔軟に調整可能です。
応用として:
- 各パネルにさらにスクロールビューやリストを組み合わせれば、装備一覧・アイテム一覧・設定画面などがすぐに構築できます。
UIOpacityを利用してフェード演出を追加したり、タブ切り替え時にサウンドを鳴らす処理をselectTab()内に拡張することも容易です。
このように、1 つの独立した汎用コンポーネントとして設計しておくことで、UI タブ切り替えというよくある要素を何度でも再利用でき、ゲーム開発の効率が大きく向上します。
まずはシンプルな 2〜3 タブ構成で試し、慣れてきたら複数画面や別シーンでも再利用してみてください。




