⚔️

『RPGツクールMZ』のレイアウト周りのコードを読んでみた

2021/05/18に公開

『RPGツクールMZ』関連記事 目次

ウィンドウ表示周りをずーっと調べてるんだけど、なかなか終わりが見えない。
ので、この辺でひとまずいろいろ疑問があっても終わることにする。
なのでちょっと長めに、これまで溜め込んでた調査情報を公開。

コードを追っていく

前回はレイアウト用数値を表形式でガーッと並べたが、今回はその中身を見ていく。今までに書いたものと重複するところも多いが、僕は気にしない、君らも気にするな!

以下、『RPGツクールMZ』プラグインを作ろうという人には有用な情報も含まれるが、だいたいは「それ、あなたの感想ですよね」みたいな文章なので、悪しからず。
いやいや、感想大事よ。

画面サイズ

[データベース]-[システム2]-[高度な設定]に画面サイズ関連の項目がある。次の表の値が規定値(デフォルト)。

名前 大きさ
画面の幅 816
画面の高さ 624
UIエリアの幅 816
UIエリアの高さ 624

これ、自分で設定できるようになってるんだから別にこの規定値なんか気にしなくても自由につければよくないの?って思うのではないだろうか。
僕もそう思うんだけど、実際の『RPGツクールMZ』の画面レイアウトはこのサイズを前提にして設計してあるものが多く、大きさを変えるとバランスが悪いぐらいならまだマシで、操作やレイアウトが破綻する場合すらある。
ちまちまとズレを修正して行く手もあるが、この規定の大きさと新規の大きさの比率を計算して自動修正する場合、絶対にこの規定サイズの値が必要になるのだ。

ところで、UIエリアってなんだろうか。
ヘルプには次のように書いてある。

UIエリアの幅・UIエリア高さ
UIエリアの幅と高さを設定します。

…説明してないよね。小泉進次郎話法(トートロジー)だよね。
なので僕が説明しようではないかっ!

UIエリアはウィンドウやボタンといったユーザインタフェース(UI)部品が配置される領域のこと。
ワイド画面(16:9)でメッセージウィンドウが横幅いっぱいだと、文字に対してウィンドウがスッカスカになりがちなので、幅を縮めておく、みたいな感じで使う。
ただ、メニューなんかは広い方が都合がいい場合も多いので、画面サイズ・UIエリアの他にメッセージウィンドウ幅も設定できるようにして欲しかったなぁ…。

なお、僕の自作プラグイン TF_VectorWindow.js なら設定できますけどね(フンス!)

なお、UIエリアは画面上下・左右方向ともに中央にセンタリングされる。
これはxy座標も設定させて欲しかった。メニューを寄せて、常に立ち絵を表示するみたいにしたいこともあるだろうに。
工画堂『コズミックソルジャー』リスペクト的に!

boxWidth・boxHeight は8px狭い!!

そして、画面を自動レイアウトするプラグインを作るときに注意して欲しいのだが、実際のUI表示エリアはこの[UIエリアの幅]と[UIエリア高さ]に設定した値より8ピクセル少ないのだっ…なんでそんな変なことを。
原因はコイツ!!

Scene_Boot.prototype.adjustBoxSize = function() {
    const uiAreaWidth = $dataSystem.advanced.uiAreaWidth;
    const uiAreaHeight = $dataSystem.advanced.uiAreaHeight;
    const boxMargin = 4;
    Graphics.boxWidth = uiAreaWidth - boxMargin * 2;
    Graphics.boxHeight = uiAreaHeight - boxMargin * 2;
};

なんだよこの boxMargin ってのはヨォ!!

いや、UIを画面ぴったりにすると枠と画面端がくっついて、枠が枠らしく見えずレイアウト的に良くない、というのは分かる。
だったら[UIエリアの幅]と[UIエリア高さ]の値を最初から8ピクセル狭く設定しておけばよかろうなのだ!!

スクリプト 大きさ 説明
$dataSystem.advanced.screenWidth 816 画面の幅
$dataSystem.advanced.screenHeight 624 画面の高さ
Graphics.width 816 画面の幅
Graphics.height 624 画面の高さ
$dataSystem.advanced.uiAreaWidth 816 UIエリアの幅
$dataSystem.advanced.uiAreaHeight 624 UIエリアの高さ
Graphics.boxWidth 808 UIエリアの幅 - 8
Graphics.boxHeight 616 UIエリアの高さ - 8

