複数の敵ターゲットを指定するスキル【プラグイン】

複数の敵ターゲットを指定するスキルを指定できるプラグインを作成しました。

Oct 11, 2024

はじめに

RPGツクールMVで動作するプラグインです。
複数の敵ターゲットを指定するスキルを指定できます。
ターゲットの選択方法として下記がしていできます。
  • 任意の敵を指定する
  • カーソルの形をスキル毎に指定しそのカーソルで選択する
 
※自分用に作成しているため、他の環境だとうまく動かなさそうです…!
 

ターゲットの選択方法

 
複数の敵を攻撃するスキルを設定できます。
 

任意の敵をターゲットに指定する

 
下記は任意の敵をターゲットに指定して、攻撃できるスキルです。
赤いチェックマークが付いた敵を攻撃します。
 
ターゲットを選択する
ターゲットを選択する
 

カーソルの形をスキル毎に設定して、ターゲットを指定する

 
下記はB,E,F,G,Jの5体の敵に対して攻撃します。
 
十文字斬り
十文字斬り
 

プラグイン本体

 
ふたつのプラグインを使って実現している。
 

TT_MultiTargetSkill.js

 
//============================================================================= // TT_MultiTargetSkill.js // ---------------------------------------------------------------------------- // Copyright (c) 2024 たくろう_りらっくすーぷ // This software is released under the MIT License. // http://opensource.org/licenses/mit-license.php // ---------------------------------------------------------------------------- // Last Updated: 2024/10/11 // // Version // 1.0.0 2024/10/11 初版 // ---------------------------------------------------------------------------- // [Blog] : https://re-lacksoup-recipenote.bullet.site/ // [X(Twitter)]: https://x.com/relacksoup //============================================================================= /*:ja * @plugindesc バトルの敵選択で複数ターゲットを選択できるようにする(Version 1.0.0) * @author たくろう_りらっくすーぷ * * @param SkillArrayVariableId * @text スキル配列格納変数ID * @desc MultiTargetALLで指定された配列を格納する変数のID * @type variable * @default 1 * * @param TargetArrayVariableId * @text ターゲット配列格納変数ID * @desc MultiTargetALLで攻撃したアクターとターゲットの配列を格納する変数のID * @type variable * @default 2 * * @param MultipleTargetsCommonEventId * @text 複数攻撃時のコモンイベントID * @desc 複数攻撃時に実行するコモンイベントのIDを指定します。 * @type common_event * @default 1 * * @help バトルの敵選択で複数ターゲットを選択できるようにします。 * このプラグインは、RPGツクールMVのバトルシステムに * 複数ターゲットを選択できるスキルを追加します。 * * バトルのターゲット選択画面で指定したターゲット数を選択すると、 * その選択したターゲットに対して、指定したスキルが発動します。 * * 多くの敵に一気に攻撃するスキルなどに使用する想定です。 * またCursorの形を変更することで、敵の生存状況によってスキルが使えたり使えなかったりするようなスキルにも使用できます(TT_MultiTargetCursor.jsが必要)。 * * * ◆注意 *  他プラグインとの競合などは検証できていません。 *  かなり自分の環境に依存したオレオレプラグインです。 *  このプラグインは、TT_MultiTargetCursor.jsと合わせて使用が前提となっています。 *  TT_MultiTargetSkill を TT_MultiTargetCursor より下に配置してください。 * * このプラグインは、複数ターゲットを選択して指定した変数に必要な情報を格納します。 * またその後、指定したコモンイベントを実行します。 * 指定した複数ターゲットに対して実行する内容については、コモンイベントで設定してください。 * * ◆使用方法: * 1. このプラグインをプロジェクトに追加し、有効にしてください。 * 2. プラグインパラメータで、スキル配列格納変数ID、ターゲット配列格納変数ID、複数攻撃時のコモンイベントIDを設定してください。 * 3. 対象スキルのメモ欄に設定したいタグ <MultiTarget:X> or <MultiTargetALL:X,[skillID,skillID,skillID]> を記述してください。 * * ▼<MultiTarget:X>について * スキルのメモ欄に以下のように記述してください。 * <MultiTarget:3><MultiTargetCursor:A> * 3の部分は選択が必要なターゲット数です。 * Aの部分は、ターゲット選択時に表示するカーソルの位置です。 * 基本的には、Aを指定する想定です。 * * バトルのターゲット選択画面で、必要なターゲット数を選択すると、 * その最後に選択したターゲットに対してスキルが発動します。 * * 敵が多くないと発動しないスキルなどに使用する想定です。 * * * ▼<MultiTargetALL:X,[skillID,skillID,skillID]>について * スキルのメモ欄に以下のように記述してください。 * <MultiTargetALL:5,[16,16,16,16,23]><MultiTargetCursor:A> * 5の部分は選択が必要なターゲット数です。 * [16,16,16,16,23]の部分は、選択したターゲットに対して発動するスキルIDです。 * スキルの数は、選択が必要なターゲット数と同じにしてください。 * * Aの部分は、ターゲット選択時に表示するカーソルの位置です(TT_MultiTargetCursor.jsが必要) * 基本的には、Aもしくは選択が必要なターゲット数と同じ文字数を指定してください。 * * ◆プラグインパラメーター *  スキル配列格納変数ID *    スキル配列を格納する変数のID *    デフォルト:1 * *  ターゲット配列格納変数ID *    ターゲット配列を格納する変数のID *    デフォルト:2 * *  複数攻撃時のコモンイベントID *    複数攻撃時に実行するコモンイベントのID *    デフォルト:1 * * ◆プラグインコマンド *  今のところありません * * ◆注意(再掲) *  他プラグインとの競合などは検証できていません。 *  かなり自分の環境に依存したオレオレプラグインです。 *  このプラグインは、TT_MultiTargetCursor.jsと合わせて使用が前提です。 *  TT_MultiTargetSkill を TT_MultiTargetCursor より下に配置してください。 * */ (function() { 'use strict'; const parameters = PluginManager.parameters('TT_MultiTargetSkill'); const skillArrayVariableId = Number(parameters['SkillArrayVariableId'] || 1); const targetArrayVariableId = Number(parameters['TargetArrayVariableId'] || 2); const multipleTargetsCommonEventId = Number(parameters['MultipleTargetsCommonEventId'] || 1); //============================================================================= // Game_Action //============================================================================= Game_Action.prototype.getMultiTargetInfo = function() { const notedata = this.item().note.match(/<MultiTargetALL:(\d+),\[([\d,]+)\]>/i); if (notedata) { return { maxTargets: parseInt(notedata[1]), array: JSON.parse('[' + notedata[2] + ']') }; } return null; }; Game_Action.prototype.isMultiTargetAll = function() { return !!this.getMultiTargetInfo(); }; Game_Action.prototype.maxTargetsAll = function() { if (this.isMultiTargetAll()) { return this.getMultiTargetInfo().maxTargets; } return 1; }; Game_Action.prototype.isMultiTarget = function() { return !!this.item().meta.MultiTarget; }; Game_Action.prototype.maxTargets = function() { const info = this.getMultiTargetInfo(); if (info) { return info.maxTargets; } else if (this.isMultiTarget()) { return parseInt(this.item().meta.MultiTarget) || 1; } return 1; }; const _Game_Action_apply = Game_Action.prototype.apply; Game_Action.prototype.apply = function(target) { if (this.isMultiTargetAll() && this.hasMultipleTargets()) { this.applyToMultipleTargets(); } else { _Game_Action_apply.call(this, target); } }; Game_Action.prototype.applyToMultipleTargets = function() { const targets = this._multipleTargets; const targetArray = [this.subject().actorId(), targets]; $gameVariables.setValue(targetArrayVariableId, targetArray); $gameTemp.reserveCommonEvent(multipleTargetsCommonEventId); }; Game_Action.prototype.setMultipleTargets = function(targets) { this._multipleTargets = targets; }; Game_Action.prototype.hasMultipleTargets = function() { return this._multipleTargets && this._multipleTargets.length > 0; }; //============================================================================= // Window_BattleActor //============================================================================= const _Window_BattleActor_initialize = Window_BattleActor.prototype.initialize; Window_BattleActor.prototype.initialize = function(x, y) { _Window_BattleActor_initialize.call(this, x, y); this._multipleSelect = false; this._selectedIndexes = []; this._maxTargets = 1; }; Window_BattleActor.prototype.setMultipleSelect = function(multiple, maxTargets) { this._multipleSelect = multiple; this._selectedIndexes = []; this._maxTargets = maxTargets || 1; }; Window_BattleActor.prototype.processOk = function() { if (this._multipleSelect) { this.processMultipleSelection(); } else { Window_Selectable.prototype.processOk.call(this); } }; Window_BattleActor.prototype.processMultipleSelection = function() { const index = this.index(); if (this._selectedIndexes.includes(index)) { this._selectedIndexes = this._selectedIndexes.filter(i => i !== index); } else if (this._selectedIndexes.length < this._maxTargets) { this._selectedIndexes.push(index); } this.redrawItem(index); SoundManager.playCursor(); if (this._selectedIndexes.length === this._maxTargets) { this.deactivate(); this.callOkHandler(); } }; const _Window_BattleActor_drawItem = Window_BattleActor.prototype.drawItem; Window_BattleActor.prototype.drawItem = function(index) { _Window_BattleActor_drawItem.call(this, index); if (this._multipleSelect && this._selectedIndexes.includes(index)) { const rect = this.itemRect(index); this.drawText('✓', rect.x, rect.y, rect.width, 'right'); } }; //============================================================================= // Window_BattleEnemy //============================================================================= const _Window_BattleEnemy_initialize = Window_BattleEnemy.prototype.initialize; Window_BattleEnemy.prototype.initialize = function(x, y) { _Window_BattleEnemy_initialize.call(this, x, y); this._multipleSelect = false; this._selectedIndexes = []; this._maxTargets = 1; }; Window_BattleEnemy.prototype.resetSelection = function() { this._selectedIndexes = []; this.refresh(); }; const _Window_BattleEnemy_show = Window_BattleEnemy.prototype.show; Window_BattleEnemy.prototype.show = function() { _Window_BattleEnemy_show.call(this); this.resetSelection(); }; const _Window_BattleEnemy_hide = Window_BattleEnemy.prototype.hide; Window_BattleEnemy.prototype.hide = function() { _Window_BattleEnemy_hide.call(this); this.resetSelection(); }; Window_BattleEnemy.prototype.setMultipleSelect = function(multiple, maxTargets) { this._multipleSelect = multiple; this._selectedIndexes = []; this._maxTargets = maxTargets || 1; }; // TT_MultiTargetCursor.jsとの競合を避けるため、コメントアウト // TT_MultiTargetCursor.jsを使用しない場合は、コメントアウトを解除したら、これだけで動作するかも知れない(未検証) // Window_BattleEnemy.prototype.processOk = function() { // const action = BattleManager.inputtingAction(); // if (action.isMultiTarget() || action.isMultiTargetAll()) { // this.processMultipleSelection(); // } else { // Window_Selectable.prototype.processOk.call(this); // } // }; Window_BattleEnemy.prototype.processMultipleSelection = function() { const index = this.index(); const enemy = this._enemies[index]; if (enemy && enemy.isAlive()) { if (this._selectedIndexes.includes(index)) { this._selectedIndexes = this._selectedIndexes.filter(i => i !== index); } else if (this._selectedIndexes.length < this._maxTargets) { this._selectedIndexes.push(index); } this.redrawItem(index); SoundManager.playCursor(); if (this._selectedIndexes.length === this._maxTargets) { this.deactivate(); this.callOkHandler(); } } else { SoundManager.playBuzzer(); } }; Window_BattleEnemy.prototype.selectAll = function(maxTargets) { this._selectedIndexes = this._enemies .filter(enemy => enemy.isAlive()) .slice(0, maxTargets) .map((_, index) => index); this.refresh(); this.deactivate(); this.callOkHandler(); }; Window_BattleEnemy.prototype.selectSingle = function() { const enemy = this._enemies[this.index()]; if (enemy && enemy.isAlive()) { this._selectedIndexes = [this.index()]; Window_Selectable.prototype.processOk.call(this); } else { SoundManager.playBuzzer(); } }; // TT_MultiTargetCursor.jsとの競合を避けるため、コメントアウト // TT_MultiTargetCursor.jsを使用しない場合は、コメントアウトを解除したら、これだけで動作するかも知れない(未検証) // const _Window_BattleEnemy_drawItem = Window_BattleEnemy.prototype.drawItem; // Window_BattleEnemy.prototype.drawItem = function(index) { // _Window_BattleEnemy_drawItem.call(this, index); // if (this._multipleSelect && this._selectedIndexes.includes(index)) { // const rect = this.itemRectForText(index); // this.drawText('✓', rect.x, rect.y, rect.width, 'right'); // } // }; // Window_BattleEnemy.prototype.drawItem = function(index) { // var enemy = this._enemies[index]; // if (enemy) { // var name = this.enemyName(enemy); // var rect = this.itemRectForText(index); // var selected = this._selectedIndexes && this._selectedIndexes.includes(index); // this.resetTextColor(); // if (enemy.isAlive()) { // this.changePaintOpacity(true); // } else { // this.changePaintOpacity(false); // this.changeTextColor(this.deathColor()); // } // this.drawText(name, rect.x, rect.y, rect.width); // if (this._multipleSelect && selected && enemy.isAlive()) { // this.changePaintOpacity(true); // this.drawText('✓', rect.x, rect.y, rect.width, 'right'); // } // } // }; // 倒れた敵の文字色を定義する Window_BattleEnemy.prototype.deathColor = function() { return this.textColor(7); // 灰色 (デフォルトカラーパレットの7番目) }; // ウィンドウに表示する敵リストを作成する Window_BattleEnemy.prototype.refresh = function() { // 倒れた敵も含めてすべての敵を表示する this._enemies = $gameTroop.members(); Window_Selectable.prototype.refresh.call(this); }; // 敵の選択可能状態を判定する Window_BattleEnemy.prototype.isCurrentItemEnabled = function() { var enemy = this.enemy(); return enemy && enemy.isAlive(); }; // 選択可能な敵がいるかチェックするメソッドを追加 Window_BattleEnemy.prototype.hasAliveEnemy = function() { return this._enemies.some(function(enemy) { return enemy.isAlive(); }); }; //============================================================================= // Scene_Battle //============================================================================= const _Scene_Battle_onEnemyCancel = Scene_Battle.prototype.onEnemyCancel; Scene_Battle.prototype.onEnemyCancel = function() { _Scene_Battle_onEnemyCancel.call(this); this._enemyWindow.resetSelection(); }; const _Scene_Battle_onSkillCancel = Scene_Battle.prototype.onSkillCancel; Scene_Battle.prototype.onSkillCancel = function() { _Scene_Battle_onSkillCancel.call(this); this._enemyWindow.resetSelection(); }; const _Scene_Battle_onItemCancel = Scene_Battle.prototype.onItemCancel; Scene_Battle.prototype.onItemCancel = function() { _Scene_Battle_onItemCancel.call(this); this._enemyWindow.resetSelection(); }; const _Scene_Battle_selectActorSelection = Scene_Battle.prototype.selectActorSelection; Scene_Battle.prototype.selectActorSelection = function() { _Scene_Battle_selectActorSelection.call(this); const action = BattleManager.inputtingAction(); if (action && action.isMultiTarget()) { this._actorWindow.setMultipleSelect(true, action.maxTargets()); } else { this._actorWindow.setMultipleSelect(false, 1); } }; const _Scene_Battle_commandSkill = Scene_Battle.prototype.commandSkill; Scene_Battle.prototype.commandSkill = function() { this._enemyWindow.setMultipleSelect(false, 1); this._actorWindow.setMultipleSelect(false, 1); _Scene_Battle_commandSkill.call(this); }; const _Scene_Battle_commandItem = Scene_Battle.prototype.commandItem; Scene_Battle.prototype.commandItem = function() { this._enemyWindow.setMultipleSelect(false, 1); this._actorWindow.setMultipleSelect(false, 1); _Scene_Battle_commandItem.call(this); }; var _Scene_Battle_selectEnemySelection = Scene_Battle.prototype.selectEnemySelection; Scene_Battle.prototype.selectEnemySelection = function() { _Scene_Battle_selectEnemySelection.call(this); var action = BattleManager.inputtingAction(); if (action.isMultiTarget() || action.isMultiTargetAll()) { this._enemyWindow.setMultipleSelect(true, action.maxTargets()); } else { this._enemyWindow.setMultipleSelect(false, 1); } if (!this._enemyWindow.hasAliveEnemy()) { this.onEnemyCancel(); } }; Window_BattleEnemy.prototype.enemyName = function(enemy) { return enemy ? enemy.name() : ''; }; const _Scene_Battle_onActorOk = Scene_Battle.prototype.onActorOk; Scene_Battle.prototype.onActorOk = function() { const action = BattleManager.inputtingAction(); if (action && action.isMultiTarget()) { const targets = this._actorWindow._selectedIndexes.map(index => $gameParty.battleMembers()[index]); action.setTarget(targets); this._actorWindow.hide(); this._skillWindow.hide(); this._itemWindow.hide(); this.selectNextCommand(); } else { _Scene_Battle_onActorOk.call(this); } }; const _Scene_Battle_onEnemyOk = Scene_Battle.prototype.onEnemyOk; Scene_Battle.prototype.onEnemyOk = function() { const action = BattleManager.inputtingAction(); if (action.isMultiTargetAll()) { const info = action.getMultiTargetInfo(); if (info) { $gameVariables.setValue(skillArrayVariableId, info.array); action.setMultipleTargets(this._enemyWindow._selectedIndexes); action.setTarget(this._enemyWindow._selectedIndexes[0]); } } else if (action.isMultiTarget()) { action.setMultipleTargets(this._enemyWindow._selectedIndexes); action.setTarget(this._enemyWindow._selectedIndexes[this._enemyWindow._selectedIndexes.length - 1]); } else { action.setTarget(this._enemyWindow.enemyIndex()); } this._enemyWindow.hide(); this._skillWindow.hide(); this._itemWindow.hide(); this.selectNextCommand(); }; })();
 

