RPGツクールMVのWindow_Selectableクラスを使うと、項目を選択可能なウィンドウを作ることができます。
この記事ではサンプルとしてアクターをお金で雇えるプラグインEmployActor.jsを作りつつ、Window_Selectableの主要なメソッドを紹介します。
このブログの記事は厳密な正確さよりも、わかりやすさを重視して書いてあるので、あらかじめご了承ください。
EmployActor.jsの仕様をザックリまとめておきます。
- アクターのメモ欄に<EASalary:>タグで賃金を設定する
- プラグインコマンドopenでアクターの一覧、所持金ウィンドウを開く
- 雇いたいアクターにカーソルをあわせて決定ボタンを押すと「雇う」コマンドを表示する
- 雇うコマンドで決定ボタンを押すと、所持金が減って選択したアクターが仲間に加わる
- お金が足りない場合とパーティ人数がいっぱいの場合は、アクターの一覧をグレーアウトする
完成イメージとしては、こんな感じ。
必要なものはシーン、一覧ウィンドウ、所持金ウィンドウ、コマンドウィンドウの4つです。
シーンはScene_MenuBaseを元にしてScene_EmployActorを作っていきます。
所持金を表示するウィンドウはWindow_Goldというクラスがあるので、それをそのまま使います。
アクターの一覧をWindow_Selectableを元にしたWindow_EmployActorIndexクラス、コマンドウィンドウをWindow_Commandを元にしたWindow_EmployActorCommandクラスで作ります。
シーン・ウィンドウ | クラス名 |
---|---|
シーン | Scene_EmployActor (Scene_MenuBaseをベースに作成) |
一覧ウィンドウ | Window_EmployActorIndex (Window_Selectableをベースに作成) |
所持金ウィンドウ | Window_Gold |
コマンドウィンドウ | Window_EmployActorCommand (Window_Commandをベースに作成) |
ウィンドウ作成とボタンを押した時の処理を定義する
まずはScene_EmployActorにウィンドウの生成処理を作り、ボタンを押した時に呼び出す処理を定義していきます。
サンプルコード1
(function(){ 'use strict'; var pluginName = "EmployActor"; // ----------プラグインコマンドの定義 ここから ---------- var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand; Game_Interpreter.prototype.pluginCommand = function(command, args){ _Game_Interpreter_pluginCommand.call(this, command, args); if(command === pluginName){ switch(args[0]){ case 'open': SceneManager.push(Scene_EmployActor); break; } } }; // ----------プラグインコマンドの定義 ここまで ---------- // ----------Sceneの定義 ここから ---------- function Scene_EmployActor(){ this.initialize.apply(this, arguments); } Scene_EmployActor.prototype = Object.create(Scene_MenuBase.prototype); Scene_EmployActor.prototype.constructor = Scene_EmployActor; Scene_EmployActor.prototype.initialize = function(){ Scene_MenuBase.prototype.initialize.call(this); }; // ウィンドウの作成 Scene_EmployActor.prototype.create = function(){ Scene_MenuBase.prototype.create.call(this); this.createIndexWindow(); this.createGoldWindow(); this.createCommandWindow(); this.activateIndexWindow(); }; // アクター一覧ウィンドウ作成処理 Scene_EmployActor.prototype.createIndexWindow = function(){ var wx = 0; var wy = 0; var ww = Graphics.width / 3; var wh = Graphics.height; this._indexWindow = new Window_EmployActorIndex(wx, wy, ww, wh); this._indexWindow.setHandler('ok', this.onIndexOk.bind(this)); this._indexWindow.setHandler('cancel', this.onIndexCancel.bind(this)); this.addWindow(this._indexWindow); }; // 所持金ウィンドウ作成処理 Scene_EmployActor.prototype.createGoldWindow = function(){ this._goldWindow = new Window_Gold(0, 0); var wx = Graphics.width - this._goldWindow.width; this._goldWindow.move(wx, 0, this._goldWindow.width, this._goldWindow.height); this.addWindow(this._goldWindow); }; // コマンドウィンドウ作成処理 Scene_EmployActor.prototype.createCommandWindow = function(){ this._commandWindow = new Window_EmployCommand(0, 0); var wx = Graphics.width - this._commandWindow.width; var wy = Graphics.height - this._commandWindow.height; this._commandWindow.move(wx, wy, this._commandWindow.width, this._commandWindow.height); this._commandWindow.setHandler('employ', this.onCommandEmploy.bind(this)); this._commandWindow.setHandler('cancel', this.onCommandCancel.bind(this)); this.hideCommandWindow(); this.addWindow(this._commandWindow); }; // インデックスウィンドウで決定ボタンを押したときの処理 Scene_EmployActor.prototype.onIndexOk = function(){ this.activateCommandWindow(); }; // インデックスウィンドウでキャンセルボタンを押したときの処理 Scene_EmployActor.prototype.onIndexCancel = function(){ this.popScene(); }; // コマンドウィンドウで雇うボタンを押したときの処理 Scene_EmployActor.prototype.onCommandEmploy = function(){ this.hideCommandWindow(); this.employ(); this.activateIndexWindow(); }; // コマンドウィンドウでキャンセルボタンを押したときの処理 Scene_EmployActor.prototype.onCommandCancel = function(){ this.hideCommandWindow(); this._indexWindow.activate(); }; // 雇うコマンド Scene_EmployActor.prototype.employ = function(){ // あとで書く }; // 一覧ウィンドウを表示 Scene_EmployActor.prototype.activateIndexWindow = function(){ this._indexWindow.activate(); this._indexWindow.refresh(); }; // コマンドウィンドウを表示 Scene_EmployActor.prototype.activateCommandWindow = function(){ this._commandWindow.activate(); this._commandWindow.select(0); this._commandWindow.show(); }; // コマンドウィンドウを非表示 Scene_EmployActor.prototype.hideCommandWindow = function(){ this._commandWindow.deselect(); this._commandWindow.deactivate(); this._commandWindow.hide(); }; // ----------Sceneの定義 ここまで ---------- // ----------インデックスウィンドウの定義 ここから ---------- function Window_EmployActorIndex(){ this.initialize.apply(this, arguments); }; Window_EmployActorIndex.prototype = Object.create(Window_Selectable.prototype); Window_EmployActorIndex.prototype.constructor = Window_EmployActorIndex; Window_EmployActorIndex.prototype.initialize = function(x, y, width, height){ Window_Selectable.prototype.initialize.call(this, x, y, width, height); this.refresh(); this.select(0); this.activate(); }; // ----------インデックスウィンドウの定義 ここまで ---------- // ----------コマンドウィンドウの定義 ここから ---------- function Window_EmployCommand(){ this.initialize.apply(this, arguments); } Window_EmployCommand.prototype = Object.create(Window_Command.prototype); Window_EmployCommand.prototype.constructor = Window_EmployCommand; Window_EmployCommand.prototype.initialize = function(x, y){ Window_Command.prototype.initialize.call(this, x, y); }; // コマンドを設定 Window_EmployCommand.prototype.makeCommandList = function(){ this.addCommand('雇う', 'employ', true); }; // ----------コマンドウィンドウの定義 ここまで ---------- })();
ウィンドウを3つも作ったからコードが長いですが、やっていることは単純です。
プラグインコマンドの定義では、「EmployActor open」とプラグインコマンドを入力したらSceneManagerにScene_EmployActorをpushし、シーンを呼び出すようにしています。
Scene_EmployActorではウィンドウごとにcreate〇〇Windowメソッドを作り、 create メソッドから呼び出しています。
SceneManagerにpushした時にシーンのcreateメソッドが呼び出され、ウィンドウが生成されます。
雇うボタンは最初は見えている必要はないのでコマンドウィンドウは最初は非表示にしておき、一覧ボタンで決定ボタンが押されたら表示するようにします。
ウィンドウ生成処理の中で、一覧ウィンドウとコマンドウィンドウはキーが押されたときに呼び出される処理を setHandler で定義しています。
各ボタンの処理は、アクティブなウィンドウを切り替えるのが主な内容です。
ウィンドウ表示・非表示はメソッド化しておけば、コードを何回も書かなくてすみます。
それぞれactivateIndexWindow、activateCommandWindow、hideCommandWindowというメソッドを作りました。
アクター一覧ウィンドウ(indexWindow)のボタン処理
アクター一覧ウィンドウでは決定ボタンが押されたらonIndexOk、キャンセルボタンが押されたらonIndexCancelを呼び出します。
onIndexOkではactivateCommandWindowを呼び出し、コマンドウィンドウを表示してアクティブにします。
onIndexCancelではpopSceneメソッドを呼び出してシーンを終了します。
コマンドウィンドウ(commandWindow)のボタン処理
コマンドウィンドウでは雇うボタンが押されたらonCommandEmploy、キャンセルボタンが押されたらonCommandCancelを呼び出します。
onCommandEmployでは雇うボタンを非表示にしてemployメソッドを呼び出します。
employメソッドの内容はあとで考えるのでメソッド名だけ先に定義し、 // あとで書く というコメントを書いてあります。
employメソッドを呼び出したあとにactivateIndexWindowを呼び出して、一覧ウィンドウをアクティブにします。
onCommandCancelではhideCommandWindowでコマンドボタンを非表示にして、一覧ウィンドウをアクティブにします。
ウィンドウの定義
ウィンドウの定義は一覧ウィンドウはあとでいろいろつけたしていきますが、とりあえずクラスの定義とinitializeメソッドの中でrefreshメソッドの呼び出しをするようにしています。
refreshメソッドは、ウィンドウの内容を再描画するメソッドです。
コマンドウィンドウの定義では、雇うコマンドをemployというシンボルで追加しています。
実行してみると、ウィンドウが表示されます。
一覧ウィンドウにはまだ内容が表示されませんが、決定ボタンを押すと雇うボタンが表示されます。
(employメソッドの処理をまだ書いていないので、ボタンを押してもなにも起こりません)
雇うボタンが出た状態でキャンセルボタンを押すと雇うボタンが消え、カーソルが一覧ウィンドウに戻ります。
その状態で再度キャンセルボタンを押すと、ウィンドウが閉じます。
一覧ウィンドウに項目を表示する
アクターの一覧を取得する
一覧ウィンドウに表示する内容は、Window_EmployActorIndexの配列に入れておきます。
表示したいのはアクターの一覧なので、アクターオブジェクトの入った配列を作ります。
サンプルコード2-1
長くなるのでWindow_EmployActorIndexのところだけ抜粋しています。
追加した部分は黄色でハイライトしています。
// ----------インデックスウィンドウの定義 ここから ---------- function Window_EmployActorIndex(){ this.initialize.apply(this, arguments); }; Window_EmployActorIndex.prototype = Object.create(Window_Selectable.prototype); Window_EmployActorIndex.prototype.constructor = Window_EmployActorIndex; Window_EmployActorIndex.prototype.initialize = function(x, y, width, height){ Window_Selectable.prototype.initialize.call(this, x, y, width, height); this.refresh(); this.select(0); this.activate(); }; // 再描画処理 Window_EmployActorIndex.prototype.refresh = function(){ this.createContents(); this.makeActorList(); this.drawAllItems(); }; // アクターリストを作る Window_EmployActorIndex.prototype.makeActorList = function(){ this._list = []; // $dataActors[0]はnullなので1から取得 for(var i = 1; i < $dataActors.length; i++ ){ var actor = $dataActors[i]; if(hasTag(actor.meta, 'EASalary') && !isJoinedParty(actor)){ this._list.push(actor); } } }; // 指定したタグがあるか function hasTag(obj, tagName){ for(var propertyName in obj){ if(propertyName === tagName){ return true; } } return false; } // パーティに参加しているか function isJoinedParty(actor){ for(var i = 0; i < $gameParty._actors.length; i++ ){ if($gameParty._actors[i] === actor.id){ return true; } } return false; } // ----------インデックスウィンドウの定義 ここまで ----------
refreshメソッドの中でウィンドウの内容の消去、アクター一覧の作成、項目を書きだす処理を呼び出しています。
アクター一覧の作成はmakeActorListというメソッドを作りました。
ウィンドウの内容の消去(createContents)、項目を書きだす処理(drawAllItems)はWindow_Selectableが持っているメソッドをそのまま使います。
データベースで定義されたアクターは、グローバル変数 $dataActors で取得できます。
Javascriptではふつう、配列のインデックス0番からデータが入っていることが多いのですが、$dataActors のインデックス0番はnullという特殊な値が入っているため、インデックス1番からループしています。
アクターごとにEASalaryというノートタグがあるか、パーティに入っているかを判定し、「EASalaryというタグがあり、パーティに入っていないアクター」を配列に追加しています。
タグがあるかと、パーティに入っているかはそれぞれhasTag、isJoinedPartyという個別の関数を作って判定しています。
パーティに入っているキャラクターは $gameParty._actors で取得できます。
$gameParty._actorsはアクターIDが入った配列なので、$dataActorsのアクターIDと比較し、一致すればパーティに入っているとみなします。
一覧ウィンドウにデータを表示する
アクターの配列ができたら、つぎはウィンドウに表示する処理を作ります。
refreshメソッドの中でWindow_SelectableのdrawAllItemsメソッドを呼び出しています。
このメソッドは、インデックス番号を渡してdrawItemメソッドを呼び出します。
Window_EmployActorIndexにdrawItemメソッドを定義しておけば、それが呼び出されるので作っていきます。
サンプルコード2-2
// ----------インデックスウィンドウの定義 ここから ---------- function Window_EmployActorIndex(){ this.initialize.apply(this, arguments); }; Window_EmployActorIndex.prototype = Object.create(Window_Selectable.prototype); Window_EmployActorIndex.prototype.constructor = Window_EmployActorIndex; Window_EmployActorIndex.prototype.initialize = function(x, y, width, height){ Window_Selectable.prototype.initialize.call(this, x, y, width, height); this.refresh(); this.select(0); this.activate(); }; // 列数 Window_EmployActorIndex.prototype.maxCols = function(){ return 1; }; // アイテム数 Window_EmployActorIndex.prototype.maxItems = function(){ return this._list ? this._list.length : 0; }; // アクターリストを表示 Window_EmployActorIndex.prototype.drawItem = function(index){ var actor = this._list[index]; var salary = actor.meta['EASalary'] + TextManager.currencyUnit; var rect = this.itemRect(index); // 表示 this.drawText(actor.name, rect.x, rect.y, 100); this.drawText(salary, rect.x + 130, rect.y, 100, 'right'); }; // 再描画処理 Window_EmployActorIndex.prototype.refresh = function(){ this.createContents(); this.makeActorList(); this.drawAllItems(); }; // アクターリストを作る Window_EmployActorIndex.prototype.makeActorList = function(){ this._list = []; // $dataActors[0]はnullなので1から取得 for(var i = 1; i < $dataActors.length; i++ ){ var actor = $dataActors[i]; if(hasTag(actor.meta, 'EASalary') && !isJoinedParty(actor)){ this._list.push(actor); } } }; // タグがあるか function hasTag(obj, tagName){ for(var propertyName in obj){ if(propertyName === tagName){ return true; } } return false; } // パーティに参加しているか function isJoinedParty(actor){ for(var i = 0; i < $gameParty._actors.length; i++ ){ if($gameParty._actors[i] === actor.id){ return true; } } return false; } // ----------インデックスウィンドウの定義 ここまで ----------
ウィンドウの列数を設定する
Windows_Selectableでは、1行に複数の項目を並べて表示できます。
1行の項目数(列数)はmaxColsプロパティで指定できます。
デフォルトは3ですが、このプラグインでは1行に1人ずつ表示したいため、maxColsを1としています。
アイテム数(項目数)を設定する
その下のmaxItemsは一覧に表示する項目の数です。
Window_SelectableのdrawAllItemsでは、インデックスがmaxItemsより小さい場合にdrawItemを呼び出します。
maxItemsを定義していない場合は0になるので、データを配列に入れていても、maxItemsを定義しないかぎりdrawItemメソッドが呼び出されません。
ウィンドウには配列の内容をぜんぶ表示したいので、 maxItemsは配列の要素数になります。
そこで、
return this._list ? this._list.length : 0;
としています。
単純に
return this._list.length
としないのは、makeActorListメソッドが呼び出されるまではthis._listがundefinedという値になっているので、lengthプロパティを取得しようとするとエラーになるからです。
条件式 ? 値1: 値2
というのは三項演算子という書き方で、条件式がtrueならば値1、falseならば値2になります。
配列のデータを一覧ウィンドウに表示する
実際にデータをウィンドウに書きだすのはdrawItemメソッドです。
drawAllItemsメソッドからインデックス番号が渡されるので、配列のindex番めの値を取得します。
配列の中身はアクターオブジェクトなので、 アクターオブジェクト.meta[タグ名] でノートタグの内容も取得できます。
賃金は通貨単位とあわせて表示するので、数値に変換はせず、文字列のまま扱っています。
通貨単位は TextManager.currencyUnit で取得します。
テキストを表示するのはdrawTextメソッドです。
drawText(表示する文字, X座標, Y座標, 行幅, 配置);
配置は右揃えとか中央揃えとかです。(省略可)
指定する場合は、’right’や’center’とか英語で指定します。
X座標とY座標は、 itemRect(index) で項目の領域が取得できるので、そのまま使っています。
ここらへんは他の人のコードを参考に作っているので、よくわかってなかったり。
賃金はアクター名より右側に表示したいので、X座標を+130しています。
また、右揃えにすることで賃金の桁数が違っても通貨単位の位置が揃うようにしています。
データベースのアクターのメモ欄に、<EASalary:>を設定してから呼び出してみます。
テレーゼに<EASalary:100> 、マーシャに<EASalary:200>、ルキウスに<EASalary:150>を設定してみました。
テストプレイすると、こんな感じで表示されます。
行の有効・無効を切り替える
アクターの一覧を表示するところまではできましたが、所持金が0Gなのにアクター名が白色で表示されています。
しかも、決定ボタンを押すと雇うボタンも押せます。
所持金が足りない場合と、パーティ人数がいっぱいの時は決定ボタンを押しても選択できないようにします。
一覧ウィンドウから所持金を取得できるようにする
所持金の額は、Window_Goldのvalueから取得できます。
しかし、Window_GoldはScene_EmployActorで生成しているため、Window_EmployActorIndexからはWindow_Goldのvalueが取得できません。
そこでScene_EmployActorからWindow_EmployActorIndexに所持金額を渡せるように、メソッドを追加します。
サンプルコード3-1
(function(){ 'use strict'; var pluginName = "EmployActor"; // ----------プラグインコマンドの定義 ここから ---------- var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand; Game_Interpreter.prototype.pluginCommand = function(command, args){ _Game_Interpreter_pluginCommand.call(this, command, args); if(command === pluginName){ switch(args[0]){ case 'open': SceneManager.push(Scene_EmployActor); break; } } }; // ----------プラグインコマンドの定義 ここまで ---------- // ----------Sceneの定義 ここから ---------- function Scene_EmployActor(){ this.initialize.apply(this, arguments); } Scene_EmployActor.prototype = Object.create(Scene_MenuBase.prototype); Scene_EmployActor.prototype.constructor = Scene_EmployActor; Scene_EmployActor.prototype.initialize = function(){ Scene_MenuBase.prototype.initialize.call(this); }; // ウィンドウの作成 Scene_EmployActor.prototype.create = function(){ Scene_MenuBase.prototype.create.call(this); this.createIndexWindow(); this.createGoldWindow(); this.createCommandWindow(); this.activateIndexWindow(); }; // アクター一覧ウィンドウ作成処理 Scene_EmployActor.prototype.createIndexWindow = function(){ var wx = 0; var wy = 0; var ww = Graphics.width / 3; var wh = Graphics.height; this._indexWindow = new Window_EmployActorIndex(wx, wy, ww, wh); this._indexWindow.setHandler('ok', this.onIndexOk.bind(this)); this._indexWindow.setHandler('cancel', this.onIndexCancel.bind(this)); this.addWindow(this._indexWindow); }; // 所持金ウィンドウ作成処理 Scene_EmployActor.prototype.createGoldWindow = function(){ this._goldWindow = new Window_Gold(0, 0); var wx = Graphics.width - this._goldWindow.width; this._goldWindow.move(wx, 0, this._goldWindow.width, this._goldWindow.height); this.addWindow(this._goldWindow); }; // コマンドウィンドウ作成処理 Scene_EmployActor.prototype.createCommandWindow = function(){ this._commandWindow = new Window_EmployCommand(0, 0); var wx = Graphics.width - this._commandWindow.width; var wy = Graphics.height - this._commandWindow.height; this._commandWindow.move(wx, wy, this._commandWindow.width, this._commandWindow.height); this._commandWindow.setHandler('employ', this.onCommandEmploy.bind(this)); this._commandWindow.setHandler('cancel', this.onCommandCancel.bind(this)); this.hideCommandWindow(); this.addWindow(this._commandWindow); }; // インデックスウィンドウで決定ボタンを押したときの処理 Scene_EmployActor.prototype.onIndexOk = function(){ this.activateCommandWindow(); }; // インデックスウィンドウでキャンセルボタンを押したときの処理 Scene_EmployActor.prototype.onIndexCancel = function(){ this.popScene(); }; // コマンドウィンドウで雇うボタンを押したときの処理 Scene_EmployActor.prototype.onCommandEmploy = function(){ this.hideCommandWindow(); this.employ(); this.activateIndexWindow(); this._goldWindow.refresh(); }; // コマンドウィンドウでキャンセルボタンを押したときの処理 Scene_EmployActor.prototype.onCommandCancel = function(){ this.hideCommandWindow(); this._indexWindow.activate(); }; // 雇うコマンド Scene_EmployActor.prototype.employ = function(){ // あとで書く }; // 所持金 Scene_EmployActor.prototype.money = function(){ return this._goldWindow.value(); }; // 一覧ウィンドウに所持金をセットして更新する Scene_EmployActor.prototype.activateIndexWindow = function(){ this._indexWindow.setMoney(this.money()); this._indexWindow.activate(); this._indexWindow.refresh(); }; // コマンドウィンドウを表示 Scene_EmployActor.prototype.activateCommandWindow = function(){ this._commandWindow.activate(); this._commandWindow.select(0); this._commandWindow.show(); }; // コマンドウィンドウを非表示 Scene_EmployActor.prototype.hideCommandWindow = function(){ this._commandWindow.deselect(); this._commandWindow.deactivate(); this._commandWindow.hide(); }; // ----------Sceneの定義 ここまで ---------- // ----------インデックスウィンドウの定義 ここから ---------- function Window_EmployActorIndex(){ this.initialize.apply(this, arguments); }; Window_EmployActorIndex.prototype = Object.create(Window_Selectable.prototype); Window_EmployActorIndex.prototype.constructor = Window_EmployActorIndex; Window_EmployActorIndex.prototype.initialize = function(x, y, width, height){ Window_Selectable.prototype.initialize.call(this, x, y, width, height); this.refresh(); this.select(0); this.activate(); }; // 列数 Window_EmployActorIndex.prototype.maxCols = function(){ return 1; }; // アイテム数 Window_EmployActorIndex.prototype.maxItems = function(){ return this._list ? this._list.length : 0; }; // アクターリストを表示 Window_EmployActorIndex.prototype.drawItem = function(index){ var actor = this._list[index]; var salary = actor.meta['EASalary'] + TextManager.currencyUnit; var rect = this.itemRect(index); // 表示 this.drawText(actor.name, rect.x, rect.y, 100); this.drawText(salary, rect.x + 130, rect.y, 100, 'right'); }; // 所持金 Window_EmployActorIndex.prototype.setMoney = function(money){ this._money = money; }; // 再描画処理 Window_EmployActorIndex.prototype.refresh = function(){ this.createContents(); this.makeActorList(); this.drawAllItems(); }; // アクターリストを作る Window_EmployActorIndex.prototype.makeActorList = function(){ this._list = []; // $dataActors[0]はnullなので1から取得 for(var i = 1; i < $dataActors.length; i++ ){ var actor = $dataActors[i]; if(hasTag(actor.meta, 'EASalary') && !isJoinedParty(actor)){ this._list.push(actor); } } }; // タグがあるか function hasTag(obj, tagName){ for(var propertyName in obj){ if(propertyName === tagName){ return true; } } return false; } // パーティに参加しているか function isJoinedParty(actor){ for(var i = 0; i < $gameParty._actors.length; i++ ){ if($gameParty._actors[i] === actor.id){ return true; } } return false; } // ----------インデックスウィンドウの定義 ここまで ---------- // ----------コマンドウィンドウの定義 ここから ---------- function Window_EmployCommand(){ this.initialize.apply(this, arguments); } Window_EmployCommand.prototype = Object.create(Window_Command.prototype); Window_EmployCommand.prototype.constructor = Window_EmployCommand; Window_EmployCommand.prototype.initialize = function(x, y){ Window_Command.prototype.initialize.call(this, x, y); }; // コマンドを設定 Window_EmployCommand.prototype.makeCommandList = function(){ this.addCommand('雇う', 'employ', true); }; // ----------コマンドウィンドウの定義 ここまで ---------- })();
Window_EmployActorIndexにsetMoneyという所持金を設定するメソッドを追加しました。
Scene_EmployActorのactivateIndexWindowメソッドの中でsetMoneyを呼び出し、所持金を渡しています。
パーティの持っている所持金は $gameParty._gold でも取得できるので回りくどい気がしますが、ショップで買う時の内部処理(Window_ShopBuy)がこういう処理をしていたので、いちおう合わせました。
パーティの最大人数を定義する
パーティ人数がいっぱいの時を判定できるように、パーティの最大人数を定義します。
パーティの最大人数はデフォルトだと4人ですが、プラグインでパーティの最大人数を増やしている人もいるのでパラメータで設定できるようにします。
ついでにほかのプラグインの説明も書いておきます。
サンプルコード3-2
// ====================== // EmployActor.js // ====================== /*: * @plugindesc Employ Actor with money * @author Moooty * * @param maxParty * @desc Max capacity in party. * @default 4 * @type Number * @help * Plugin Command: * open # open employ screen. * * Actor's Note tag: * <EASalary: 100> # employ actor 100 gold. */ /*:ja * @plugindesc お金で仲間を雇うプラグイン * @author むーてぃ * * @param maxParty * @desc パーティの最大人数 * @default 4 * @type Number * * @help * プラグインコマンド: * open # 雇用画面を開く * * アクターのメモ欄: * <EASalary: 100> # 賃金を100Gにする */ (function(){ 'use strict'; var pluginName = "EmployActor"; var parameters = PluginManager.parameters(pluginName); var maxParty = String(parameters['maxParty'] || 4); // ----------プラグインコマンドの定義 ここから ---------- var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand; Game_Interpreter.prototype.pluginCommand = function(command, args){ _Game_Interpreter_pluginCommand.call(this, command, args); if(command === pluginName){ switch(args[0]){ case 'open': SceneManager.push(Scene_EmployActor); break; } } }; // ----------プラグインコマンドの定義 ここまで ----------
お金が足りない時とパーティがいっぱいの時は選択できないようにする
これで所持金とパーティの最大人数が取得できるようになったので、行の有効・無効を判定できます。
行の有効・無効を切り替えるには、isCurrentItemEnabledメソッドを使います。
サンプルコード3-3
// ----------インデックスウィンドウの定義 ここから ---------- function Window_EmployActorIndex(){ this.initialize.apply(this, arguments); }; Window_EmployActorIndex.prototype = Object.create(Window_Selectable.prototype); Window_EmployActorIndex.prototype.constructor = Window_EmployActorIndex; Window_EmployActorIndex.prototype.initialize = function(x, y, width, height){ Window_Selectable.prototype.initialize.call(this, x, y, width, height); this.refresh(); this.select(0); this.activate(); }; // 列数 Window_EmployActorIndex.prototype.maxCols = function(){ return 1; }; // アイテム数 Window_EmployActorIndex.prototype.maxItems = function(){ return this._list ? this._list.length : 0; }; // アクターリストを表示 Window_EmployActorIndex.prototype.drawItem = function(index){ var actor = this._list[index]; var salary = actor.meta['EASalary'] + TextManager.currencyUnit; var rect = this.itemRect(index); // 表示 this.drawText(actor.name, rect.x, rect.y, 100); this.drawText(salary, rect.x + 130, rect.y, 100, 'right'); }; // 選択しているアクター Window_EmployActorIndex.prototype.selectedItem = function(){ return this._list[this.index()]; }; // 選択できるか Window_EmployActorIndex.prototype.isCurrentItemEnabled = function(){ return this.isEnabled(this.selectedItem()); }; // 使用可能か Window_EmployActorIndex.prototype.isEnabled = function(actor){ if(actor){ var hasPartyCapacity = $gameParty._actors.length < maxParty; var hasMoney = actor.meta['EASalary'] <= this._money; } return hasPartyCapacity && hasMoney; }; // 所持金 Window_EmployActorIndex.prototype.setMoney = function(money){ this._money = money; }; // 再描画処理 Window_EmployActorIndex.prototype.refresh = function(){ this.createContents(); this.makeActorList(); this.drawAllItems(); }; // アクターリストを作る Window_EmployActorIndex.prototype.makeActorList = function(){ this._list = []; // $dataActors[0]はnullなので1から取得 for(var i = 1; i < $dataActors.length; i++ ){ var actor = $dataActors[i]; if(hasTag(actor.meta, 'EASalary') && !isJoinedParty(actor)){ this._list.push(actor); } } }; // タグがあるか function hasTag(obj, tagName){ for(var propertyName in obj){ if(propertyName === tagName){ return true; } } return false; } // パーティに参加しているか function isJoinedParty(actor){ for(var i = 0; i < $gameParty._actors.length; i++ ){ if($gameParty._actors[i] === actor.id){ return true; } } return false; } // ----------インデックスウィンドウの定義 ここまで ----------
isCurrentItemEnabledがtrueの時には決定ボタンで選択可能になります。
falseの時は決定ボタンを押すとブブーッという効果音が鳴り、決定ボタンを押した時の処理が呼ばれなくなります。
お金が足りるかとパーティの最大人数未満かを判定するisEnabledメソッドを作り、戻り値をisCurrentItemEnabledで使っています。
選択中のアクターはこれから雇うコマンドを作る時にも取得したいので、selectedItemメソッドを作って取得できるようにしました。
項目を選択できない時に文字をグレーアウトする
isCurrentItemEnabledは項目を押せなくなるだけで、文字色までは変わりません。
文字をグレーアウトするにはchangePaintOpacityメソッドを使います。
trueの時には通常の白い文字、falseの時には文字の不透明度が下がりグレーになります。
ついでに、 meta[‘EASalary’] と書いているところが多いので、ノートタグ名を変数にしておきます。
ノートタグの名前を変えたくなった時に、1か所なおすだけですみます。
サンプルコード3-4
(function(){ 'use strict'; var pluginName = "EmployActor"; var tagName = "EASalary"; var parameters = PluginManager.parameters(pluginName); var maxParty = String(parameters['maxParty'] || 4);
コード中略(省略部分は変更なし)
// ----------インデックスウィンドウの定義 ここから ---------- function Window_EmployActorIndex(){ this.initialize.apply(this, arguments); }; Window_EmployActorIndex.prototype = Object.create(Window_Selectable.prototype); Window_EmployActorIndex.prototype.constructor = Window_EmployActorIndex; Window_EmployActorIndex.prototype.initialize = function(x, y, width, height){ Window_Selectable.prototype.initialize.call(this, x, y, width, height); this.refresh(); this.select(0); this.activate(); }; // 列数 Window_EmployActorIndex.prototype.maxCols = function(){ return 1; }; // アイテム数 Window_EmployActorIndex.prototype.maxItems = function(){ return this._list ? this._list.length : 0; }; // アクターリストを表示 Window_EmployActorIndex.prototype.drawItem = function(index){ var actor = this._list[index]; var salary = actor.meta[tagName] + TextManager.currencyUnit; var rect = this.itemRect(index); // 文字色を変更 this.changePaintOpacity(this.isEnabled(actor)); // 表示 this.drawText(actor.name, rect.x, rect.y, 100); this.drawText(salary, rect.x + 130, rect.y, 100, 'right'); }; // 選択しているアクター Window_EmployActorIndex.prototype.selectedItem = function(){ return this._list[this.index()]; }; // 選択できるか Window_EmployActorIndex.prototype.isCurrentItemEnabled = function(){ return this.isEnabled(this.selectedItem()); }; // 使用可能か Window_EmployActorIndex.prototype.isEnabled = function(actor){ if(actor){ var hasPartyCapacity = $gameParty._actors.length < maxParty; var hasMoney = actor.meta[tagName] <= this._money; } return hasPartyCapacity && hasMoney; }; // 所持金 Window_EmployActorIndex.prototype.setMoney = function(money){ this._money = money; }; // 再描画処理 Window_EmployActorIndex.prototype.refresh = function(){ this.createContents(); this.makeActorList(); this.drawAllItems(); }; // アクターリストを作る Window_EmployActorIndex.prototype.makeActorList = function(){ this._list = []; // $dataActors[0]はnullなので1から取得 for(var i = 1; i < $dataActors.length; i++ ){ var actor = $dataActors[i]; if(hasTag(actor.meta, tagName) && !isJoinedParty(actor)){ this._list.push(actor); } } }; // タグがあるか function hasTag(obj, tagName){ for(var propertyName in obj){ if(propertyName === tagName){ return true; } } return false; } // パーティに参加しているか function isJoinedParty(actor){ for(var i = 0; i < $gameParty._actors.length; i++ ){ if($gameParty._actors[i] === actor.id){ return true; } } return false; } // ----------インデックスウィンドウの定義 ここまで ----------
文字色がグレーになって押せなくなりました。
お金が手に入るイベントを別につくり、ゴールドを増やしてから開くとキチンと選択できるようになります。
あとは雇うコマンドの内容を作っていきます。
コマンドボタンを押した時の処理を作る
最初にScene_EmployActorを定義したときにemployメソッドも名前だけ定義して // あとで書く とコメントを書いておいたので、そこに処理を書いています。
// 雇うコマンド Scene_EmployActor.prototype.employ = function(){ var actor = this._indexWindow.selectedItem(); if(actor){ var salary = Number(actor.meta[tagName]); $gameParty.loseGold(salary); $gameParty.addActor(actor.id); } };
一覧で選択したアイテムはWindow_EmployActorIndexのselectedItemから取得できるようにしたので、選択されたアクターを取得します。
ノートタグから賃金を取得し、 $gameParty.loseGold でお金を減らして $gameParty.addActor でアクターをパーティに追加するだけです。
デバッグと細かい修正
プラグインの挙動に問題がないか、チェックします。
ゴールドを増やすイベントを設置し、ウィンドウを開いてみます。
ちゃんと選択できますね。
雇うと雇った人はリストから消え、金額の足りなくなった人はグレーアウトされます。
データが変わってもウィンドウをrefreshしないとウィンドウの表示は更新されないので、Scene_EmployActorのactivateIndexWindowメソッドの中でWindow_EmployActorIndexをrefreshし、employメソッドの最後で呼び出すことでカーソルを一覧に戻しつつ、データを更新しているところがポイントです。
おおむねいい感じですが、項目の最後の人を選択すると空白行が残ってしまいました。
最後にカーソルをあわせた位置は、lastIndexプロパティに入っています。
しかしlastIndexの値はウィンドウをrefreshして項目が減った場合でも変わらないため、データがない行が選択されてしまいます。
refreshメソッドの処理を追加して、カーソル位置を調整します。
// 再描画処理 Window_EmployActorIndex.prototype.refresh = function(){ this.createContents(); this.makeActorList(); this.drawAllItems(); // 最後の項目を選択した時にカーソルを1つ上の項目にセット if(this.lastIndex > this._list.length - 1){ this.select(this._list.length - 1); } };
一覧のn番めの人を選択する(カーソルをうつす)にはselectメソッドを使います。
指定したインデックス番号の項目にカーソルが移動します。
インデックス番号は0から始まります。
ウィンドウ描画時にlastIndexをの値がアクターの配列の要素数-1より大きかったら、lastIndexに配列の要素数-1を代入します。
lastIndexが3で一覧に表示されるアクターが2人だったらインデックス1番(リストの2番めの人)にカーソルが移ります。
Window_Selectableを使ったサンプルコード(最終版)
// ====================== // EmployActor.js // ====================== /*: * @plugindesc Employ Actor with money * @author Moooty * * @param maxParty * @desc Max capacity in party. * @default 4 * @type Number * @help * Plugin Command: * open # open employ screen. * * Actor's Note tag: * <EASalary: 100> # employ actor 100 gold. */ /*:ja * @plugindesc お金で仲間を雇うプラグイン * @author むーてぃ * * @param maxParty * @desc パーティの最大人数 * @default 4 * @type Number * * @help * プラグインコマンド: * open # 雇用画面を開く * * アクターのメモ欄: * <EASalary: 100> # 賃金を100Gにする */ (function(){ 'use strict'; var pluginName = "EmployActor"; var tagName = "EASalary"; var parameters = PluginManager.parameters(pluginName); var maxParty = String(parameters['maxParty'] || 4); // ----------プラグインコマンドの定義 ここから ---------- var _Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand; Game_Interpreter.prototype.pluginCommand = function(command, args){ _Game_Interpreter_pluginCommand.call(this, command, args); if(command === pluginName){ switch(args[0]){ case 'open': SceneManager.push(Scene_EmployActor); break; } } }; // ----------プラグインコマンドの定義 ここまで ---------- // ----------Sceneの定義 ここから ---------- function Scene_EmployActor(){ this.initialize.apply(this, arguments); } Scene_EmployActor.prototype = Object.create(Scene_MenuBase.prototype); Scene_EmployActor.prototype.constructor = Scene_EmployActor; Scene_EmployActor.prototype.initialize = function(){ Scene_MenuBase.prototype.initialize.call(this); }; // ウィンドウの作成 Scene_EmployActor.prototype.create = function(){ Scene_MenuBase.prototype.create.call(this); this.createIndexWindow(); this.createGoldWindow(); this.createCommandWindow(); this.activateIndexWindow(); }; // アクター一覧ウィンドウ作成処理 Scene_EmployActor.prototype.createIndexWindow = function(){ var wx = 0; var wy = 0; var ww = Graphics.width / 3; var wh = Graphics.height; this._indexWindow = new Window_EmployActorIndex(wx, wy, ww, wh); this._indexWindow.setHandler('ok', this.onIndexOk.bind(this)); this._indexWindow.setHandler('cancel', this.onIndexCancel.bind(this)); this.addWindow(this._indexWindow); }; // 所持金ウィンドウ作成処理 Scene_EmployActor.prototype.createGoldWindow = function(){ this._goldWindow = new Window_Gold(0, 0); var wx = Graphics.width - this._goldWindow.width; this._goldWindow.move(wx, 0, this._goldWindow.width, this._goldWindow.height); this.addWindow(this._goldWindow); }; // コマンドウィンドウ作成処理 Scene_EmployActor.prototype.createCommandWindow = function(){ this._commandWindow = new Window_EmployCommand(0, 0); var wx = Graphics.width - this._commandWindow.width; var wy = Graphics.height - this._commandWindow.height; this._commandWindow.move(wx, wy, this._commandWindow.width, this._commandWindow.height); this._commandWindow.setHandler('employ', this.onCommandEmploy.bind(this)); this._commandWindow.setHandler('cancel', this.onCommandCancel.bind(this)); this.hideCommandWindow(); this.addWindow(this._commandWindow); }; // インデックスウィンドウで決定ボタンを押したときの処理 Scene_EmployActor.prototype.onIndexOk = function(){ this.activateCommandWindow(); }; // インデックスウィンドウでキャンセルボタンを押したときの処理 Scene_EmployActor.prototype.onIndexCancel = function(){ this.popScene(); }; // コマンドウィンドウで雇うボタンを押したときの処理 Scene_EmployActor.prototype.onCommandEmploy = function(){ this.hideCommandWindow(); this.employ(); this.activateIndexWindow(); this._goldWindow.refresh(); }; // コマンドウィンドウでキャンセルボタンを押したときの処理 Scene_EmployActor.prototype.onCommandCancel = function(){ this.hideCommandWindow(); this._indexWindow.activate(); }; // 雇うコマンド Scene_EmployActor.prototype.employ = function(){ var actor = this._indexWindow.selectedItem(); if(actor){ var salary = Number(actor.meta[tagName]); $gameParty.loseGold(salary); $gameParty.addActor(actor.id); } }; // 所持金 Scene_EmployActor.prototype.money = function(){ return this._goldWindow.value(); }; // 一覧ウィンドウに所持金をセットして更新する Scene_EmployActor.prototype.activateIndexWindow = function(){ this._indexWindow.setMoney(this.money()); this._indexWindow.activate(); this._indexWindow.refresh(); }; // コマンドウィンドウを表示 Scene_EmployActor.prototype.activateCommandWindow = function(){ this._commandWindow.activate(); this._commandWindow.select(0); this._commandWindow.show(); }; // コマンドウィンドウを非表示 Scene_EmployActor.prototype.hideCommandWindow = function(){ this._commandWindow.deselect(); this._commandWindow.deactivate(); this._commandWindow.hide(); }; // ----------Sceneの定義 ここまで ---------- // ----------インデックスウィンドウの定義 ここから ---------- function Window_EmployActorIndex(){ this.initialize.apply(this, arguments); }; Window_EmployActorIndex.prototype = Object.create(Window_Selectable.prototype); Window_EmployActorIndex.prototype.constructor = Window_EmployActorIndex; Window_EmployActorIndex.prototype.initialize = function(x, y, width, height){ Window_Selectable.prototype.initialize.call(this, x, y, width, height); this.refresh(); this.select(0); this.activate(); }; // 列数 Window_EmployActorIndex.prototype.maxCols = function(){ return 1; }; // アイテム数 Window_EmployActorIndex.prototype.maxItems = function(){ return this._list ? this._list.length : 0; }; // アクターリストを表示 Window_EmployActorIndex.prototype.drawItem = function(index){ var actor = this._list[index]; var salary = actor.meta[tagName] + TextManager.currencyUnit; var rect = this.itemRect(index); // 文字色を変更 this.changePaintOpacity(this.isEnabled(actor)); // 表示 this.drawText(actor.name, rect.x, rect.y, 100); this.drawText(salary, rect.x + 130, rect.y, 100, 'right'); }; // 選択しているアクター Window_EmployActorIndex.prototype.selectedItem = function(){ return this._list[this.index()]; }; // 選択できるか Window_EmployActorIndex.prototype.isCurrentItemEnabled = function(){ return this.isEnabled(this.selectedItem()); }; // 使用可能か Window_EmployActorIndex.prototype.isEnabled = function(actor){ if(actor){ var hasPartyCapacity = $gameParty._actors.length < maxParty; var hasMoney = actor.meta[tagName] <= this._money; } return hasPartyCapacity && hasMoney; }; // 所持金 Window_EmployActorIndex.prototype.setMoney = function(money){ this._money = money; }; // 再描画処理 Window_EmployActorIndex.prototype.refresh = function(){ this.createContents(); this.makeActorList(); this.drawAllItems(); // 最後の項目を選択した時にカーソルを1つ上の項目にセット if(this.lastIndex > this._list.length - 1){ this.select(this._list.length - 1); } }; // アクターリストを作る Window_EmployActorIndex.prototype.makeActorList = function(){ this._list = []; // $dataActors[0]はnullなので1から取得 for(var i = 1; i < $dataActors.length; i++ ){ var actor = $dataActors[i]; if(hasTag(actor.meta, tagName) && !isJoinedParty(actor)){ this._list.push(actor); } } }; // タグがあるか function hasTag(obj, tagName){ for(var propertyName in obj){ if(propertyName === tagName){ return true; } } return false; } // パーティに参加しているか function isJoinedParty(actor){ for(var i = 0; i < $gameParty._actors.length; i++ ){ if($gameParty._actors[i] === actor.id){ return true; } } return false; } // ----------インデックスウィンドウの定義 ここまで ---------- // ----------コマンドウィンドウの定義 ここから ---------- function Window_EmployCommand(){ this.initialize.apply(this, arguments); } Window_EmployCommand.prototype = Object.create(Window_Command.prototype); Window_EmployCommand.prototype.constructor = Window_EmployCommand; Window_EmployCommand.prototype.initialize = function(x, y){ Window_Command.prototype.initialize.call(this, x, y); }; // コマンドを設定 Window_EmployCommand.prototype.makeCommandList = function(){ this.addCommand('雇う', 'employ', true); }; // ----------コマンドウィンドウの定義 ここまで ---------- })();
今回はWindow_Selectableの使い方を紹介するのが目的なので、機能は必要最低限にしています。
実際にゲームに使う場合は、画面の空いているところに選択しているアクターのステータスを表示させたり、雇うたびに好感度があがっていって賃金が安くなるみたいなシステムを組み込んでもよいかと思います。