という感じになっているので注意だ!!
僕はこれに気づかなくて2日ぐらい悩んだ。Graphics の値を Scene_Boot で変えてるとは思わないじゃない?

タイルの大きさ

マップ画面に表示されるタイル、一枚の大きさはどのくらいだろうか。

スクリプト 大きさ 説明
$gameMap.tileWidth() 48 タイル幅
$gameMap.tileHeight() 48 タイル高さ

ドット絵のタイル(マップチップ)サイズとしては、かなり大きい。
解像度が高いゲームでもタイル一枚の大きさは16×16とかせいぜい32×32に押さえておいて、細かく組み合わせることが多いような(個人の感想です)
別の言い方をすると『RPGツクールMZ』のタイルは高解像度どころか、かなり大雑把。
例えば実際に壁や床に貼るタイルは10cm程度の大きさが普通だが、ツクールは50cmというと雰囲気がつかめるだろうか。
デケェよ、使いづれぇよ!顔が隠れるよ!! タイルの上に大盛りパスタ乗せたいのかよ!!! みたいな感じだ。
個人的にはタイルサイズは24×24でいいんじゃないかなぁ。そんでキャラは48×72でいいんじゃないかなぁ。と思う。

スクリプト 大きさ 説明
Tilemap.prototype._tileWidth 48 タイル幅
Tilemap.prototype._tileHeight 48 タイル高さ

あれ?さっきも同じ表を書いたよね。いや、なんかスクリプトが違うけど何?
そう思ってらっしゃることだろう。
なんと、同じタイルの48という数字が、別の場所で定義されているのだだだっ!!
へにゃーん。萎えるぅ。

この48って数字、タイルに関するものに限らず、あちこちで出てくる。キャラの規定サイズが48×48ってのもそうだが、画面周りにちょいちょいある。
レイアウト単位をタイルに合わせるという意味では分かるが、タイルと別に Graphics.unitWidth みたいなの用意して使い回しませんこと?
直(リテラル)に48って書かれるとタイルなんだかキャラなんだか、レイアウトの最小単位だかわかんないんで困る。

あっ! フキダシのサイズも48×48でしたよ。もーっ!!

Sprite_Balloon.prototype.updateFrame = function() {
    const w = 48;
    const h = 48;
    const sx = this.frameIndex() * w;
    const sy = (this._balloonId - 1) * h;
    this.setFrame(sx, sy, w, h);
};

直前に定数に入れてるだけの、ほぼ直書き(リテラル)。ヒドイ。
なんでアイコンや[顔]画像みたいに ImageManager に入ってないの!!!

だいたいコアスクリプトを「48」で検索して、あちこちで見つかる時点でおかしい。
これって「同じものを指している値は定数を使って一箇所にまとめる」という、わりと「初歩のプログラミング」で出てくるテクニックを使ってないということ。
ちなみにこの値の直書きスタイルのプログラミングを「ハードコーディング(Wikipedia)」と言って、プログラミング入門の第1章に「ヤメろ!!」ときつく書いてあるのが定番なのだ。
僕の作ってるプラグインにしても大概ハードコーディングだが、ここまでひどくはないよ!!

結局いろんな場所の48が何に関係あるのかないのか分からないこともあって、タイルサイズに関連するプラグイン作るの、無駄に難易度が上がっている。
ちなみに「タイルサイズに関連するプラグイン」ってマップのグラフィック関連なら、ほぼ全部DE・SU・YO!!

あと画面を拡大・縮小したときに、タイルサイズが対応してないのが困る。
例えば画面を2倍に拡大しているときに、素材の大きさである48が必要な箇所と、画面上の大きさである96が必要な箇所がある。
これが雑なことに両方とも48で作ってあって、拡大中に画像を動かすと、ずれちゃって大変なことになる。
クローンヤクザじゃなくても「ザッケンナコラー!」ぐらい言いたくなる。

タイル数に換算すると?

さて画面サイズをマップのタイルを単位とすると、規定値は次の通り。

スクリプト 大きさ 説明
$gameMap.screenTileX() 17 一画面中の横のタイル数
$gameMap.screenTileY() 13 一画面中の縦のタイル数

コードを見ると次のように、ちょっと不思議な処理がある。

Game_Map.prototype.screenTileX = function() {
    return Math.round( ( Graphics.width / this.tileWidth() ) * 16 ) / 16;
};

