選択ウィンドウを作ってみる
yamachanさんのショップ販売のカテゴリ化プラグインを作成してみるや SigureyaさんのWindow_Selectableの解説 - Qiitaを見つつ、選択ウィンドウを追っていこうと思います。
残りのウィンドウ
今回は残ったウィンドウを探っていきます。
Window_Selectable
の直下には たくさんクラスがありますが、メニューらしいメニューは Window_Command
を基本としてつくられているので、 Window_Selectable
Window_Command
を中心に見ていこうと思います。
- Window_Selectable
- Window_BattleEnemy 敵の選択
- Window_BattleLog 戦闘ログ
- Window_DebugEdit デバッグ(F9)編集
- Window_DebugRange デバッグ(F9)範囲
- Window_EquipSlot 装備スロット
- Window_NameInput [名前入力の処理]文字入力表
- Window_NumberInput [数値入力の処理]数値入力表
- Window_SavefileList [セーブ画面を開く]ファイルリスト
- Window_ShopBuy [ショップの処理]購入
- Window_ShopNumber [ショップの処理]個数
- Window_Status ステータス
- Window_BattleStatus
- Window_BattleActor 戦闘シーンのアクター選択
- Window_MenuStatus
- Window_MenuActor アクター選択
- Window_SkillList
- Window_BattleSkill 戦闘シーンのスキル選択
- Window_ItemList
- Window_BattleItem 戦闘シーンのアイテム選択
- Window_EquipItem 装備アイテム選択
- Window_EventItem イベントアイテム選択
- Window_ShopSell [ショップの処理]売却
- Window_Command
- Window_ActorCommand アクターコマンド
- Window_ChoiceList [選択肢の表示]
- Window_GameEnd [ゲーム終了]
- Window_MenuCommand メニューコマンド
- Window_Options オプション
- Window_PartyCommand パーティコマンド
- Window_SkillType [スキルタイプ]
- Window_TitleCommand タイトルコマンド
- Window_HorzCommand
- Window_EquipCommand [装備]の種類選択
- Window_ItemCategory [アイテム]の種類選択
- Window_ShopCommand [ショップの処理]の売買選択
Window_Selectable
と Window_Command
をじっくりやって、その下のやつはまた必要になったら調べるという感じで、今回は流しちゃおうと思います。
Window_Selectable
まずは今回調べるクラスの大元である Window_Selectable を使ってみましょう。
前回と同様に、Scene_Title.prototype.create
の後ろの方に間借りします。
// ↓ ここから追加部分
const selectWindow = new Window_Selectable( 100, 20, 400, 200 );
this.addChild( selectWindow );
};
表示されましたが、選択肢もないし、当然選択した後の処理も何もありません。
Window_Selectable
を読んでみたら drawItem()
というコマンド項目を描くメソッドはあるんですが中身は空です!
データの保持と描画は継承して新しいクラスで実装してね、ということみたいです。
ごく基本的なテキストコマンドは実行できると思ってましたが、意外。
文字や画像の表示に関しては今まで追ってきたように
Window_Base
が持っているので、それを継承したWindow_Selectable
でも当然使えます。
活用して drawItem()
の中身を実装すれば良いというわけです。
それに入力関係の処理やレイアウトの処理などは一通り Window_Selectable
に揃っています。
Window_Command
Window_Command
は、いわゆるコマンドメニューを実現するクラスです。
このクラスの中身を調べておけば、自分で Window_Selectable
を拡張してクラスを作る場合もスムースに実現できるはず。
Window_Selectable
は選択項目のデータを持っていません。
そこで Window_Command
は _list
という配列プロパティを用意して、そこにコマンドに必要な情報を格納しています。
そして選択項目の最大数を返す maxItems()
メソッドを、このリストの長さを返すものに変えてあります。
この全体の数さえ判明すれば、その他の部分はいい感じに処理してくれます。drawItem( index )
は引数の項目番号をもとに位置を決めて描画すればOK。
位置決めのためのメソッドも用意してあって、itemRectForText( index )
に項目番号を渡せば矩形範囲(
Rectangle
)を返してくれます。
この矩形範囲に沿って drawText()
を使えば文字を描けます。
なお、選択項目が文字列でない場合は itemRect( index )
がいい感じに選択範囲を返してくれます。
しかし、ここまでの変更ではウィンドウしか表示されません。
Window_Command
は initialize() で refresh()
を実行して、コマンド文字の描画を行っています。
それでも、選択肢は表示されるもののカーソルの操作ができません。これはウィンドウにフォーカスが合ってないからです。
なので続けて activate()
を実行してウィンドウにフォーカスを合わせます。
そしてまだカーソルが表示されません。初期状態では項目が選択されていないからです。
なので続けて、select( 0 )
として最初の項目を選択しています。
以上がおおよその Window_Selectable
を拡張してコマンド選択ウィンドウにする手順です。
んが! まだコマンドリストは表示できないんですよ!!
コマンドリストの登録は makeCommandList()
で行うように設計されていますが、 Window_Command
では空のメソッドなんです。
Window_Command を継承する
Window_Command
のコードを読むと、initialize()
で refresh()
が呼ばれて、そこからさらに makeCommandList()
が呼ばれてます。
だから普通に追加しただけでは空のウィンドウができてしまいます。
後から makeCommandList()
を書き換えてコマンドを追加する機能をつける手もありますが、ここはもう新たにクラスを作りましょう。
1・2・3を選べるものにするので、名前は Choice_123
にしましょう。
RPGツクールMVもずっと Window_ じゃなくて接頭辞変えた方がわかりやすいと思うんですけどね。
// ↓ ここから追加部分
const choice = new Choice_123( 100, 20 );
this.addChild( choice );
};
class Choice_123 extends Window_Command {
constructor( x, y ) {
super( x, y );
}
makeCommandList() {
this.addCommand( '1', 'command1' );
this.addCommand( '2', 'ok' );
this.addCommand( '3', 'cancel' );
}
}
class
は最近の JavaScript
環境だったらだいたい動く書式で、RPGツクールMVで採用されている書き方ではありませんが非常にわかりやすいので使っていきます。
ここでは class
について解説するのは本題ではないので避けます。
makeCommandList()
の中で addCommand()
というこれも Window_Command
のメソッドを呼んでいます。
だいたい予想できる通りの機能ですね。実際に画面に表示されるラベル('1')と、スクリプトの操作で使うシンボル('command1')を設定してます。
ラベルとシンボルが分かれているのは多言語対応を見越した仕組みだと思いますが、RPGツクールMVそのものがイマイチ多言語対応機能に積極的でないので、宝の持ち腐れというかただ面倒臭いだけになってる感じがあります。
さて、それはそれとして、これで一見コマンド選択ウィンドウができたように見えますが、決定しても何も起きません。
選択後の処理を設定する必要があります。面倒くさいですね〜(笑)
Window_Command にハンドラを設定
選択後の処理は setHandler()
メソッドで実行する関数
を指定していきます。
addCommand()
でいっぺんにやりたい感じもしますが処理を分けておくことで、表示まではウィンドウで行い入力への対処はシーンで行うことが可能になります。
方向キーによる移動やタッチでの選択なんかは Window_Selectable
がいい感じにやってくれるので、実際ハンドラを設定するのは、決定・キャンセルの処理です。
これらは Window_Selectable
で設定されています。決定は callOkHandler()
、キャンセルは
callCancelHandler ()
です。
中身はそれぞれ this.callHandler( 'ok' );
と this.callHandler( 'cancel' );
を呼んでるだけの単純なものです。
Window_Command
では callOkHandler()
が上書きされていて、addCommand()
で設定されたシンボルが呼ばれています。
オブジェクトはそのままで、追加部分を書き換えます。
// ↓ ここから追加部分
const choice = new Choice_123( 100, 20 );
this.addChild( choice );
choice.setHandler( 'command1', () => {
alert( 'ダイアログに出力' );
choice.activate();
} );
choice.setHandler( 'ok', () => {
console.log( 'コンソールへ出力!' );
choice.activate();
} );
choice.setHandler( 'cancel', () => {
choice.deactivate();
choice.close();
this._commandWindow.activate();
} );
};
choice.activate();
しているのは、連続で決定やキャンセルが実行されないように deactivate();
されてるんで、それを戻す処理です。
実際、2 は決定キーを押している間連続して発生します。
2 のシンボルに ok を指定してますが、全ての選択肢の決定で呼ばれるのではなく、設定した選択肢で発生します。
3 はシンボルの方に cancel を指定しているので、3 を選んだ場合もキャンセルキーを押した場合も実行されます。
実行内容は、新規に作ったウィンドウを閉じて選択ウィンドウを作ってみるコマンドへ選択権を渡すようにしてます。
なんかもう選択ウィンドウは十分作れるような気がしてきました!!
Window_Selectable を継承する
以上を踏まえて、Window_Selectable
を継承したクラスも作ってみました。
もう一通り解説してるんで、改めて解説するところはないと思います。
// ↓ ここから追加部分
const choice = new Selectable_123( 100, 20 );
this.addChild( choice );
choice.setHandler( 'ok', () => {
console.log( `選択肢番号: ${choice.index()}` );
choice.activate();
} );
choice.setHandler( 'cancel', () => {
choice.deactivate();
choice.close();
this._commandWindow.activate();
} );
};
class Selectable_123 extends Window_Selectable {
constructor( x, y ) {
super( x, y );
this.refresh();
this.activate();
this.select( 0 );
}
initialize( x, y ) {
this._data = [ 'いち', 'にー', 'さん' ];
super.initialize( x, y, 200, this.fittingHeight( this._data.length ) );
}
maxItems() {
return this._data.length;
}
drawItem( index ) {
const rect = this.itemRectForText( index );
this.drawText( this._data[ index ], rect.x, rect.y, rect.width );
}
}
まとめ
というわけで、基本的なウィンドウの仕組みは分かった(ような気がする)ので、ガシガシとメニューを書き換えていけると思います。
Window_BattleLog
など、全然よく分かってないやつもチラホラありますが、つどつど必要になったら調べていこうと思います。
というか、必要なんだけどよく分かってないんで調べてみました記事が書けないんだけどね。
そこで結論。