#初めに
ツクールMVアドベントカレンダーの最終日の記事です。
今回の企画に参加していただいた方々にここでお礼を申し上げます
#Window_Selectableとは?
※記事内の*hogehoge()*とカッコが付いている英語は関数名を指す。
さまざまなウィンドウ系クラスの基本となるのがWindow_Selectableクラスです。
ここでの要素とは何らかのデータの集まりである配列を指します。
この配列は基底クラス(ベースクラス)であるWindow_Selectableのメンバーには含まれません。
#改造してみよう!
自作のWindowを作る場合、以下のメソッド(メンバ関数)を書き換えれば最小の機能は作成できます。
関数名 | 何をする関数か | 引数 |
---|---|---|
initialize() | メンバ変数の設定などの初期化処理 | x(横軸の座標) y(縦軸の座標), width(ウィンドウの幅), height(ウィンドウの高さ) |
makeItemList() | 要素に変更があったとき更新を行う | 無し |
drawItem() | 項目の描画 | index(選択されている項目の番号) |
isEnabled() | ある要素についての選択の可否 グレーアウトの判定 |
item(任意の型のデータ・スキルやアイテム・コマンドなど) |
maxItems() | 選択できる項目の要素数 | 無し |
これに加えて*setHandler()*で、入力があった場合に呼び出される処理を追加する必要があります。
ハンドル名 | どのタイミングで呼び出されるか |
---|---|
ok | 決定キーが押されたとき |
cancel | 取り消しキーが押されたとき |
pageup | 次のぺージに移るとき ゲームパッドでLが押されたとき |
pagedown | 次のぺージに移るとき ゲームパッドでRが押されたとき |
基本的にこの4つと十字キーによるカーソル移動で動きます。
追加のボタン入力に対応させたい場合、*Window_Selectable.prototype.processHandling()*を改造してください。
なお、Window_Commandにも同名のメソッドがあり、機能はほぼ同じですが細かいところが異なるので注意してください。
##具体的な流れ
処理の流れですが、こんな感じなっています。
フローチャートで表現しようとしたのですが、一直線にまとめることが難しいです。
毎フレーム呼ばれる関数はupdate()ぐらいで、それ以外は必要に応じて呼び出されます。
#どうやって表示されるのか
以下は、デフォルトのメニュー画面です。
メニュー画面はScene_Menuというクラスで実装されています。
Aの部分がWindow_MenuCommandクラス、Bの部分がWindow_MenuStatusクラスによって実装されています。
先にWindow_MenuStatusから説明します。
このクラスは**$gameParty.members()**で取得できるアクターの配列を使って処理を行っています。
上記の画像の場合、members()は要素がGame_Actor型の[ハロルド,テレーゼ,マーシャ,ルキウス]という配列を返します。
そのあと、項目一つ一つに対してdrawItem(index)で描画関数が呼び出されます。
#選択処理
サンプルにしているのはスキルの一覧画面です。
(明らかにデバッグ用データなのが丸見えである)
スキルの名前がグレーアウトしていますが、これは*isEnabled()*メソッドで判定されています。
Window_SkillList.prototype.isEnabled = function(item) {
return this._actor && this._actor.canUse(item);
};
actor.canUse(item)とありますが、itemは現在選択されているスキル、アクターは選択されているアクターです。
サンプル画像の場合itemに斬撃、アクターにはハロルドが入っています。
画面上部に「武器で切り付けるよー」とありますが、これはWindow_Helpによって実装されています。
全てのWindowクラスには*setHelpWindow()*というメソッドがあり、これを利用してヘルプメッセージを実装しています。
#実際に使う
Windowだけでは動きません。
実際に使うためにはSceneクラスにWindowを動かす処理を追加する必要があります。
まずSceneクラスの*create()*をフックして自作のWindowクラスを作成して登録する必要があります。
基本的にシーンのprototype.createをフックして、自作したウィンドウクラスを作成する処理を追加します。
私が過去に作った物ですと、敵の弱点を表示するプラグインがサンプルとして小さめなので参考になるかもしれません。
また、公式のItemBook(アイテム図鑑)プラグインは機能がシンプルですので、参考になります。
#メンバ関数(メソッド)解説
再定義して使うものと、処理の内容で呼び出すメソッドが混ざっていますが、機能が関連するものを順番に並べています。
##initialize()
Window_SelectableのベースになっているWindow_Baseからの引継ぎ。
width/heightは大きさの定義も含みますが、それ以上に大きいのは描画領域の確保です。
ウィンドウの処理は最初の確保した1枚の画像に対して上から書き込むことで行われています。
そのため、Window_Baseの初期化がされていないと、textWidthなどのメソッドも呼べません。
##makeItemList()
表示するデータを作成する関数です。
デフォルトでは何もしない設定になっています。
主にrefresh()の前に呼び出されます。
基本的に、この関数でwindowの配列型のメンバーを初期化します。
- Window_ItemListは$gameParty.allItems()ですべてのアイテムを取得して、fillter()で必要なものだけに切り替えます。
- Window_SkillListも同じようなもので、this._actor.skills().で取得した後に、fillter()で種別ごとに切り替えます。
種別とは魔法・必殺技などです。
#index関連
引数にindexとして数値型一つを要求する関数をindex関連とします。
このindexはカーソルがあっている位置を指します。
##index()
Window_Selectable.prototype.index = function() {
return this._index;
};
単純な処理ですが、Window_Selectableの最も基本的で重要となるメソッドです。
現在選択されている要素が何番目かを表します。
Window_Selectableは何らかの配列で管理されたデータに対して操作を行う前提で作られています。
その時に、添え字アクセスに使うのがこのindex()の戻り値です。
##select()
indexを変更するときはselect()メソッドを使います。
Window_Selectable.prototype.select = function(index) {
this._index = index;
this._stayCount = 0;
this.ensureCursorVisible();
this.updateCursor();
this.callUpdateHelp();
};
引数で指定した数値の要素を選択します。
この関数を呼ぶと_indexの値が変更されるだけでなく、カーソルの描画の更新やヘルプウィンドウの更新が行われます。
カーソルを動かすときは必ずこの関数を使いましょう。
何も選択していない状態にするのであれば、*deselect()*を使いましょう。
select(-1)でも同じですが、メソッド呼びだして明示的にしておくとあとで助かります。
##itemRect()
四角いカーソや項目の表示座標に使うパラメータ定義しています。
戻り値はRectangle型です。
redrawItem()の際に描画されている内容をクリアするときにも使われています。
##maxPageRows()
スクロールに関連している。
##maxItems()
配列のlengthプロパティのようなものです。
正常な動作であれば、index < maxItems()が常に成り立ちます。
再定義しない場合、この関数は0を返すようになっていますので注意しましょう。
(私も何度かやらかして、描画されないと悩んでいました)
#制御関連
##activate()/deactivate()
それぞれウィンドウの更新状態を切り替える関数です。
window.activeで現在の状態を取得できます。
このactiveはpublicな変数なので、window.active=trueなどとやっても変更できます。
ただし、呼び出し履歴を把握するためにもactivate()またはdeactivate()で制御することをお勧めします。
ちなみに、windowのcallHandlerを呼び出しているprocessXX()(注:XXは何らかのシンボル名)ではthis.deactivate()している場合が多いです。
これはactiveの時だけ入力を受け取るので、それを利用して変にボタンが連打されないように調整しているためです。
*processOk()*のthis.deactivate()をコメントアウトしてみると決定音がマシンガンのように連打されるはずです。
#描画関連(WIndow_Base)
基本的にdraw~といった名前の関数です。
多くはWIndow_Baseクラスで定義されています
#描画関連(Window_Selectable専用)
選択肢の表示やその他のあれこれです。
##drawAllItems
Window_Selectable.prototype.drawAllItems = function() {
var topIndex = this.topIndex();
for (var i = 0; i < this.maxPageItems(); i++) {
var index = topIndex + i;
if (index < this.maxItems()) {
this.drawItem(index);
}
}
};
全要素を描画する場合、この関数が呼ばれます。
そのあとで*drawItem()がそれぞれ呼び出されて描画を更新します。
描画範囲はtopIndex()で指定された要素を先頭にmaxPageItems()*まで行われます。
*topIndex()*はスクロールなどが発生している時に、先頭位置を変更するために使われる関数です。
描画されていなくて変だと思ったら、*maxPageItems()*を書き換えたりこの関数自体を書き換えて描画範囲を変更することで対処できます。
*maxPageItems()*はこんな関数なので、スクロールしないのであれば、割と適当に変更しても大丈夫です。
Window_Selectable.prototype.maxPageItems = function() {
return this.maxPageRows() * this.maxCols();
};
##maxCols
Window_Selectable.prototype.maxCols = function() {
return 1;
};
ウィンドウの横方向の要素数を表します。
例えばWindow_Commandを継承したものはこれが1なので横方向の要素は1です。
メニュー画面の「アイテム」「スキル」などとある部分がいい例ですね。
一方でWindow_ItemListは2を返すため、横方向の要素が2個ずつになっています。
割と改造しやすい部分です。
#描画関連
##drawText()
最も基本となるテキスト表示関数です。
アイコンや制御文字によるコントロールは行いません。
その場合、drawTextEx()を使いましょう。
文字列・XY座標・描画幅の順で指定します。
描画幅を指定すると、範囲内に収まるように文字を小さくします。
最後のalignは右寄せ・左寄せ・中央ぞろえを設定するのに使います。
Window_Base.prototype.drawText = function(text, x, y, maxWidth, align) {
this.contents.drawText(text, x, y, maxWidth, this.lineHeight(), align);
};
##textColor()
normalColor()などのtextColor()を呼び出すメソッドが複数あります。
数が多いので詳細は省略します。
色を変更する場合changeTextColor()に上記関数の戻り値を渡して色を設定します。
色を変更した場合、再度changeTextColor()を呼び出すまで変更された値は維持されます。
Window_MyCustom.prototype.maxCols = function() {
this.changeTextColor(this.normalColor());
this.drawText(...);
};
##changePaintOpacity()
選択できない項目をグレーアウトさせるときに使われています。
引数はboolean型の変数を一つで、trueなら通常色、falseならグレーアウトさせます・
理由はよくわからないのですが、先に描画先を何らかの方法で塗りつぶしてしまっていると、この関数で設定したグレーアウトが機能しないようです。
次の1回分だけ描画設定の変更が有効なのでしょうか?
よくわからないです。
#おまけ
本編に書くと長くなるので省略しましたが、Window_Selectableを継承してES5のクラス構文で作ることも可能です。
#終わりに
以上、割と長めでしたがWindow_Selectable徹底解説でした。
これ以外にも要素はあるのですが、自作メニューを作るうえで最低限必要な要素に絞り込みました。
分からなかった公式フォーラムやツクマテなどで積極的に質問しましょう。
プラグインの書き方に関する質問はいい回答が返ってくることが多いです。
くれぐれもプラグインばかり作ってゲームが完成しないということにならないよう気を付けてください。
(自分の状態から目をそらしつつ)