*16して丸めたものをまた/16しているということは、 1/16タイル単位で切りそろえられていることになる。
ピクセル数でいうと48/16 = 3 ピクセルと綺麗に割れる数なので、動かした時に画像が綺麗に出力されるのだろうか。
1/48していないのがなぜかは分からないが、小数点以下の桁数的にブレが出るのかも。

逆にいうとこういう部分の処理まで 48ピクセルの大きさに最適化してるということは、タイルの大きさを自由にユーザに決めてもらおうという気がカケラもないということですね。分かります(キリン並感)
多分これタイルサイズどころか、画面サイズが3の倍数じゃなくなるだけでも、いまいち意味がなくなる処理のような気がする。

ウィンドウ

前提として『RPGツクールMZ』はウィンドウの大きさを決めて生成するのはシーン(Scene_Baseと子孫クラス)が担当している。
なので、ウィンドウが生成される前に大きさが決まるのが基本。
だいたい create〇〇Window() とそこで呼ばれる 〇〇WindowRect() がセットになっている。

ウィンドウ幅

自由に決められはするのだが、実際の幅の決め方は大まかに3種類。

  1. [UIエリアの幅]いっぱいから-8引いたやつ。つまり boxWidth
  2. mainCommandWidth()メソッドの値。
  3. boxWidth から2.のウィンドウ幅を引いた値。
スクリプト 大きさ 説明
Scene_Base.prototype.mainCommandWidth() 240 コマンドウィンドウの横幅

値が固定なのはもう「はいはい、ツクール、ツクール」って感じになってきた。
にしても、レイアウト関連のプロパティ・メソッド、分散しすぎ。
LayoutManager みたいなのに値を集約すべきだったんじゃないかなぁ。

ウィンドウ高さ

これも自由に決められはするのだが、決め方は大まかに2種類。

  1. [UIエリアの高さ]いっぱいから-8引いたやつ。つまり boxHeight。から別の部品の高さを引いた値。
  2. 表示される文字の行数・メニュー項目の個数で決定される値。

「別のUI部品」というのは、ヘルプなど他のウィンドウか、タッチ環境向けのボタン領域。
『RPGツクールMZ』には[UIエリアの高さ]いっぱいのウィンドウはなかった気がする。
だいたいは mainAreaHeight()というヘルプとボタンの高さを抜いた高さをもとに作る。

で、行数・個数から高さを算出するのが Scene_Base.prototype.calcWindowHeight()メソッド。
この calcWindowHeight()というか、fittingHeight()メソッドがちょっとおかしい。
ではコードをご覧じあれ。

Scene_Base.prototype.calcWindowHeight = function( numLines, selectable ) {
    if( selectable ) {
        return Window_Selectable.prototype.fittingHeight( numLines );
    } else {
        return Window_Base.prototype.fittingHeight( numLines );
    }
};

条件分けされて Window_Base.prototype.fittingHeight()Window_Selectable.prototype.fittingHeight() の2箇所が呼ばれている。selectable引数はUI部品が選択されるウィンドウか、ってことを表してそうだ。
なるほど Window_Base は行高さ、Window_Selectable はメニュー項目高さに応じたウィンドウ高さを返すんだな、と思うだろうがさにあらず。
Window_Selectable.prototype.fittingHeight() は定義されてなくて、現実は両方とも Window_Base.prototype.fittingHeight() が実行されてる。
ハァ?

まず prototype のメソッドがインスタンス化されずに直に呼ばれているのが気持ち悪いが、MVとの互換性という名の手抜きかと思う。
それを置いても意味不明度が限界を超えている。
Window_Base.prototype.fittingHeight() のコードがこれ。

Window_Base.prototype.fittingHeight = function( numLines ) {
    return numLines * this.itemHeight() + $gameSystem.windowPadding() * 2;
};

itemHeight() のほう呼ぶんかい!! というね。selectable引数が false の時 Window_Base.prototype.fittingHeight() が呼ばれていた。
ならば流石にここは、選択しない表示のみの文字領域の高さである lineHeight() 呼ばないとおかしくないかな?selectable引数が false なんだよ!
なんとなく「 lineHeight()呼んだら上手くいかなかったけど itemHeight() 呼んだら上手く行った」ぐらいのノリでコードが書かれた空気を感じる。
itemHeight()Window_Selectable でオーバーライドされてるから結果的に動くんだけど、たまたま動いてるだけだよねこれ。

ウィンドウ詰め物

さて先ほどの fittingHeight() の中に windowPadding() というメソッドがあった。

スクリプト 大きさ 説明
$gameSystem.windowPadding() 12 詰め物