TT_MultiTargetCursor.js

 
//============================================================================= // TT_MultiTargetCursor.js // ---------------------------------------------------------------------------- // Copyright (c) 2024 たくろう_りらっくすーぷ // This software is released under the MIT License. // http://opensource.org/licenses/mit-license.php // ---------------------------------------------------------------------------- // Last Updated: 2024/10/11 // // Version // 1.0.0 2024/10/11 初版 // ---------------------------------------------------------------------------- // [Blog] : https://re-lacksoup-recipenote.bullet.site/ // [X(Twitter)]: https://x.com/relacksoup //============================================================================= /*:ja * @plugindesc バトルの敵選択でカーソルの形や挙動を拡張します(Version 1.0.0) * @author たくろう_りらっくすーぷ * * @help バトルの敵選択でカーソルの形や挙動を拡張します。 * このプラグインは、RPGツクールMVのバトルシステムに * カーソルの形を拡張したスキルを追加します。 * * このプラグインは、カーソルの形を拡張し、複数のターゲットに対して選択処理を行います。 * またカーソルの形に合わせて、カーソルの移動やターゲット点滅処理なども制御します。 * TT_MultiTargetSkillによって、複数のターゲットの選択が前提となっています。 * * ◆注意 *  他プラグインとの競合などは検証できていません。 *  かなり自分の環境に依存したオレオレプラグインです。 *  このプラグインは、TT_MultiTargetSkill.jsと合わせて使用が前提となっています。 *  TT_MultiTargetSkill を TT_MultiTargetCursor より下に配置してください。 * * ◆使用方法: * 1. このプラグインをプロジェクトに追加し、有効にしてください。 * 2. 対象スキルのメモ欄に以下のように記述してください。 * * ▼<MultiTargetCursor:ABC>について * スキルのメモ欄に以下のように記述してください。 * <MultiTargetCursor:ABC> * ABCの部分は、ターゲット選択時に表示するカーソルの位置です。 * A~Zの文字列を指定してください。 * 下記の配置に対応した位置にカーソルが表示されます。 * 【カーソル対応位置】 *  A B C D *  E F G H *  I J K L *  M N O P *  Q R S T *  U V W X *  Y Z * * 例えば、<MultiTargetCursor:ABCFJ>と指定すると、 * カーソルがA、B、C、F、Jの位置に表示されます。 * 要するに、T型のカーソルが表示されます。 *  A B C *   F *   J * * 記載した並び順通りにターゲット処理が実行されます。 * ※実際の処理はTT_MultiTargetSkill.jsで指定したコモンイベントによって実行されます。 * * ◆プラグインパラメーター *  今のところありません * * ◆プラグインコマンド *  今のところありません * * ◆注意(再掲) *  他プラグインとの競合などは検証できていません。 *  かなり自分の環境に依存したオレオレプラグインです。 *  このプラグインは、TT_MultiTargetCursor.jsと合わせて使用が前提です。 *  TT_MultiTargetSkill を TT_MultiTargetCursor より下に配置してください。 * */ (function() { var windowProto = Window_BattleEnemy.prototype; var sceneProto = Scene_Battle.prototype; // 初期化処理の拡張 var _initialize = windowProto.initialize; windowProto.initialize = function(x, y) { _initialize.call(this, x, y); this._doubleWidth = false; this._selectedIndexes = []; this._maxTargets = 1; }; // カーソル設定の拡張 windowProto.setCursorAll = function(cursorAll) { if (this._doubleWidth !== cursorAll) { this._doubleWidth = cursorAll; this.refreshCursor(); } }; // 対象スキルのカーソルタイプを取得する windowProto.cursorType = function() { var action = BattleManager.inputtingAction(); return action.item().meta.MultiTargetCursor }; function check_double_width(){ // 選択中のスキルがマルチターゲットスキルかどうかをチェックする var action = BattleManager.inputtingAction(); return action && action.item() && action.item().meta.MultiTargetCursor; } function update_multi_target_cursor_path(cursor_type) { const mappings = { 'A': 'M 0 0 h 144 v 35 h -144 v -35 ', 'B': 'M 144 0 h 144 v 35 h -144 v -35 ', 'C': 'M 288 0 h 144 v 35 h -144 v -35 ', 'D': 'M 432 0 h 144 v 35 h -144 v -35 ', 'E': 'M 0 35 h 144 v 35 h -144 v -35 ', 'F': 'M 144 35 h 144 v 35 h -144 v -35 ', 'G': 'M 288 35 h 144 v 35 h -144 v -35 ', 'H': 'M 432 35 h 144 v 35 h -144 v -35 ', 'I': 'M 0 70 h 144 v 35 h -144 v -35 ', 'J': 'M 144 70 h 144 v 35 h -144 v -35 ', 'K': 'M 288 70 h 144 v 35 h -144 v -35 ', 'L': 'M 432 70 h 144 v 35 h -144 v -35 ', 'M': 'M 0 105 h 144 v 35 h -144 v -35 ', 'N': 'M 144 105 h 144 v 35 h -144 v -35 ', 'O': 'M 288 105 h 144 v 35 h -144 v -35 ', 'P': 'M 432 105 h 144 v 35 h -144 v -35 ', 'Q': 'M 0 140 h 144 v 35 h -144 v -35 ', 'R': 'M 144 140 h 144 v 35 h -144 v -35 ', 'S': 'M 288 140 h 144 v 35 h -144 v -35 ', 'T': 'M 432 140 h 144 v 35 h -144 v -35 ', 'U': 'M 0 175 h 144 v 35 h -144 v -35 ', 'V': 'M 144 175 h 144 v 35 h -144 v -35 ', 'W': 'M 288 175 h 144 v 35 h -144 v -35 ', 'X': 'M 432 175 h 144 v 35 h -144 v -35 ', 'Y': 'M 0 210 h 144 v 35 h -144 v -35 ', 'Z': 'M 144 210 h 144 v 35 h -144 v -35 ' }; let multi_target_cursor_path = ''; for (let char of cursor_type) { if (mappings.hasOwnProperty(char)) { multi_target_cursor_path += mappings[char]; } } return multi_target_cursor_path; } // カーソル更新の拡張 var _refreshCursor = windowProto.refreshCursor; windowProto.refreshCursor = function() { if (this._doubleWidth) { // ビットマップのサイズを設定 this._windowCursorSprite.bitmap = new Bitmap(600, 600); this._windowCursorSprite.bitmap.clear(); // カーソルタイプによってカーソルの形状を変更 var cursor_type = this.cursorType(); this._multiTargetCursorPath = update_multi_target_cursor_path(cursor_type); var path = new Path2D(this._multiTargetCursorPath); var ctx = this._windowCursorSprite.bitmap._context; ctx.save(); ctx.fillStyle = 'rgba(0, 255, 0, 0.2)'; ctx.fill(path); ctx.restore(); this._windowCursorSprite.bitmap._setDirty(); } else if (_refreshCursor) { _refreshCursor.call(this); } }; // 敵表示の行数を取得する windowProto.maxRows = function() { return Math.ceil(this.maxItems() / this.maxCols()); }; Sprite_Enemy.prototype.getTargetIndices = function(cursor_type, currentIndex, enemyCount) { var indices = []; for (var i = 0; i < cursor_type.length; i++) { var char = cursor_type[i]; var offset = char.charCodeAt(0) - 'A'.charCodeAt(0) + 1; indices.push((currentIndex + offset) % enemyCount); } return indices; }; // 白く点滅する機能 Sprite_Battler.prototype.updateSelectionEffect = function() { // もしスキルがマルチターゲットスキルだったら if (check_double_width()){ var target = this._effectTarget; if (this._battler.isSelected()) { var cursor_type = check_double_width(); // カーソルタイプを取得 BEGJFみたいな文字列が取得できる var troop = this._battler.friendsUnit(); var currentIndex = troop._enemies.indexOf(this._battler); // cursor_typeに基づいて点滅させる敵のインデックスを取得 var targetIndices = this.getTargetIndices(cursor_type, currentIndex, troop._enemies.length); // 点滅対象の敵スプライトを取得 var targetEnemySprites = targetIndices.map(function(index) { var enemy = troop._enemies[index-1]; return SceneManager._scene._spriteset.battlerSprites().find(function(sprite) { return sprite._battler === enemy; }); }).filter(Boolean); // nullや undefined を除去 if (this._battler.isSelected()) { this._selectionEffectCount++; if (this._selectionEffectCount % 30 < 15) { targetEnemySprites.forEach(function(sprite) { sprite.setBlendColor([255, 255, 255, 64]); }); } else { targetEnemySprites.forEach(function(sprite) { sprite.setBlendColor([0, 0, 0, 0]); }); } } } else { // 選択が解除されたとき if (this._selectionEffectCount > 0) { this._selectionEffectCount = 0; // すべての敵スプライトの色を通常に戻す SceneManager._scene._spriteset.battlerSprites().forEach(function(sprite) { if (sprite instanceof Sprite_Enemy) { sprite.setBlendColor([0, 0, 0, 0]); } }); } } return; } var target = this._effectTarget; if (this._battler.isSelected()) { this._selectionEffectCount++; if (this._selectionEffectCount % 30 < 15) { target.setBlendColor([255, 255, 255, 64]); } else { target.setBlendColor([0, 0, 0, 0]); } } else if (this._selectionEffectCount > 0) { this._selectionEffectCount = 0; target.setBlendColor([0, 0, 0, 0]); } }; var moveCursor = function(direction, wrap) { return function() { var index = this.index(); var maxItems = this.maxItems(); var maxCols = this.maxCols(); var newIndex = index; var cursor_type = this.cursorType(); var maxSelectableRows = 1; // デフォルトの行数 var maxSelectableCols = 1; // デフォルトの列数 // 最大選択行数を設定 if (/[EFGH]/.test(cursor_type)) { maxSelectableRows = 2; } if (/[IJKL]/.test(cursor_type)) { maxSelectableRows = 3; } if (/[MNOP]/.test(cursor_type)) { maxSelectableRows = 4; } if (/[QRST]/.test(cursor_type)) { maxSelectableRows = 5; } if (/[UVWX]/.test(cursor_type)) { maxSelectableRows = 6; } if (/[YZ]/.test(cursor_type)) { maxSelectableRows = 7; } // 最大選択列数を設定 if (/[BFJNRVZ]/.test(cursor_type)) { maxSelectableCols = 2; } if (/[CGKOSW]/.test(cursor_type)) { maxSelectableCols = 3; } if (/[DHLPTX]/.test(cursor_type)) { maxSelectableCols = 4; } switch (direction) { case 'down': var maxRows = this.maxRows(); var currentRow = Math.floor(index / maxCols); // カーソルの行数が最大選択行数を超えないかチェック if (currentRow < maxRows - maxSelectableRows) { newIndex = Math.min(index + maxCols, maxItems - 1); } break; case 'up': var currentRow = Math.floor(index / maxCols); if (currentRow > 0) { newIndex = index - maxCols; } break; case 'right': // カーソルの列数が最大選択列数を超えないかチェック if (index % maxCols < maxCols - maxSelectableCols && index < maxItems - maxSelectableCols) { newIndex = index + 1; } break; case 'left': if (index % maxCols > 0) { newIndex = index - 1; } break; } if (newIndex !== index) { this.select(newIndex); this.refreshCursor(); } }; }; windowProto.cursorDown = moveCursor('down', false); windowProto.cursorUp = moveCursor('up', false); windowProto.cursorRight = moveCursor('right', false); windowProto.cursorLeft = moveCursor('left', false); // 選択処理の最適化 windowProto.processOk = function() { if (!this._multipleSelect) { return Window_Selectable.prototype.processOk.call(this); } var index = this.index(); var cursor_type = this.cursorType(); var selectedIndexes = []; for (let char of cursor_type) { if (char >= 'A' && char <= 'Z') { selectedIndexes.push(index + (char.charCodeAt(0) - 'A'.charCodeAt(0))); } } if (!selectedIndexes.every(function(idx) { return this._enemies[idx] && this._enemies[idx].isAlive(); }, this)) { return SoundManager.playBuzzer(); } var allSelected = selectedIndexes.every(function(idx) { return this._selectedIndexes.indexOf(idx) !== -1; }, this); var noneSelected = selectedIndexes.every(function(idx) { return this._selectedIndexes.indexOf(idx) === -1; }, this); // 新しい選択が最大選択数を超えないかチェック var potentialNewSelections = selectedIndexes.filter(function(idx) { return this._selectedIndexes.indexOf(idx) === -1; }, this); if (noneSelected) { // 全て選択されていない場合、maxTargetsを超えない範囲で選択 if (this._selectedIndexes.length + potentialNewSelections.length <= this._maxTargets) { this._selectedIndexes = this._selectedIndexes.concat(potentialNewSelections); } else { SoundManager.playBuzzer(); // 選択できない場合はブザー音を鳴らす return; } } else if (allSelected) { // 全て選択されている場合は解除 this._selectedIndexes = this._selectedIndexes.filter(function(idx) { return selectedIndexes.indexOf(idx) === -1; }); } else { // 一部選択されている場合 var newSelectedIndexes = this._selectedIndexes.slice(); selectedIndexes.forEach(function(idx) { var indexInSelected = newSelectedIndexes.indexOf(idx); console.log("indexInSelected: " + indexInSelected); console.log("idx: " + idx); console.log("newSelectedIndexes: " + newSelectedIndexes); console.log("this._maxTargets: " + this._maxTargets); if (indexInSelected > -1) { // 選択されているものは解除 newSelectedIndexes.splice(indexInSelected, 1); } else { // 選択されていないものは、maxTargetsを超えない範囲で選択 if (newSelectedIndexes.length < this._maxTargets) { newSelectedIndexes.push(idx); console.log("====================================="); console.log("indexInSelected: " + indexInSelected); console.log("idx: " + idx); console.log("newSelectedIndexes: " + newSelectedIndexes); console.log("this._maxTargets: " + this._maxTargets); } } }, this); // 新しい選択が最大選択数を超えていないか確認 if (newSelectedIndexes.length <= this._maxTargets) { this._selectedIndexes = newSelectedIndexes; } else { SoundManager.playBuzzer(); // 選択できない場合はブザー音を鳴らす return; } } selectedIndexes.forEach(function(idx) { this.redrawItem(idx); }, this); SoundManager.playCursor(); if (this._selectedIndexes.length === this._maxTargets) { this.deactivate(); this.callOkHandler(); } }; // アイテム描画の最適化 windowProto.drawItem = function(index) { var enemy = this._enemies[index]; var rect = this.itemRectForText(index); var selected = this._selectedIndexes && this._selectedIndexes.indexOf(index) !== -1; this.resetTextColor(); this.changePaintOpacity(enemy.isAlive()); this.drawText(enemy.name(), rect.x, rect.y, rect.width); if ((this._multipleSelect && selected)) { this.changeTextColor(this.textColor(2)); // 赤色 this.drawText('✓', rect.x, rect.y, rect.width, 'center'); this.resetTextColor(); // 色をリセット } }; // シーンの拡張 var _selectEnemySelection = sceneProto.selectEnemySelection; sceneProto.selectEnemySelection = function() { _selectEnemySelection.call(this); var action = BattleManager.inputtingAction(); var isMultiTarget = action && action.item() && action.item().meta.MultiTargetCursor; this._enemyWindow.setCursorAll(isMultiTarget); this._enemyWindow._maxTargets = isMultiTarget ? action.maxTargets() : 1; }; var _onEnemyOk = sceneProto.onEnemyOk; sceneProto.onEnemyOk = function() { var action = BattleManager.inputtingAction(); if (action && action.item() && action.item().meta.MultiTargetCursor) { action.setMultipleTargets(this._enemyWindow._selectedIndexes); } _onEnemyOk.call(this); }; })();