// DarkPlasma_GamepadSettingBase 1.0.0 // Copyright (c) 2023 DarkPlasma // This software is released under the MIT license. // http://opensource.org/licenses/mit-license.php /** * 2023/05/23 1.0.0 公開 */ /*: @target MZ @url https://github.com/elleonard/DarkPlasma-MZ-Plugins/tree/release @plugindesc Gamepad configuration base @author DarkPlasma @license MIT @help English Help Translator: munokura This is an unofficial English translation of the plugin help, created to support global RPG Maker users. Feedback is welcome to improve translation quality (see: https://github.com/munokura/DarkPlasma-MZ-Plugins ). Original plugin by DarkPlasma. Please check the latest official version at: https://github.com/elleonard/DarkPlasma-MZ-Plugins/tree/release ----- version: 1.0.0 Provides gamepad settings as an option. Enter \GAMEPAD[control name] in text to convert it to a control button notation. Control Name List ok: Confirm cancel: Cancel menu: Menu pageup: Toggle left pagedown: Toggle right shift: Special control 1 special2: Special control 2 Example: \GAMEPAD[special2] You can obtain the control button notation using the Window_Base.prototype.getManualButtonName method. To further customize gamepad settings, override the following methods in an additional plugin. Input.inputSymbols(): InputSymbol Input.inputBehaviorKeys(): string[] Input.inputBehaviorKeyName(key: string): string If using with the following plugins, add them below them. PluginCommonBase @param keyMapper @text Key Mapping @desc Sets the keyboard mapping. @type struct[] @default ["{\"keyCode\":\"77\",\"action\":\"menu\"}","{\"keyCode\":\"83\",\"action\":\"special2\"}"] @param gamepadMapper @text Gamepad Mapping @desc Sets the mapping for gamepad operations. @type struct[] @default ["{\"keyCode\":\"6\",\"action\":\"special2\"}"] @param colsWidth @text Item width @type number @default 124 */ /*:ja @plugindesc ゲームパッド設定の基底 @author DarkPlasma @license MIT @target MZ @url https://github.com/elleonard/DarkPlasma-MZ-Plugins/tree/release @orderAfter PluginCommonBase @param keyMapper @desc キーボード操作のマッピングを設定します。 @text キーマッピング @type struct[] @default ["{\"keyCode\":\"77\",\"action\":\"menu\"}","{\"keyCode\":\"83\",\"action\":\"special2\"}"] @param gamepadMapper @desc ゲームパッド操作のマッピングを設定します。 @text ゲームパッドマッピング @type struct[] @default ["{\"keyCode\":\"6\",\"action\":\"special2\"}"] @param colsWidth @text 項目幅 @type number @default 124 @help version: 1.0.0 オプションにゲームパッド設定を提供します。 テキスト中で \GAMEPAD[操作名] と入力すると 操作説明ボタン表記に変換されます。 操作名一覧 ok: 決定 cancel: キャンセル menu: メニュー pageup: 左切替 pagedown: 右切替 shift: 特殊操作1 special2: 特殊操作2 入力例: \GAMEPAD[special2] Window_Base.prototype.getManualButtonName メソッドにより 操作ボタン表記を取得できます。 ゲームパッド設定を更にカスタマイズする場合は、 追加プラグインで以下のメソッドを上書きしてください。 Input.inputSymbols(): InputSymbol Input.inputBehaviorKeys(): string[] Input.inputBehaviorKeyName(key: string): string 下記プラグインと共に利用する場合、それよりも下に追加してください。 PluginCommonBase */ (() => { 'use strict'; const pluginName = document.currentScript.src.replace(/^.*\/(.*).js$/, function () { return arguments[1]; }); const pluginParametersOf = (pluginName) => PluginManager.parameters(pluginName); const pluginParameters = pluginParametersOf(pluginName); const settings = { keyMapper: JSON.parse( pluginParameters.keyMapper || '[{"keyCode":"77","action":"menu"},{"keyCode":"83","action":"special2"}]' ).map((e) => { return ((parameter) => { const parsed = JSON.parse(parameter); return { keyCode: Number(parsed.keyCode || 0), action: String(parsed.action || ``), }; })(e || '{}'); }), gamepadMapper: JSON.parse(pluginParameters.gamepadMapper || '[{"keyCode":"6","action":"special2"}]').map((e) => { return ((parameter) => { const parsed = JSON.parse(parameter); return { keyCode: Number(parsed.keyCode || 0), action: String(parsed.action || ``), }; })(e || '{}'); }), colsWidth: Number(pluginParameters.colsWidth || 124), }; settings.keyMapper.forEach((mapping) => { Input.keyMapper[mapping.keyCode] = mapping.action; }); settings.gamepadMapper.forEach((mapping) => { Input.gamepadMapper[mapping.keyCode] = mapping.action; }); let gamepadMapperTemporary = {}; const BUTTON_NAME_TYPE = { KEYBOARD: 0, DEFAULT: 1, DUALSHOCK4: 2, XBOX: 3, }; const BUTTON_NAME_TYPE_TEXT = ['キーボード', 'ボタン番号', 'DS4', 'XBOX']; const BUTTON_NAME_DS4 = { 0: '×', 1: '○', 2: '□', 3: '△', 4: 'L1', 5: 'R1', 6: 'L2', 7: 'R2', 8: 'share', 9: 'options', 10: 'L3', 11: 'R3', 16: 'PS', 17: 'touch pad', }; const BUTTON_NAME_XBOX = { 0: 'B', 1: 'A', 2: 'X', 3: 'Y', 4: 'LB', 5: 'RB', 6: 'LT', 7: 'RT', 8: 'view', 9: 'menu', 10: 'LS', 11: 'RS', 16: 'GUIDE', 17: '-', }; class InputSymbol { constructor(name, text, behavior, keyname) { this._name = name; this._text = text; this._behavior = behavior; this._keyName = keyname; } get name() { return this._name; } get symbolText() { return this._text; } buttonId() { const mapper = gamepadMapperTemporary[12] ? gamepadMapperTemporary : Input.gamepadMapper; return Number(Object.keys(mapper).find((buttonId) => mapper[Number(buttonId)] === this._name)); } buttonName(buttonNameType) { switch (buttonNameType) { case BUTTON_NAME_TYPE.KEYBOARD: return this._keyName; case BUTTON_NAME_TYPE.DEFAULT: return `Button${this.buttonId() + 1}`; case BUTTON_NAME_TYPE.DUALSHOCK4: return BUTTON_NAME_DS4[this.buttonId()] || ''; case BUTTON_NAME_TYPE.XBOX: return BUTTON_NAME_XBOX[this.buttonId()] || ''; default: return ''; } } behavior(behaviorKey) { return this._behavior.get(behaviorKey) || ''; } } globalThis.InputSymbol = InputSymbol; function Input_GamepadMixIn() { Input.triggeredGamepadButtonId = function () { const gamepads = navigator.getGamepads(); if (gamepads) { const state = this._gamepadStates.find((state) => state.some((button) => button)); return state ? state.findIndex((button) => button) : null; } else { return null; } }; Input.createInputSymbol = function (name, text, behavior, key) { return new InputSymbol(name, text, behavior, key); }; Input.inputSymbols = function () { return DEFAULT_INPUT_SYMBOLS; }; Input.inputBehaviorKeys = function () { return ['menu', 'map', 'battle']; }; Input.inputBehaviorKeyName = function (key) { switch (key) { case 'map': return 'マップ'; case 'menu': return 'メニュー'; case 'battle': return '戦闘'; } return ''; }; } Input_GamepadMixIn(); const DEFAULT_INPUT_SYMBOLS = [ Input.createInputSymbol( 'ok', '決定', new Map([ ['map', '決定'], ['menu', '決定'], ['battle', '決定'], ]), 'Z/Enter' ), Input.createInputSymbol( 'cancel', 'キャンセル', new Map([ ['map', 'メニュー'], ['menu', 'キャンセル'], ['battle', 'キャンセル'], ]), 'X' ), Input.createInputSymbol( 'menu', 'メニュー', new Map([ ['map', 'メニュー'], ['menu', '-'], ['battle', '-'], ]), 'M' ), Input.createInputSymbol( 'pageup', '左切替', new Map([ ['map', '-'], ['menu', 'アクター切替'], ['battle', '-'], ]), 'Q/PageUp' ), Input.createInputSymbol( 'pagedown', '右切替', new Map([ ['map', '-'], ['menu', 'アクター切替'], ['battle', '-'], ]), 'W/PageDown' ), Input.createInputSymbol( 'shift', '特殊操作1', new Map([ ['map', 'ダッシュ'], ['menu', '-'], ['battle', '-'], ]), 'Shift' ), Input.createInputSymbol( 'special2', '特殊操作2', new Map([ ['map', '-'], ['menu', '-'], ['battle', '-'], ]), 'S' ), ]; function ConfigManager_GamepadMixIn() { ConfigManager.buttonNameType = BUTTON_NAME_TYPE.DEFAULT; ConfigManager.manualButtonType = BUTTON_NAME_TYPE.KEYBOARD; const _makeData = ConfigManager.makeData; ConfigManager.makeData = function () { const result = _makeData.call(this); result.gamepadConfig = Input.gamepadMapper; result.buttonNameType = this.buttonNameType; result.manualButtonType = this.manualButtonType; return result; }; const _applyData = ConfigManager.applyData; ConfigManager.applyData = function (config) { _applyData.call(this, config); this.gamepadConfig = config.gamepadConfig ?? Input.gamepadMapper; Object.entries(this.gamepadConfig).forEach(([key, value]) => { Input.gamepadMapper[Number(key)] = value; }); this.buttonNameType = config.buttonNameType || BUTTON_NAME_TYPE.DEFAULT; this.manualButtonType = config.manualButtonType || BUTTON_NAME_TYPE.KEYBOARD; }; } ConfigManager_GamepadMixIn(); /** * PluginCommonBaseで、metaテキストの読み込み時に行う変換ロジックは * Window_Baseからコピーされたコードになっている。 * 用語集に反映するためにはここで書き換える必要があるため、上書きする */ if (PluginManagerEx) { function PluginManagerEx_GamepadMixIn() { const _convertEscapeCharacters = PluginManagerEx.convertEscapeCharacters; PluginManagerEx.convertEscapeCharacters = function (text, data = null) { text = convertGamepadEscapeCharacter(text); return _convertEscapeCharacters.call(this, text, data); }; } PluginManagerEx_GamepadMixIn(); } class Scene_GamepadConfig extends Scene_MenuBase { constructor() { super(...arguments); this._configWindow = new Window_GamepadConfig(this.configWindowRect()); this._changeButtonMode = false; this._changeButtonModeWait = 0; this._commitWait = 0; } create() { super.create(); this.initializeTemporary(); this.setupConfigWindow(); } initializeTemporary() { Object.entries(Input.gamepadMapper).forEach(([key, value]) => { gamepadMapperTemporary[Number(key)] = value; }); } setupConfigWindow() { this._configWindow.setHandler('ok', this.onConfigOk.bind(this)); this._configWindow.setHandler('cancel', this.popScene.bind(this)); this.addWindow(this._configWindow); } configWindowRect() { return new Rectangle(0, 0, Graphics.boxWidth, Graphics.boxHeight); } onConfigOk() { const inputSymbols = Input.inputSymbols(); switch (this._configWindow.index()) { case inputSymbols.length: this.toggleButtonNameType(); this._configWindow.activate(); break; case inputSymbols.length + 1: this.commitGamepadMapper(); this._commitWait = 30; Input.clear(); this._configWindow.setCommitWaitMode(true); this._configWindow.activate(); break; case inputSymbols.length + 2: this.popScene(); break; default: this._changeButtonMode = true; this._configWindow.setChangeButtonMode(true); this._changeButtonModeWait = 30; break; } } toggleButtonNameType() { ConfigManager.buttonNameType++; if (ConfigManager.buttonNameType > BUTTON_NAME_TYPE.XBOX) { ConfigManager.buttonNameType = BUTTON_NAME_TYPE.DEFAULT; } this._configWindow.refresh(); } commitGamepadMapper() { Object.entries(gamepadMapperTemporary).forEach(([key, value]) => { Input.gamepadMapper[Number(key)] = value; }); } setGamepadMapping(buttonId) { const beforeValue = gamepadMapperTemporary[buttonId]; const beforeId = this._configWindow.currentItem()?.buttonId(); if (beforeValue && beforeId !== undefined) { gamepadMapperTemporary[beforeId] = beforeValue; } gamepadMapperTemporary[buttonId] = this._configWindow.currentItem().name; this._configWindow.refresh(); } update() { super.update(); if (this._changeButtonMode) { if (this._changeButtonModeWait <= 0) { const triggeredButtonId = Input.triggeredGamepadButtonId(); if (triggeredButtonId !== null && ![12, 13, 14, 15].includes(triggeredButtonId)) { this.setGamepadMapping(triggeredButtonId); this._changeButtonMode = false; this._changeButtonModeWait = 0; this._configWindow.setChangeButtonMode(false); this._configWindow.activate(); } } else { this._changeButtonModeWait--; } } if (this._commitWait > 0) { this._commitWait--; if (this._commitWait <= 0) { this._configWindow.setCommitWaitMode(false); } } } } function Scene_Options_GamepadMixIn(sceneOptions) { const _maxCommands = sceneOptions.maxCommands; sceneOptions.maxCommands = function () { return _maxCommands.call(this) + 2; }; } Scene_Options_GamepadMixIn(Scene_Options.prototype); class Window_GamepadConfig extends Window_Selectable { constructor() { super(...arguments); this._changeButtonMode = false; this._commitWaitMode = false; } initialize(rect) { super.initialize(rect); this.refresh(); this.select(0); this.activate(); } setChangeButtonMode(mode) { if (this._changeButtonMode !== mode) { this._changeButtonMode = mode; this.refresh(); } } setCommitWaitMode(mode) { if (this._commitWaitMode !== mode) { this._commitWaitMode = mode; this.refresh(); } } drawHeader() { const HEADER_TEXTS = ['操作', 'ボタン'].concat( Input.inputBehaviorKeys().map((key) => Input.inputBehaviorKeyName(key)) ); HEADER_TEXTS.forEach((text, index) => { this.drawText(text, index * (this.colsWidth() + this.colsPadding()), 0, this.colsWidth(), 'center'); }); } drawItem(index) { const rect = this.itemRect(index); const inputSymbols = Input.inputSymbols(); if (index < inputSymbols.length) { this.drawText(inputSymbols[index].symbolText, rect.x, rect.y, this.colsWidth(), 'center'); this.drawText( inputSymbols[index].buttonName(ConfigManager.buttonNameType), rect.x + this.colsWidth() + this.colsPadding(), rect.y, this.colsWidth(), 'center' ); Input.inputBehaviorKeys().forEach((key, i) => { this.drawText( inputSymbols[index].behavior(key), rect.x + (this.colsWidth() + this.colsPadding()) * (2 + i), rect.y, this.colsWidth(), 'center' ); }); } else { if (index === inputSymbols.length) { this.drawText('ボタン表示', rect.x, rect.y, this.colsWidth(), 'left'); this.drawText( BUTTON_NAME_TYPE_TEXT[ConfigManager.buttonNameType], rect.x + this.colsWidth(), rect.y, this.colsWidth() * 4, 'center' ); } else if (index === inputSymbols.length + 1) { this.drawText('設定を保存', rect.x, rect.y, this.colsWidth(), 'left'); } else if (index === inputSymbols.length + 2) { this.drawText('戻る', rect.x, rect.y, this.colsWidth(), 'left'); } } } drawFooter() { this.changeTextColor(ColorManager.textColor(6)); const y = this.lineHeight() * (this.maxItems() + 3); if (this._changeButtonMode) { this.drawText('設定したいボタンを押してください', 0, y, Graphics.boxWidth, 'center'); } if (this._commitWaitMode) { this.drawText('設定を保存しました', 0, y, Graphics.boxWidth, 'center'); } this.resetTextColor(); } item(index) { return index < Input.inputSymbols().length ? Input.inputSymbols()[index] : null; } currentItem() { return this.item(this.index()); } maxItems() { return Input.inputSymbols().length + 3; } itemRect(index) { const result = super.itemRect(index); result.y += result.height; return result; } colsWidth() { return settings.colsWidth; } colsPadding() { return 5; } refresh() { super.refresh(); this.drawHeader(); this.drawFooter(); } } function Window_Base_GamepadMixIn(windowClass) { windowClass.getManualButtonName = function (symbol) { return ( Input.inputSymbols() .find((inputSymbol) => inputSymbol.name === symbol) ?.buttonName(ConfigManager.manualButtonType) ?? '' ); }; const _convertEscapeCharacters = windowClass.convertEscapeCharacters; windowClass.convertEscapeCharacters = function (text) { /** * \G がかぶるため、先に変換しておく */ text = convertGamepadEscapeCharacter(text); text = _convertEscapeCharacters.call(this, text); return text; }; } Window_Base_GamepadMixIn(Window_Base.prototype); function Window_Options_GamepadMixIn(windowClass) { const gamepadSymbol = 'gamepad'; const manualSymbol = 'manualButtonType'; const _makeCommandList = windowClass.makeCommandList; windowClass.makeCommandList = function () { _makeCommandList.call(this); this.addCommand('操作説明表示', manualSymbol); this.addCommand('ゲームパッド', gamepadSymbol); }; const _statusText = windowClass.statusText; windowClass.statusText = function (index) { switch (this.commandSymbol(index)) { case gamepadSymbol: return ''; case manualSymbol: return this.manualStatusText(this.getConfigValue(this.commandSymbol(index))); } return _statusText.call(this, index); }; windowClass.manualStatusText = function (value) { return BUTTON_NAME_TYPE_TEXT[value]; }; const _processOk = windowClass.processOk; windowClass.processOk = function () { switch (this.commandSymbol(this.index())) { case gamepadSymbol: this.playOkSound(); SceneManager.push(Scene_GamepadConfig); return; case manualSymbol: this.changeManualButtonType(true); return; } _processOk.call(this); }; const _cursorRight = windowClass.cursorRight; windowClass.cursorRight = function () { switch (this.commandSymbol(this.index())) { case gamepadSymbol: return; case manualSymbol: this.changeManualButtonType(true); return; } _cursorRight.call(this); }; const _cursorLeft = windowClass.cursorLeft; windowClass.cursorLeft = function () { switch (this.commandSymbol(this.index())) { case gamepadSymbol: return; case manualSymbol: this.changeManualButtonType(false); return; } _cursorLeft.call(this); }; windowClass.changeManualButtonType = function (forward) { const value = this.getConfigValue(manualSymbol) + (forward ? 1 : -1); if (value >= BUTTON_NAME_TYPE_TEXT.length) { this.changeValue(manualSymbol, 0); } else if (value < 0) { this.changeValue(manualSymbol, BUTTON_NAME_TYPE_TEXT.length - 1); } else { this.changeValue(manualSymbol, value); } }; } Window_Options_GamepadMixIn(Window_Options.prototype); /** * PluginCommonBaseでも利用するため、変換ロジックをWindow_Baseの外に切り出す */ function convertGamepadEscapeCharacter(text) { text = text.replace(/\\/g, '\x1b'); text = text.replace(/\x1b\x1b/g, '\\'); text = text.replace(/\x1bGAMEPAD\[(.+)\]/gi, (_, symbol) => Window_Base.prototype.getManualButtonName(symbol)); return text; } })();