padding などの隙間を調整する値はレイアウトや、ユーザインタフェース的にはかなり繊細で、フォントの大きさはもちろん色やスタイル、周囲の情報や与えたい印象などを鑑みて調整する必要がある。
それが固定値というの、ないよりマシとないほうがマシの絶妙なバランスの上にある感じだ。

で、各ウィンドウは paddingプロパティを持っていて、updatePadding() で値が設定されている。

Window_Base.prototype.updatePadding = function() {
    this.padding = $gameSystem.windowPadding();
};

つまり『RPGツクールMZ』のウィンドウの paddingは基本 12 ってこと。
Window_BattleStatus だけが updatePadding() をオーバーライドしており、その値は 8。
updatePadding() は頻繁に呼ぶみたいな名前だが、実際は初期化で一回呼ばれるだけ。

また$gameSystem.windowPadding()Scene_Name.prototype.editWindowRect() で例外的に使われているものの、ほとんど先ほど代入した paddingfittingHeight() を通して使っている。

いちいち padding に代入しないで $gameSystem.windowPadding() を直に使って行けばいいんじゃないの、という感想もあるとは思う。

でも個々のインスタンスのプロパティにしておけば、あとから値を変えやすいからね。
どーやって変えるかって?[スクリプト]コマンドやプラグインを使えば変えられるよ! 簡単だね!

じゃねーよ!!ツクールがJavaScript使うこと前提に設計しちゃダメだろ!!
ついでに言うと、JavaScript使うこと前提ならもっと整理されたクラス設計とリファレンス用意してからにしろーっ!!!
はぁはぁはぁ。まぁ…あれだ、実際の意図はよくわからないけど、どう転んでも良い作りではない。

ウィンドウ内容

名前 大きさ 説明
Window.prototype.innerWidth ※1 内容の幅
Window.prototype.innerHeight ※2 内容の高さ
Window_Base.prototype.contentsWidth() ※1 内容の幅
Window_Base.prototype.contentsHeight() ※2 内容の高さ

※1 this._width - this._padding * 2
※2 this._height - this._padding * 2

要は、ウィンドウ幅や高さから詰め物分を引いた値だ。マイナス値は返ってこなくて最低で0。
Window_Base のメソッドは単純に inner〇〇 をそのまま返してるが、最初から contents〇〇() を定義しちゃいけなかったんだろうか?
ちょっと謎。Pixi.js との絡みかなぁ?Pixi.js 履修してないから分からんけど!

それから内容関連のメソッドとして baseTextRect() というのが用意してある。

Window_Base.prototype.baseTextRect = function() {
    const rect = new Rectangle( 0, 0, this.innerWidth, this.innerHeight );
    rect.pad( -this.itemPadding(), 0 );
    return rect;
};

内容の横方向を itemPadding() ぶん縮めた Rectangle だ。文字を表示するとき左右にちょっと余裕を入れるためのもの。
Window_HelpWindow_NameBoxWindow_ScrollText で使われている。
なぜか Window_Message では使われておらず、独自に対処してる…

Window_Message.prototype.newLineX = function( textState ) {
    const faceExists = $gameMessage.faceName() !== "";
    const faceWidth = ImageManager.faceWidth;
    const spacing = 20;
    const margin = faceExists ? faceWidth + spacing : 4;
    return textState.rtl ? this.innerWidth - margin : margin;
};

顔画像のあるなしで切り替えるためのようだけど、またここで20とか4とかの突然出てきた数字でその場限りの対処がされている。
ここ 4じゃなくて itemPadding()(8)を使わないのなんなん。4ピクセルそんな重要か?むしろ統一感が悪くなってるように思うんだけど。
もしかしてMVと仕様が違うと行末位置が変わっちゃうから、ここだけ4ピクセルにした、とかかな?
itemPadding() はMZで導入されたメソッドだからなー。
そもそもで言えば itemPadding() は項目用のメソッドだから lineHeadSpace() みたいなのがあるべきなんだろうけど。

ヘルプウィンドウ

Scene_MenuBase には選択した項目に関するヘルプが表示されるヘルプウィンドウ(_helpWindow)が用意されている。
これ、位置も大きさも固定なんだよね。

