【Cocos Creator 3.8】KeyConfig の実装:アタッチするだけで InputMap のアクションに対するキー割り当てをプレイヤーが変更できる汎用スクリプト
本記事では、Cocos Creator 3.8.7 と TypeScript で、
「InputMap のアクションに対して、プレイヤーが押したキーを割り当て直す」ための汎用コンポーネント KeyConfig を実装します。
このコンポーネントを任意のノードにアタッチするだけで、特定のアクション名に対して、ユーザーが好きなキーを押して再設定する UI を簡単に作れます。
外部の GameManager やシングルトンには一切依存せず、インスペクタで設定したアクション名とラベル/ボタンだけで完結する設計です。
コンポーネントの設計方針
機能要件の整理
- Cocos Creator 3.8 の
inputシステム(Input.EventType.KEY_DOWN)を利用して、ユーザーが押したキーを取得する。 - 「アクション名(例:
MoveLeft,Jump)」ごとに、現在割り当てられているキーを表示する。 - ユーザーが「変更ボタン」を押すと、そのアクションのキー待ち状態になり、次に押されたキーをそのアクションに割り当てる。
- 割り当て結果は ローカルストレージ(
localStorage) に保存し、ゲーム起動時に復元する。 - 他のスクリプトに依存せず、このコンポーネント単体で「キー設定 UI」と「保存・読み込み」まで完結させる。
- InputMap(アクション名とキーの対応表)は、インスペクタで編集できる形で保持する。
- UI 表示には
Labelコンポーネントを使用し、割り当て中などの状態をユーザーに分かりやすく表示する。
ここでは「エディタの Input Map アセット(入力マップアセット)」と直接連携するのではなく、
汎用の「アクション名とキーのマッピング管理コンポーネント」として実装し、
ゲーム側ではこの情報を参照して独自に判定に使うことを想定した設計にします。
インスペクタで設定可能なプロパティ設計
KeyConfig コンポーネントは以下のようなプロパティを持ちます。
-
configId: string
– 同じゲーム内で複数の KeyConfig を使う場合に、保存キーを分けるための ID。
– 例:"player1","global"など。
– 実際の保存キーはKeyConfig_[configId]のような形式でlocalStorageに保存される。 -
actions: KeyActionConfig[](カスタムクラス配列)
各アクションごとの設定をまとめた配列。
各要素(KeyActionConfig)は以下のプロパティを持つ:- actionName: string
– 論理的なアクション名。例:"MoveLeft","Jump"など。
– ゲーム側でこの名前を使って判定に利用できる。 - defaultKeyCode: KeyCode
– そのアクションに対するデフォルトのキー。
– 初回起動時や「リセット」時にこのキーが使われる。 - labelNode: Node | null
– 現在割り当てられているキー名を表示するためのLabelを持つノード。
– 未設定またはLabelが存在しない場合は、ログに警告を出しつつ動作は継続。 - changeButtonNode: Node | null
– 「キー変更」操作を受け付けるボタンノード。
–Buttonコンポーネントを持っている必要がある。
– 押されたときに、そのアクションのキー待ち状態に移行する。
- actionName: string
-
statusLabelNode: Node | null
– 「どのアクションのキーを待っているか」「保存完了」などのメッセージを表示するためのLabelノード。
– 未設定でも動作はするが、ユーザーへのフィードバックが分かりにくくなる。 -
autoSave: boolean
–trueの場合、キーが変更されるたびに自動でlocalStorageに保存する。
–falseの場合、後述のsaveToStorage()を手動で呼び出す必要がある(とはいえ、このコンポーネント単体で完結させるため、通常はtrueを推奨)。 -
debugLog: boolean
–trueの場合、キー変更や保存・読み込みの詳細なログをconsole.logへ出力する。
– 開発中のデバッグ用途。
防御的実装として、各 UI ノードに必要なコンポーネント(Label や Button)が無い場合は、
console.error / console.warn で警告を出し、クラッシュせずに動作を継続します。
TypeScriptコードの実装
import { _decorator, Component, Node, Label, Button, input, Input, EventKeyboard, KeyCode, sys } from 'cc';
const { ccclass, property } = _decorator;
/**
* 1アクション分の設定情報
*/
@ccclass('KeyActionConfig')
export class KeyActionConfig {
@property({
tooltip: '論理アクション名。ゲーム側で識別に使う名前です(例: MoveLeft, Jump など)。'
})
public actionName: string = '';
@property({
type: KeyCode,
tooltip: 'このアクションのデフォルトキー。初期化やリセット時に使用されます。'
})
public defaultKeyCode: KeyCode = KeyCode.NONE;
@property({
type: Node,
tooltip: '現在割り当てられているキー名を表示する Label を持つノード。任意ですが、未設定だと表示されません。'
})
public labelNode: Node | null = null;
@property({
type: Node,
tooltip: '「キー変更」ボタンノード。Button コンポーネントを持っている必要があります。'
})
public changeButtonNode: Node | null = null;
// ランタイム用:現在割り当てられているキー
public currentKeyCode: KeyCode = KeyCode.NONE;
}
/**
* KeyConfig
* - 各アクションに対して、ユーザーがキーを割り当て直せるようにする汎用コンポーネント
* - localStorage に保存・読み込みを行う
*/
@ccclass('KeyConfig')
export class KeyConfig extends Component {
@property({
tooltip: '設定を保存するためのID。同じIDを持つ KeyConfig は同じストレージキーを共有します。'
})
public configId: string = 'default';
@property({
type: [KeyActionConfig],
tooltip: 'キーコンフィグ対象となるアクションの一覧。アクション名・デフォルトキー・UIノードを設定します。'
})
public actions: KeyActionConfig[] = [];
@property({
type: Node,
tooltip: '状態メッセージを表示する Label ノード(例: 「Jump のキー入力待ち...」など)。任意。'
})
public statusLabelNode: Node | null = null;
@property({
tooltip: 'キーが変更されるたびに自動で localStorage に保存するかどうか。'
})
public autoSave: boolean = true;
@property({
tooltip: 'コンソールにデバッグログを出力するかどうか。'
})
public debugLog: boolean = false;
// 現在キー待ち状態のアクション(なければ null)
private _waitingAction: KeyActionConfig | null = null;
// キャッシュ用:statusLabel
private _statusLabel: Label | null = null;
onLoad() {
// 状態ラベルの取得
if (this.statusLabelNode) {
const label = this.statusLabelNode.getComponent(Label);
if (!label) {
console.error('[KeyConfig] statusLabelNode に Label コンポーネントがありません。状態メッセージは表示されません。');
}
this._statusLabel = label;
}
// 各アクションの初期化(デフォルトキーを currentKeyCode にコピー)
for (const action of this.actions) {
action.currentKeyCode = action.defaultKeyCode;
// Label と Button の存在チェック
if (action.labelNode) {
const label = action.labelNode.getComponent(Label);
if (!label) {
console.error(`[KeyConfig] action "${action.actionName}" の labelNode に Label コンポーネントがありません。`);
}
}
if (action.changeButtonNode) {
const btn = action.changeButtonNode.getComponent(Button);
if (!btn) {
console.error(`[KeyConfig] action "${action.actionName}" の changeButtonNode に Button コンポーネントがありません。`);
} else {
// ボタンのクリックイベントにハンドラを登録
btn.node.on(Button.EventType.CLICK, () => {
this.startWaitingKey(action);
}, this);
}
}
}
// 既存の保存データがあれば読み込む
this.loadFromStorage();
// UI表示を反映
this.refreshAllLabels();
// キー入力イベント登録
input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this);
}
onDestroy() {
// イベントの解除
input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this);
// ボタンのイベントも解除(念のため)
for (const action of this.actions) {
if (action.changeButtonNode) {
const btn = action.changeButtonNode.getComponent(Button);
if (btn) {
btn.node.off(Button.EventType.CLICK, () => {
this.startWaitingKey(action);
}, this);
}
}
}
}
/**
* 指定アクションのキー変更待ち状態を開始
*/
private startWaitingKey(action: KeyActionConfig) {
this._waitingAction = action;
this.setStatusMessage(`"${action.actionName}" のキー入力待ち... 任意のキーを押してください。`);
if (this.debugLog) {
console.log(`[KeyConfig] Waiting for key input for action "${action.actionName}".`);
}
}
/**
* キー入力イベントハンドラ
*/
private onKeyDown(event: EventKeyboard) {
if (!this._waitingAction) {
// 何も待っていないときは無視
return;
}
const keyCode = event.keyCode as KeyCode;
// 割り当て
const action = this._waitingAction;
action.currentKeyCode = keyCode;
if (this.debugLog) {
console.log(`[KeyConfig] Action "${action.actionName}" was assigned to key: ${KeyCode[keyCode]} (${keyCode}).`);
}
// ラベル更新
this.refreshLabelForAction(action);
// 状態ラベル更新
this.setStatusMessage(`"${action.actionName}" に "${KeyCode[keyCode]}" を割り当てました。`);
// 自動保存
if (this.autoSave) {
this.saveToStorage();
}
// 待ち状態解除
this._waitingAction = null;
// イベントの既定動作を止めたい場合は preventDefault も検討(ブラウザビルド時など)
// event.preventDefault();
}
/**
* すべてのアクションのラベル表示を更新
*/
private refreshAllLabels() {
for (const action of this.actions) {
this.refreshLabelForAction(action);
}
}
/**
* 特定アクションのラベル表示を更新
*/
private refreshLabelForAction(action: KeyActionConfig) {
if (!action.labelNode) {
return;
}
const label = action.labelNode.getComponent(Label);
if (!label) {
return;
}
const keyCode = action.currentKeyCode;
const keyName = KeyCode[keyCode] ?? 'NONE';
label.string = `${action.actionName}: ${keyName}`;
}
/**
* 状態メッセージを設定
*/
private setStatusMessage(message: string) {
if (this._statusLabel) {
this._statusLabel.string = message;
}
if (this.debugLog) {
console.log('[KeyConfig] Status:', message);
}
}
/**
* localStorage から設定を読み込む
*/
public loadFromStorage() {
if (!sys.isBrowser) {
// ネイティブ環境でも sys.localStorage は使えるが、環境によっては制限があるためチェック
if (this.debugLog) {
console.warn('[KeyConfig] loadFromStorage: ブラウザ環境ではありません。sys.localStorage の挙動に注意してください。');
}
}
const storageKey = this.getStorageKey();
const json = sys.localStorage.getItem(storageKey);
if (!json) {
if (this.debugLog) {
console.log(`[KeyConfig] No saved data found for key "${storageKey}". Using default keys.`);
}
return;
}
try {
const data = JSON.parse(json) as Record<string, number>;
for (const action of this.actions) {
const code = data[action.actionName];
if (typeof code === 'number') {
action.currentKeyCode = code as KeyCode;
}
}
if (this.debugLog) {
console.log('[KeyConfig] Loaded key config from storage:', data);
}
} catch (e) {
console.error('[KeyConfig] Failed to parse saved key config:', e);
}
}
/**
* 現在の設定を localStorage に保存する
*/
public saveToStorage() {
const storageKey = this.getStorageKey();
const data: Record<string, number> = {};
for (const action of this.actions) {
data[action.actionName] = action.currentKeyCode;
}
const json = JSON.stringify(data);
sys.localStorage.setItem(storageKey, json);
if (this.debugLog) {
console.log(`[KeyConfig] Saved key config to "${storageKey}":`, data);
}
}
/**
* すべてのアクションをデフォルトキーにリセットし、保存する
*/
public resetToDefault(save: boolean = true) {
for (const action of this.actions) {
action.currentKeyCode = action.defaultKeyCode;
}
this.refreshAllLabels();
this.setStatusMessage('すべてのキーをデフォルトにリセットしました。');
if (save) {
this.saveToStorage();
}
}
/**
* ゲーム側から現在のキー割り当てを取得するためのヘルパー
* 例: getKeyForAction('Jump') で KeyCode スカラーが返る
*/
public getKeyForAction(actionName: string): KeyCode {
const action = this.actions.find(a => a.actionName === actionName);
return action ? action.currentKeyCode : KeyCode.NONE;
}
/**
* ストレージキーを組み立てる
*/
private getStorageKey(): string {
return `KeyConfig_${this.configId}`;
}
}
主要部分の解説
-
KeyActionConfigクラス
1つのアクション(例:Jump)ごとに、
– アクション名(actionName)
– デフォルトキー(defaultKeyCode)
– 表示用ラベルノード(labelNode)
– キー変更ボタンノード(changeButtonNode)
をまとめて管理するためのクラスです。
@property([KeyActionConfig])としてKeyConfigから配列で編集できます。 -
onLoad()
–statusLabelNodeからLabelを取得し、なければエラーを出します。
– 各アクションについて、currentKeyCodeをdefaultKeyCodeで初期化します。
–labelNodeにLabelがついているか、changeButtonNodeにButtonがついているかを検査し、無ければエラーログを出します。
– ボタンにクリックイベントを登録し、押されたときにstartWaitingKey(action)が呼ばれるようにします。
–localStorageから過去の設定をloadFromStorage()で読み込みます。
–refreshAllLabels()で UI 表示を更新します。
–input.on(KEY_DOWN, ...)でキー入力イベントを購読します。 -
onKeyDown()
– 何かのアクションが「キー待ち状態(_waitingAction)」なら、そのアクションに今回押されたキーを割り当てます。
–currentKeyCodeを書き換え、ラベル表示を更新し、状態メッセージを更新します。
–autoSaveがtrueならsaveToStorage()を呼び出します。
– 最後に_waitingActionをnullにして待ち状態を解除します。 -
loadFromStorage()/saveToStorage()
–configIdからKeyConfig_[configId]形式のキー名を生成し、sys.localStorageに保存・読み込みを行います。
– 保存形式は{ [actionName: string]: number }のシンプルな JSON です(値はKeyCodeの数値)。 -
getKeyForAction()
– ゲーム側から「今、Jump に割り当てられているキーコードは何か?」を取得するためのヘルパーです。
– このコンポーネントを持つノードを参照し、getComponent(KeyConfig)で取得して呼び出せば、入力判定に利用できます。
使用手順と動作確認
1. スクリプトファイルの作成
- Assets パネルで右クリック → Create → TypeScript を選択します。
- ファイル名を
KeyConfig.tsにします。 - 自動生成されたコードをすべて削除し、本記事の
KeyConfigとKeyActionConfigのコードを丸ごと貼り付けて保存します。
2. テスト用シーンの準備
- Hierarchy で右クリック → Create → Canvas を選択して UI 用の Canvas を作成します(既にあれば不要)。
- Canvas の子として、以下のノードを作成します:
- KeyConfigRoot(空ノード)
- Label_MoveLeft(Create → UI → Label)
- Button_MoveLeft(Create → UI → Button)
- Label_Jump(Create → UI → Label)
- Button_Jump(Create → UI → Button)
- StatusLabel(Create → UI → Label)
- 各 Label / Button ノードの位置やテキストは任意で調整してください。
Label_MoveLeftの Label.string を一旦MoveLeft: (未設定)などにしておく。Button_MoveLeftの Button の Label.string を変更にする。- Jump 用も同様に設定。
StatusLabelの初期文字列をキーコンフィグのテストなどにしておく。
3. KeyConfig コンポーネントをアタッチ
- Hierarchy で KeyConfigRoot ノードを選択します。
- Inspector の Add Component ボタンをクリックします。
- Custom → KeyConfig を選択してアタッチします。
4. インスペクタでプロパティを設定する
KeyConfigRoot の Inspector に表示される KeyConfig コンポーネントを設定します。
-
configId に任意の文字列を設定(例:
player1)。
→ 複数の KeyConfig を使う場合はここを変えることで保存領域を分けられます。 -
statusLabelNode に
StatusLabelノードをドラッグ&ドロップします。 -
autoSave を
trueにしておくと、キー変更のたびに自動保存されます。 -
debugLog は開発中であれば
trueにしておくと挙動が分かりやすいです。 -
actions 配列を設定します:
Sizeを 2 に設定します(MoveLeft と Jump の2つ)。-
Element 0(アクション1)を選択:
- actionName:
MoveLeft - defaultKeyCode:
KeyCode.ARROW_LEFTなど好みのキー - labelNode:
Label_MoveLeftノードをドラッグ&ドロップ - changeButtonNode:
Button_MoveLeftノードをドラッグ&ドロップ
- actionName:
-
Element 1(アクション2)を選択:
- actionName:
Jump - defaultKeyCode:
KeyCode.SPACEなど好みのキー - labelNode:
Label_Jumpノードをドラッグ&ドロップ - changeButtonNode:
Button_Jumpノードをドラッグ&ドロップ
- actionName:
この時点で、KeyConfig コンポーネントは完全に設定されました。
他のカスタムスクリプトは一切不要です。
5. 再生して動作を確認する
- エディタ右上の ▶(Play) ボタンを押してゲームを実行します。
- 画面に
MoveLeft: ARROW_LEFT、Jump: SPACEのようなラベルが表示されていることを確認します。 Button_MoveLeftの「変更」ボタンをクリックします。- StatusLabel に
"MoveLeft" のキー入力待ち... 任意のキーを押してください。のようなメッセージが表示されます。 - キーボードで
Aキーを押します。 MoveLeft: KEY_Aのようにラベルが更新され、StatusLabel に"MoveLeft" に "KEY_A" を割り当てました。のようなメッセージが表示されます。- 一度停止してから再度再生し、設定が
localStorageから復元されているか確認します。- 再生後も
MoveLeft: KEY_Aのままになっていれば保存・読み込み成功です。
- 再生後も
6. ゲーム側からキー設定を参照する(任意)
このコンポーネントは単体で完結しますが、ゲームロジックから現在のキー割り当てを参照したい場合は、
例えばプレイヤー制御スクリプト内で以下のように利用できます。
// 例: PlayerController.ts 内の一部(このスクリプト自体は任意)
// KeyConfig コンポーネントを持つノードをインスペクタで指定しておく想定
import { _decorator, Component, Node, input, Input, EventKeyboard, KeyCode } from 'cc';
import { KeyConfig } from './KeyConfig';
const { ccclass, property } = _decorator;
@ccclass('PlayerController')
export class PlayerController extends Component {
@property({ type: Node, tooltip: 'KeyConfig コンポーネントを持つノード' })
keyConfigNode: Node | null = null;
private _keyConfig: KeyConfig | null = null;
onLoad() {
if (this.keyConfigNode) {
this._keyConfig = this.keyConfigNode.getComponent(KeyConfig);
}
input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this);
}
onDestroy() {
input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this);
}
private onKeyDown(event: EventKeyboard) {
if (!this._keyConfig) {
return;
}
const moveLeftKey = this._keyConfig.getKeyForAction('MoveLeft');
const jumpKey = this._keyConfig.getKeyForAction('Jump');
if (event.keyCode === moveLeftKey) {
// 左移動処理
} else if (event.keyCode === jumpKey) {
// ジャンプ処理
}
}
}
このように、KeyConfig コンポーネントは「キー設定の管理」と「UI 操作」を完結させつつ、
必要であれば他のスクリプトからも簡単に参照できるようになっています。
まとめ
- KeyConfig コンポーネントは、アタッチするだけでキーコンフィグ画面を構築できる汎用スクリプトです。
- アクション名とデフォルトキー、表示用ラベル、変更ボタンをインスペクタから設定するだけで、ユーザーが任意のキーに変更できます。
- 設定は localStorage に自動保存/読み込みされるため、ゲームを再起動してもキーコンフィグが維持されます。
- 他のカスタムスクリプトやシングルトンに依存せず、このスクリプト単体でキー設定 UI と保存ロジックが完結しているため、プロジェクト間での再利用が容易です。
- 応用として:
- アクション数を増やしてフルキーコンフィグ画面を作る。
- 「デフォルトに戻す」ボタンから
resetToDefault()を呼び出してリセット機能を実装する。 - ゲーム内のポーズメニューにこの KeyConfig を組み込んで、いつでもキー変更できるようにする。
このコンポーネントをプロジェクトの「共通 UI パーツ」として用意しておけば、
どのゲームでも簡単に「ユーザーが自分でキーを決められる」環境を提供でき、
設定画面の実装コストを大きく削減できます。




