【Cocos Creator 3.8】InputVisualizer の実装:アタッチするだけで「押したキーに対応するアイコンを自動表示」できる汎用スクリプト
このコンポーネントは、キーボード入力(例:WASD / 矢印キー / スペースなど)を監視し、押されているキーに対応したアイコンを自動的に表示する「入力表示用 UI コンポーネント」です。チュートリアルやキーガイド、リプレイ画面などで「プレイヤーがどのキーを押しているか」を視覚的に見せたいときに便利です。
InputVisualizer を UI ノード(例:Canvas 配下の空ノード)にアタッチし、インスペクタで「どのキーを監視するか」と「対応するアイコン画像」を設定するだけで動作します。外部の GameManager などには一切依存しません。
コンポーネントの設計方針
機能要件の整理
- 指定したキー(複数可)の押下状態を常に監視する。
- 監視対象キーごとに「押されているときだけ表示されるアイコン」を用意する。
- アイコンは InputVisualizer がアタッチされたノードの子として自動生成し、Transform や Anchor はインスペクタで調整可能にする。
- 押下中はアイコンを表示、離したら非表示にする。
- 押している間だけ軽く拡大させるなど、簡単な視覚的フィードバックも付けられるようにする(任意)。
- 外部スクリプトには依存せず、このコンポーネント単体で完結させる。
外部依存をなくすためのアプローチ
- 必要な情報はすべてインスペクタから設定できるようにする。
- 「どのキーを監視するか」と「表示するアイコン」は シリアライズ可能な構造体(クラス) にまとめ、配列として設定できるようにする。
- UI 表示に必要な
Spriteコンポーネントは、コード内で動的に生成し、存在しない場合は自動追加する。 - Input コンポーネントは Cocos の
input/systemEventを直接利用し、独自の InputManager を作らない。
インスペクタで設定可能なプロパティ設計
今回のコンポーネントでは、以下のようなプロパティを用意します。
- keyBindings: KeyBinding[]
監視するキーと、そのキーに対応するアイコンの設定をまとめた配列。- keyCode: KeyCode
どのキーを監視するか。KeyCode.KEY_W,KeyCode.ARROW_LEFT,KeyCode.SPACEなどを選択。 - spriteFrame: SpriteFrame | null
押下時に表示するアイコン画像。UI 用の SpriteFrame を指定。 - offset: Vec3
InputVisualizer ノードからの相対位置。複数キーを並べたいときに、それぞれの表示位置を調整する。 - pressedScale: number
押している間のスケール倍率。1.0 なら拡大なし、1.2 なら 1.2 倍に拡大。
- keyCode: KeyCode
- autoHideWhenNoInput: boolean
どのキーも押されていないときに、InputVisualizer ノード自体を非表示にするかどうか。
true:何も押していないときは全体を非表示。
false:アイコンだけ非表示で、親ノードは表示のまま。 - listenOnFocusOnly: boolean
ゲームウィンドウにフォーカスがあるときだけ入力を受け付けるかどうか。通常はtrue推奨。
これにより、チュートリアルごとに「監視するキー」と「アイコン配置」を変えたい場合でも、コンポーネントを再利用しつつインスペクタの設定だけで柔軟に対応できます。
TypeScriptコードの実装
以下が完成した InputVisualizer.ts の全コードです。
import { _decorator, Component, Node, Sprite, SpriteFrame, Vec3, UITransform, input, Input, EventKeyboard, KeyCode } from 'cc';
const { ccclass, property } = _decorator;
/**
* 個々のキーに対応する表示設定
*/
@ccclass('InputVisualizerKeyBinding')
export class InputVisualizerKeyBinding {
@property({
type: KeyCode,
tooltip: '監視するキーコードを指定します(例: KEY_W, ARROW_LEFT, SPACE など)。',
})
public keyCode: KeyCode = KeyCode.NONE;
@property({
type: SpriteFrame,
tooltip: 'このキーが押されている間に表示するアイコン画像(SpriteFrame)を指定します。',
})
public spriteFrame: SpriteFrame | null = null;
@property({
tooltip: 'InputVisualizer ノードからの相対位置(ローカル座標)を指定します。',
})
public offset: Vec3 = new Vec3(0, 0, 0);
@property({
tooltip: 'キー押下中に適用するスケール倍率。1.0 で拡大なし、1.2 で 1.2 倍に拡大します。',
min: 0.1,
max: 5.0,
step: 0.1,
})
public pressedScale: number = 1.0;
/** 実行時に生成されるアイコンノード参照(エディタには表示されません) */
public runtimeNode: Node | null = null;
/** 押されているかどうかの状態フラグ */
public isPressed: boolean = false;
}
@ccclass('InputVisualizer')
export class InputVisualizer extends Component {
@property({
type: [InputVisualizerKeyBinding],
tooltip: '監視するキーと、そのキーに対応するアイコン画像・表示位置などを設定します。',
})
public keyBindings: InputVisualizerKeyBinding[] = [];
@property({
tooltip: 'どのキーも押されていないときに、このノード自体を非表示にするかどうか。\ntrue: 全体を非表示 / false: 親ノードは表示のまま。',
})
public autoHideWhenNoInput: boolean = false;
@property({
tooltip: 'ゲームウィンドウにフォーカスがあるときのみ入力を受け付けるかどうか(通常は true 推奨)。',
})
public listenOnFocusOnly: boolean = true;
/** 1 フレーム前に「何かしらのキーが押されていたか」の状態 */
private _hadAnyInputLastFrame: boolean = false;
onLoad() {
// このノードが UI として使われることを前提に、UITransform が無い場合は自動追加しておく
let uiTransform = this.node.getComponent(UITransform);
if (!uiTransform) {
uiTransform = this.node.addComponent(UITransform);
console.warn('[InputVisualizer] UITransform が存在しなかったため、自動で追加しました。UI レイアウトを調整する場合は確認してください。');
}
// キーごとのアイコンノードを生成
this._createKeyIconNodes();
// 入力イベントの登録
input.on(Input.EventType.KEY_DOWN, this._onKeyDown, this);
input.on(Input.EventType.KEY_UP, this._onKeyUp, this);
}
onDestroy() {
// 入力イベントの解除
input.off(Input.EventType.KEY_DOWN, this._onKeyDown, this);
input.off(Input.EventType.KEY_UP, this._onKeyUp, this);
}
/**
* 各 KeyBinding に対応するアイコンノードを生成し、初期状態では非表示にする。
*/
private _createKeyIconNodes() {
if (!this.keyBindings || this.keyBindings.length === 0) {
console.warn('[InputVisualizer] keyBindings が設定されていません。インスペクタで監視するキーとアイコンを設定してください。');
return;
}
for (const binding of this.keyBindings) {
if (!binding) {
continue;
}
if (!binding.spriteFrame) {
console.warn(`[InputVisualizer] KeyCode=${binding.keyCode} の spriteFrame が設定されていません。このキー用のアイコンは表示されません。`);
continue;
}
// アイコン用ノードを作成
const iconNode = new Node(`KeyIcon_${KeyCode[binding.keyCode] ?? binding.keyCode}`);
iconNode.setParent(this.node);
iconNode.setPosition(binding.offset);
// Sprite コンポーネントを追加
const sprite = iconNode.addComponent(Sprite);
sprite.spriteFrame = binding.spriteFrame;
// 初期状態は非表示
iconNode.active = false;
binding.runtimeNode = iconNode;
binding.isPressed = false;
}
}
/**
* キー押下イベント
*/
private _onKeyDown(event: EventKeyboard) {
if (this.listenOnFocusOnly && !this._isWindowFocused()) {
return;
}
const keyCode = event.keyCode as KeyCode;
for (const binding of this.keyBindings) {
if (!binding || binding.keyCode !== keyCode) {
continue;
}
if (!binding.runtimeNode) {
// spriteFrame 未設定などでノードが作られていない場合
continue;
}
binding.isPressed = true;
binding.runtimeNode.active = true;
binding.runtimeNode.setScale(new Vec3(binding.pressedScale, binding.pressedScale, 1));
}
}
/**
* キー離しイベント
*/
private _onKeyUp(event: EventKeyboard) {
if (this.listenOnFocusOnly && !this._isWindowFocused()) {
return;
}
const keyCode = event.keyCode as KeyCode;
for (const binding of this.keyBindings) {
if (!binding || binding.keyCode !== keyCode) {
continue;
}
if (!binding.runtimeNode) {
continue;
}
binding.isPressed = false;
binding.runtimeNode.active = false;
// スケールは 1 に戻しておく
binding.runtimeNode.setScale(new Vec3(1, 1, 1));
}
}
/**
* ブラウザ or ネイティブでウィンドウにフォーカスがあるかどうかをざっくり判定
*/
private _isWindowFocused(): boolean {
if (typeof window !== 'undefined' && 'document' in window) {
return document.hasFocus();
}
// ネイティブなどではとりあえず true 扱い
return true;
}
update(deltaTime: number) {
// 何かしらのキーが押されているかを確認
let hasAnyInput = false;
for (const binding of this.keyBindings) {
if (binding && binding.isPressed) {
hasAnyInput = true;
break;
}
}
// autoHideWhenNoInput が有効なら、ノード自体の表示・非表示を切り替える
if (this.autoHideWhenNoInput && this.node.isValid) {
if (hasAnyInput !== this._hadAnyInputLastFrame) {
this.node.active = hasAnyInput;
}
}
this._hadAnyInputLastFrame = hasAnyInput;
}
}
主要メソッドの解説
- onLoad()
- UITransform が付いていない場合は自動で追加し、UI レイアウトの前提を整えます。
_createKeyIconNodes()を呼び出し、各キーに対応するアイコンノード(Sprite)を生成します。- Cocos の
inputに対してKEY_DOWN/KEY_UPイベントを登録します。
- _createKeyIconNodes()
keyBindings配列を走査し、SpriteFrame が設定されているものだけアイコンノードを生成します。- 生成されたノードは
binding.runtimeNodeに保存し、初期状態ではactive = falseにします。 - SpriteFrame が設定されていない場合は
console.warnで警告を出します。
- _onKeyDown() / _onKeyUp()
- 押された / 離されたキーの
keyCodeをもとに、対応するbindingを探します。 - 押下時は
binding.isPressed = trueとし、アイコンノードをactive = trueにして、pressedScale倍に拡大します。 - 離したときは
binding.isPressed = falseにし、アイコンノードを非表示に戻します。 listenOnFocusOnlyがtrueの場合は、ブラウザのフォーカスが外れているときに入力を無視します。
- 押された / 離されたキーの
- update()
- 毎フレーム
keyBindingsを走査して、「どれか 1 つでも押されているか」を判定します。 autoHideWhenNoInputがtrueのとき、何も押されていなければ InputVisualizer ノード自体を非表示にします。
- 毎フレーム
使用手順と動作確認
1. スクリプトファイルの作成
- Assets パネルで右クリック → Create → TypeScript を選択します。
- ファイル名を
InputVisualizer.tsに変更します。 - 作成された
InputVisualizer.tsをダブルクリックし、エディタで開きます。 - 中身をすべて削除し、前述の TypeScript コードをそのまま貼り付けて保存します。
2. テスト用の UI ノードを作成
- Hierarchy パネルで右クリック → Create → UI → Canvas(既に Canvas がある場合はスキップ)。
- Canvas の子として、右クリック → Create → Empty Node を選択し、ノード名を
InputVisualizerRootなどに変更します。 InputVisualizerRootを選択し、Inspector の UITransform でアンカーやサイズを必要に応じて調整します(中央に小さく表示したい場合はデフォルトのままで問題ありません)。
3. コンポーネントのアタッチ
InputVisualizerRootノードを選択します。- Inspector 下部の Add Component ボタンをクリックします。
- Custom カテゴリから InputVisualizer を選択して追加します。
4. キーとアイコンの設定
次に、どのキーを監視し、どのアイコンを表示するかを設定します。
- あらかじめ、プロジェクト内に UI 用の画像(例:
key_w.png,key_a.png,key_d.png,space.png等)をインポートし、それぞれ SpriteFrame として利用できる状態にしておきます。 InputVisualizerRootを選択し、Inspector の InputVisualizer コンポーネントを確認します。- Key Bindings の右側にある + ボタンをクリックし、要素を追加します。(例として 4 つ追加)
各要素を以下のように設定してみてください(WASD + Space の例):
- 要素 0(W キー)
- Key Code:
KEY_W - Sprite Frame:
key_w画像の SpriteFrame - Offset:
(0, 0, 0)(中央) - Pressed Scale:
1.1
- Key Code:
- 要素 1(A キー)
- Key Code:
KEY_A - Sprite Frame:
key_a - Offset:
(-80, 0, 0)(左に 80) - Pressed Scale:
1.1
- Key Code:
- 要素 2(D キー)
- Key Code:
KEY_D - Sprite Frame:
key_d - Offset:
(80, 0, 0)(右に 80) - Pressed Scale:
1.1
- Key Code:
- 要素 3(Space キー)
- Key Code:
SPACE - Sprite Frame:
space - Offset:
(0, -80, 0)(下に 80) - Pressed Scale:
1.0(拡大なし)
- Key Code:
その他のプロパティはまず以下のように設定しておきます:
- Auto Hide When No Input:
false(常に親ノードは表示) - Listen On Focus Only:
true
5. プレビューで動作確認
- メインメニューの Preview ボタン(▶)をクリックし、ブラウザまたはシミュレータでゲームを起動します。
- ゲーム画面が表示されたら、必ずゲームウィンドウを一度クリックして フォーカスを当てます。
- キーボードで
W/A/D/Spaceを押してみましょう。- 押している間だけ対応するキーアイコンが表示され、
- 離すとアイコンが消えるはずです。
- Pressed Scale を 1.1 にしているキーは、押している間だけ少し大きく表示されます。
- Auto Hide When No Input を
trueに変更して再度プレビューすると、何も押していないときはInputVisualizerRootノード自体が非表示になります(Hierarchy 上でもグレーアウトされます)。
6. よくあるハマりポイントと対処
- アイコンが表示されない
keyBindingsに要素が追加されているか確認。- 各要素の Sprite Frame が正しく設定されているか確認。
- Canvas の RenderMode やカメラ設定で UI が見切れていないか確認。
- ゲームウィンドウにフォーカスが当たっているか確認(Listen On Focus Only が true の場合)。
- キーを押しても反応しない
- Key Code が期待しているキーと一致しているか確認(例:スペースは
SPACE)。 - ブラウザで複数タブを開いているときは、ゲームタブにフォーカスがあるか確認。
- Key Code が期待しているキーと一致しているか確認(例:スペースは
まとめ
この InputVisualizer コンポーネントは、
- 指定したキーの押下状態を監視し、
- 対応するアイコンを自動生成して表示・非表示を切り替え、
- 押下中の拡大など簡単なフィードバックも付けられる、
汎用的な「入力表示 UI」を実現します。
ポイントは以下の通りです。
- 監視キーとアイコン設定を KeyBinding 配列 として抽象化し、インスペクタから柔軟に編集可能にしたこと。
- アイコンノードをコード側で自動生成することで、シーン上の事前配置や外部スクリプトへの依存を一切なくしていること。
- UITransform や Sprite コンポーネントを防御的に追加し、必要な標準コンポーネントが無い場合でも動作するようにしていること。
チュートリアルやキーガイドだけでなく、
- アクションゲームの「入力リプレイ表示」
- トレーニングモードの「現在押しているキーの可視化」
- ゲームパッド対応版に拡張して「ボタンアイコン表示」にする
といった応用も簡単です。シーンごとに InputVisualizer を複数配置し、異なるキーセット・レイアウトを設定するだけで、さまざまなチュートリアル UI を素早く構築できます。
このスクリプト単体で完結しているため、プロジェクトにそのままコピーして使い回せる「入力表示コンポーネント」として、ぜひ自前の UI ライブラリに組み込んでみてください。