isBottomHelpMode ()がヘルプを下に表示するかを真偽値で返すメソッドなんだが、これの返り値 true に固定。
[データベース]-[システム2]-[高度な設定]に入れるつもりだったけど間に合わなかった、という雰囲気を感じる。
発売されてから結構経っているのにアップデートで入る気配もないというのは、これまた謎だ。
となると間に合わなかったのではなく、何らかの理由でヤメたということになる。
このメソッド改造して false 返すようにすると、ヘルプの位置を返すメソッド helpAreaTop()helpWindowRect()helpAreaBottom() は、isBottomHelpMode ()を元にちゃんと値が変わる。
なおhelpAreaHeight()は高さ2行に固定だが、これを1行に改造しても、それぞれちゃんと動く。
つまり、ちゃんとヘルプは上に行くし大きさに合わせて他のウィンドウサイズは変わるし、ボタンとも重ならないように配置されるんで、もうこれ99%動いてる。
ここまでやってるのに…なんでこのオプション、バージョンアップ時に追加されないんだろ。

ちなみに isBottomHelpMode () って名前おかしくないだろうか。普通の感覚では isTopHelpMode () と上に描かれる方を true にしたくないか?
どーもイベント設定の[プライオリティ]のプルダウンや[移動ルート]の方向コマンドボタン、レイヤーメニューなどなど、上下逆なんだよねツクールって。たまにそういう感覚の人がいたりもするだろうけど、複数人で作ってて最後まで上下逆で製品になるの、相当変だと思う。

文章

今度は内容に表示する文章を見ていく。

フォントサイズ

[データベース]-[システム2]-[高度な設定]-[フォントサイズ]でフォントの大きさを指定できる。
ピクセル数…だよね。さぁ、僕らのヘルプくんなら期待に応えてくれることだろう。

フォントサイズ
文字の大きさを設定します。

見事な小泉進次郎っぷり!これはもう期待以上。
じゃねーよ!! 大きさの単位を書けやぁゴルァ!! (`∀')/ ←これはターンイーじゃなくてターンエーでは?(そこに気づくとは…)
公式からの正式な情報はどこにもないと思うんだが、個人的権限(とくにない)によってピクセルということにしておく。
少なくとも S・M・L みたいな指定ではなさそう。

ま、とにかく。フォントサイズはタイルサイズと同様に画面レイアウトを規定する基本単位だ。
次の表は、取り出すためのスクリプトと規定値。

スクリプト 大きさ 説明
$dataSystem.advanced.fontSize 26 フォントサイズ
$gameSystem.mainFontSize() 26 フォントサイズ

なんで二種類あるの?というのはおそらく mainFontSize() とメソッドにすることでプラグインで上書きしやすくしようということかと。
$dataSystem のような $data〇〇 系の大域(グローバル)変数はデータベース(JSONファイル)の内容が入っているもので、意味的には定数。
なので定数は書き換えられない、という発想ではないかな。
先ほどのタイルサイズと違って、$gameSystem.mainFontSize() の中で $dataSystem.advanced.fontSize が呼ばれているので両者の値は共通しており、この辺は設計として納得いくところ。

Game_System.prototype.mainFontSize = function() {
    return $dataSystem.advanced.fontSize;
};

advancedは無駄に階層を深くしてるだけに思えるが、『RPGツクールMV』のプラグインが fontSize を使っていたら誤動作することを懸念しての仕様と想像する。
個人的には「そんな行儀の悪いプラグインは誤動作させておけ!!」と思うが。

フォントサイズは制御文字 \{\} で大きくしたり、小さくしたりでき、その変更単位は次の通り。

スクリプト 大きさ 説明
Window_Base.prototype.makeFontBigger() +12 \{ 最大108ピクセル
Window_Base.prototype.makeFontSmaller() -12 \} 最小12ピクセル

規定値26なんだよ!一段階で12も変わるとか雑すぎる!! ほとんど半分!!!
そしてそこはもちろんツクール、段階の調整はできず固定値!!
幸い『RPGツクールMZ』は \FS[n] が導入されたんで細かな制御もできるんだが。
あと最大と最小の判定も雑なんだけど、気になる人は実際のコード見てね。

他の箇所でも音量の調整が20%刻みとか、数字も整数しか使えないとか、単位が雑。
「理解しやすいことと大雑把なこと」をごっちゃにしてると思います。Gotcha Gotcha Games だけに!!
例えるなら、どんぶり一杯で多すぎるなら下はゼロ杯しかなく、食い足りないならどんぶり二杯いくしかない!
「わかりやすいでしょ!」「んなわけあるか!!」みたいな。

行高さ

スクリプト 大きさ 説明
Window_Base.prototype.lineHeight() 36 行高さ
Window_Base.prototype.lineHeight = function() {
    return 36;
};

『RPGツクールMZ』ってフォントサイズをエディタで設定できるのに、なんで行の高さが固定なのか?
これを呼んでいるんだから、当然メニュー項目も高さ固定ということになる。
[行高さ]って設定項目を作るか、せめて「行高さ = フォント×1.5」という感じでないとダメじゃね?

項目

項目ってなんだって話なんだが、メニューの項目とかコマンド項目とかそういう「選択肢」のこと。
これがややこしいというかコードの理解を妨げるのが、英語表記だと item だということ。
RPGのプログラムに出てくる item が選択肢を意味するって直感で分かる?分かんないよね。
絶対アイテムって言ったら、道具のことだと思うよね。実際コアスクリプトの中でアイテムの意味でも item が使われてて、カオス!! ゲットマイレイジ!!
プログラム的には、menuItem とか使われがちではあるんだけど、ことツクールでは遠慮して、selector とか choice とか別の単語当てて欲しかったわー。

項目高さ

文字が表示される場合の高さは二種類あって、ひとつが先ほどの文字表示の際の行高さで、もう一つが選択肢として出てくる項目の高さ。

スクリプト 大きさ 説明
Window_Base.prototype.itemHeight() 36 メニュー項目高さ
Window_Base.prototype.itemHeight = function() {
    return this.lineHeight();
};

一種類やんけ……lineHeight()呼んでるだけやんけーーーっ!!
いやいやいや、メニュー項目高さと行高さは別に設定なさいよ。
メニュー項目はタッチ操作に対応しなきゃいけないから、フォントサイズが小さくてもある程度は大きな数字が必要でしょ!

さてつまり Window_Base のメニュー項目の高さは、フォントの規定値とは関係なく36なわけ。
そして Window_Selectableで次のように上書き設定(オーバーライド)されている。

Window_Selectable.prototype.itemHeight = function() {
    return Window_Scrollable.prototype.itemHeight.call( this ) + 8;
};

雑に +8 されてるんだが、単なる数字なんで意図が分からない。
どーもツクールは、こういうのが多くて「分からん…なんも分からん」って宇宙猫みたいな顔になる。
さっき書いた通りタッチ操作対応の+8なんだろうけど。それずっとコード追ってやっと「そうじゃないかな」と予想できる程度で、実際にどんな意図なのかは今もわからないからね。
そもそもタッチ操作対応が目的だったら、最小限必要な大きさを確保したらそれ以上は lineHeight() と同じ大きさでいいのだ。

先ほど指摘したフォントサイズに合わせてないというのは置いといたとしても、 itemHeight() 最初から36+8の44に設定してよくない?なんで Window_Selectableまで継承を待って拡大してんの??

項目詰め物

先ほど itemHeight()で突然 +8されてたんだけど、あるのよね。メニュー項目の詰め物の値返してくれる itemPadding()メソッド。
これ使ってくれたら意図はちょっとは分かりやすくなるのに。

スクリプト 大きさ 説明
Window_Base.prototype.itemPadding() 8 メニュー項目詰め物

これは割といろんなところで使われている数値で、以下のような場所で使われている。
軽い気持ちで調べたら思いのほか使われているところが多くてビビった。

Window_Base.prototype.baseTextRect
Window_Selectable.prototype.itemRectWithPadding
Window_EquipStatus.prototype.drawAllParams
Window_EquipStatus.prototype.drawParamName
Window_EquipStatus.prototype.paramX
Window_ShopNumber.prototype.drawCurrentItemName
Window_ShopNumber.prototype.drawNumber
Window_ShopNumber.prototype.drawHorzLine
Window_ShopNumber.prototype.drawTotalPrice
Window_ShopNumber.prototype.cursorWidth
Window_ShopNumber.prototype.cursorX
Window_ShopStatus.prototype.refresh
Window_ShopStatus.prototype.drawPossession
Window_ShopStatus.prototype.drawActorEquipInfo
Window_ShopStatus.prototype.drawActorParamChange
Window_NameBox.prototype.windowWidth
Window_ChoiceList.prototype.maxChoiceWidth
Window_BattleLog.prototype.lineRect

…若干、使い所違うんじゃないかと思うものもあるが、野放図にリテラルで値が設定されるよりずっといい。
一定の値が使われていると、レイアウトに安定感が出るし。

ただ、値が8に固定というのはドット単位のレイアウトの感覚かと思う。
フルカラー高解像度に対応しているけど、中身の考え方はパレットカラー低解像度なままの古い感覚という感じ。
いまどき8の倍数にこだわる必要もないので、エディタで設定できる値にしてほしい。
すごいギリギリの部分だと8の倍数の意味もあるとは思うが、ツクールがそんなギリギリを攻めなくていいし、実際のツクールがギリギリを攻める実装にもなってないので。

表項目詰め物

縦横に項目が並ぶ表形式の場合、隙間は列と行で隙間が設定されている。

スクリプト 大きさ 説明
Window_Selectable.prototype.colSpacing() 8 列隙間
Window_Selectable.prototype.rowSpacing() 4 行隙間
Window_BattleStatus.prototype.rowSpacing() 0 行隙間
Window_EquipItem.prototype.colSpacing() 8 列隙間
Window_EquipStatus.prototype.colSpacing() 0 列隙間
Window_Gold.prototype.colSpacing() 0 列隙間
Window_ItemList.prototype.colSpacing() 16 列隙間
Window_SkillList.prototype.colSpacing() 16 列隙間

rowSpacing()colSpacing()Window_Selectable が持っているので、子孫クラスは全部持っていて、この表に出てくるクラスも全部 Window_Selectable の子孫。

項目矩形範囲

メニュー項目の矩形範囲はちょっとややこしいが Window_Selectable の場合、次のようなコードで決定されている。
itemWidth()innerWidth(ウィンドウの内容表示領域幅)を項目列数で割った大きさ。
itemHeight() 44、colSpacing()8、rowSpacing() 4、になっている。
で、引数に index 渡してあるのは、項目のxy位置を算出するため。

Window_Selectable.prototype.itemRect = function( index ) {
    const maxCols = this.maxCols();
    const itemWidth = this.itemWidth();
    const itemHeight = this.itemHeight();
    const colSpacing = this.colSpacing();
    const rowSpacing = this.rowSpacing();
    const col = index % maxCols;
    const row = Math.floor( index / maxCols );
    const x = col * itemWidth + colSpacing / 2 - this.scrollBaseX();
    const y = row * itemHeight + rowSpacing / 2 - this.scrollBaseY();
    const width = itemWidth - colSpacing;
    const height = itemHeight - rowSpacing;
    return new Rectangle( x, y, width, height );
};

itemRect() は、各クラスで再定義(オーバーライド)されていて、値は一定していない。
と言って、ユーザがエディタ設定で指定できるわけでもないので、クラスごとに固定という言い方もできる。

スクリプト 説明
Window_Selectable.prototype.itemRect() メニュー項目矩形範囲
Window_Selectable.prototype.itemRectWithPadding() 項目矩形範囲(詰め物抜き)
Window_Selectable.prototype.itemLineRect() 項目行矩形範囲

えーと itemRectWithPadding() 次のコードを見ると横方向の大きさを縮めてる。
意味的には itemInnerRect() なんだけど、なんで itemRectWithPadding() なのか。担当者疲れてたのかな。付けるにしても itemRectWithoutPadding() かと。
他にも Rectangle の大きさ縮めるのになんで pad()メソッド使ってないの?とか、Window_Selectablepadding プロパティ持ってるんだから、意味の異なる値の局所(ローカル)変数に padding 使うの良くないとか、この短いコードで気になる点が多すぎる。

Window_Selectable.prototype.itemRectWithPadding = function( index ) {
    const rect = this.itemRect( index );
    const padding = this.itemPadding();
    rect.x += padding;
    rect.width -= padding * 2;
    return rect;
};

僕が書くなら次のコードのように書くんだけど(そもそも item とかの識別子から変えたいけど、それは置いといて)

Window_Selectable.prototype.itemRectWithPadding= function( index ) {
    return this.itemRect( index ).pad( -this.itemPadding(), 0 );
};

あらゆるコードにはリファクタリングの余地が残っているものだから、後出しでリファクタリングして偉そうにするの良くない感じはするけど。
メソッド用意してるんだから使ったほうが意図が分かりやすくなるし、ここで使わないならなんで pad() メソッド用意したのよって話だし。

itemLineRect()itemRectWithPadding() を元にした文字列用の矩形範囲。なんか Line って書かれると行っぽいが項目単体の大きさだ。
これもMVの類似メソッドが使ってた itemRectForText()の方が名称は適切だったような。
このコードも謎いんで、ちょっと見て欲しい。

Window_Selectable.prototype.itemLineRect = function( index ) {
    const rect = this.itemRectWithPadding( index );
    const padding = ( rect.height - this.lineHeight() ) / 2;
    rect.y += padding;
    rect.height -= padding * 2;
    return rect;
};

そもそもこの計算だと…ちょっと丁寧に書きますけど、
rect.height -= padding * 2;
rect.height = rect.height - padding * 2;
rect.height = rect.height - ( rect.height - this.lineHeight() ) / 2 * 2;
rect.height = rect.height - ( rect.height - this.lineHeight() );
rect.height = rect.height - rect.height + this.lineHeight();
rect.height = this.lineHeight();
ではないか?計算まるきり無駄じゃね?
あと半分にした値は丸めなくていいの?

…これは疲れてたというよりMZで Window_Selectable 作った人、プログラム経験浅い & 英語苦手なのでは?

項目幅

んで今度は、項目の横幅。
itemWidth()ってのがあって、Window_Base では innerWidth をそのまま返す。
Window_Selectableでは innerWidthmaxCols() で割った値を返す。
Window_NumberInput では固定値の48。
要するに、各クラスでまちまち。
このへんは、まちまちで当たり前ではあるけど、横の項目数ぐらいはエディタで設定させて欲しかった。
ワイド画面で2項目だと間延びして間延びして。もうスキル名「あまかけるりゅうのひらめき」ぐらいにしないと間が持たない。

ボタン

『RPGツクールMZ』はタッチ入力により力を入れていて、その一環としてキャンセルなどのボタンを表示している。
実に付け焼き刃って感じだが、ないよりはかかなり良い。
なにせ『RPGツクールMV』は「キャンセルが二本指タップということが分からずに詰む」というのがスマホプレイの最難関だったからな。

ボタン領域

名前 大きさ 説明
Sprite_Button.prototype.blockWidth() 48 ボタンの単位幅
Sprite_Button.prototype.blockHeight() 48 ボタンの単位高さ

これは、ボタンのサイズを決めるための単位。
タイルと同じ値ではあるが、別のことに使うので別に定義されているのは問題ない。
しかしなんで Sprite_Button のインスタンスメソッドに定義してんのか。
プラグインでクラスごとにレイアウト変更できるとか、インスタンスメソッドに定義するメリットもあるが、静的なメソッドは必要でしょ。

そこで問題となるのは以下のコード。

Scene_Base.prototype.buttonY = function() {
    const offsetY = Math.floor((this.buttonAreaHeight() - 48) / 2);
    return this.buttonAreaTop() + offsetY;
};

そこの -48 どっから持ってきた、ああん!! コラ!!
ボタンの高さだとは思うんだけど「とにかく納期に間に合わせた」感が強いコードだ。
さっきせっかく設定してた blockHeight() がインスタンスメソッドなんで使えないとゆーね。
Sprite_Button.prototype.blockHeight()prototype のメソッドを呼ぶ手もなくはないけど、お行儀悪いよね。

Scene_Base には、ヘルプと同様に表示の上下位置を指定する isBottomButtonMode ()が用意されている。
例によって返るのは固定値だっ! ご一緒に「はいはいツクール、ツクールゥ!」(エヴァの次回予告のように朗らかに)

そして、buttonAreaBottom()buttonAreaHeight()buttonAreaBottom()buttonY()の各メソッドは isBottomButtonMode () の値を変えるとちゃんとそれに合わせた値を返す。
……ヘルプと一緒で、なんでこれバージョンアップでエディタでの設定値に追加されないんだろうか???

まとめ

以上のように、設定箇所がとっちらかっていたり、エディタ側で設定できない死に機能が沢山あったり、挙句に随所にリテラル書いて対処していたりしている。
とまぁ『RPGツクールMZ』のレイアウトは、その場しのぎの行き当たりばったり感あふるる実装となっている。

本気でレイアウトをしたいなら、スタイルシート的な統一した仕組みも必要だと思う。
ウィンドウに setStyle() メソッドがあって WindowStyleクラスのオブジェクトを渡すみたいな。

ただこれでも『RPGツクールMV』よりかなり整理されてるのだ。
これより混沌としてたのかと思うと、もう思い出したくない。
世の中は少しずつ良くなってる、そう思わないとやっていけないこともあるんじゃなかろうか。

ちなみに、さらに装備画面であるとかの細かな部分ではよりダイナミックかつスタティックに(笑)レイアウト調整がされている。
これ以上コードを追うの面倒くさくなったので、実態は君の目で確かめて欲しい!(丸投げ)

エンジョイツクールライフ!!

Discussion