【済】『TMSimpleWindow.js』の改変依頼

KJC

ユーザー
https://plugin.fungamemake.com/archives/2312
にてダウンロードしたTMSimpleWindow.jsについての要望です。
showWindow 1 0 24 160 72 テスト
 大きさが 160 * 72 のウィンドウ 1 番に テスト という文字列を描画して
 座標(0, 24) に表示します。
 ウィンドウ番号は 1 ~ 8 (初期設定) が使用できます。
 文字列には \C[2] などの制御文字の他に \n (改行)が使用できます。
プラグイン内には上記のような使用方法が記載されているのですが、
"文字列"以外の、座標などの部分を変数の値で(\v[n]といった具合で)指定できればと思っております。
showWindow、showNoFrameWindow、showNoBackWindow、eraseWindowの各プラグインコマンドにて、
もし可能でありましたら、変数の値も反映させられるようなプラグインの改変をお願いいたします。
 

げれげれ

ユーザー
こんにちばんわ~

"文字列"以外の、座標などの部分を変数の値で(\v[n]といった具合で)指定できれば
丁度良い課題だったのでやってみました。できました。

ご希望の仕様が
①プラグインコマンドでゲーム変数が使える
②更にそのゲーム変数の変更がウインドウに随時反映される(変数の値を変えたらウインドウが動く)
の内、①だけなのか②まで必須なのかはっきりしませんでしたので
とりあえず①②両方とも対応しています。

[追加機能]
・x、y、width、heightの4つのコマンドパラメータに対して「\V[n]」の形で変数を指定できます。
・上記座標についてはautoRefreshの設定オン/オフに依らず、常に変数の値が即時反映されます。

なお、本改変について私は一切の権利を主張しません。

いやー、やはりたまには他人のコードを読んでみるもんですね。
良い勉強になりました。
 

Attachments

  • TMSimpleWindow.js
    10.1 KB · 閲覧: 3

KJC

ユーザー
げれげれ様、プラグインの改変に応じていただきまして大変ありがとうございます…!
早速で恐縮なのですが、改変いただいたプラグインを導入したところエラー画面が表示されてしまいました

TMSimpleWindowがon、YEP_CoreEngine.jsがoffの場合↓
スクリーンショット 2021-08-15 015509.png
TMSimpleWindow・YEP_CoreEngine.jsがonの場合↓
スクリーンショット 2021-08-15 015706.png
(1枚目、2枚目ともにそれ以外のプラグインはoffにしてテストしたものです)

お手を煩わせてしまうようで申し訳ありません、これらのエラーの回避方法などありますでしょうか?
最終的に環境によるといった場合、プロジェクトデータをお送りしたいと思います。
 

げれげれ

ユーザー
ご報告ありがとうございます。

こちらでは再現していないので断定できないのですが、エラーに近い部分で一部考慮漏れが
見つかりましたので取り急ぎ修正してみました。
(※文字列でないといけない部分に数値が残っていた)

お手数ですがご確認ください。
 

Attachments

  • TMSimpleWindow.js
    10.1 KB · 閲覧: 1

DarkPlasma

ユーザー
simpleWindow の要素が文字列化してしまったことにより、 needsRefresh が常に真を返します。
autoRefresh 設定との兼ね合いも考えると、変数は都度展開するよりもリフレッシュすることが確定した場合にのみ展開するようにしちゃうほうが自然な気はします。
仕様の定義次第ではありますが。
 

げれげれ

ユーザー
ご指摘ありがとうございます。
ほんと助かります。

変数IDが2桁以上になるとパースできませんね。
我ながら抜け漏れ多いですね・・・

@KJC さん
修正しました。(下記添付)
ひとまずこれで普通には動くのではないかと思います。
度々申し訳ありませんが、ご確認お願いいたします。


simpleWindow の要素が文字列化してしまったことにより、 needsRefresh が常に真を返します。
この点はコードを記述している際にも気にかかっていた部分ではあります。
ここに対応しようとするとプラグイン本来の処理フローにまで踏み込むことになりそうなため
二の足を踏んでおりました。
autoRefresh 設定との兼ね合いも考えると、変数は都度展開するよりもリフレッシュすることが確定した場合にのみ展開するようにしちゃうほうが自然な気はします。
一応、現状、そのようにしているつもりではあるんですよね。
refreshの処理内でmoveを行う直前ギリギリに文字列(変数)から数値に変換。
ただそれだとWindow_Simpleインスタンス側で持っている各座標(数値)と、
元となる管理用のGame_Screen_simpleWindowsオブジェクトの値(文字列)で齟齬が起こり、
結果、 needsRefreshが常時trueになってしまっている、ってことですよね。

プラグイン本来の挙動(autoRefreshがオフだと変数を変更しても反映されない)から考えると、
いっそWindow_Simple.prototype.update側での更新を行わずに、
Spriteset_Base.prototype.updateSimpleWindows側でrefreshContentsだけでなくWindow全体を
refreshしてしまえば済むのではないか、という気がしてきてます。

ただちょっと怖い部分ではあるので、もう少し検討・検証してみます。
 

Attachments

  • TMSimpleWindow.js
    10.1 KB · 閲覧: 3

げれげれ

ユーザー
@DarkPlasma さん
ありがとうございます!
ウィンドウのメタ情報部分をクラス化し、ややこい処理は全部そっちに押し込みました!なるほど!
(カプセル化の威力を実感しました)

@KJC さん
たぶんこれで完璧、なはずです。
座標についてもautoRefreshに対応しています。

おかげで良い勉強ができました。
 

Attachments

  • TMSimpleWindow.js
    11.4 KB · 閲覧: 2
最後に編集:

fspace

ユーザー
横から失礼します。コード読んだので勝手ながらいくつか。



とりあえずマズイところから。

セーブデータに独自クラスのインスタンスを含める場合には、クラスをグローバルに公開する必要があります。でないと、ロード時にプロトタイプが復元されないので、メソッドが見つからずにエラーになります。

JavaScript:
window.SimpleWindowMeta = SimpleWindowMeta;

Windowクラスのmovewidthheightの変更を効率的に行うためのものなので、moveを使うのであれば直接代入する必要はありません。

JavaScript:
Window_Simple.prototype.refresh = function() {
    var windowMeta = $gameScreen.simpleWindow(this._id);
    windowMeta.refresh();
    // [this.x, this.y, this.width, this.height] = windowMeta.getXYWH();
    var [x, y, width, height] = windowMeta.getXYWH();
    this._text = windowMeta.text;
    this._backgroundType = windowMeta.backgroundType;
    // this.move(this.x, this.y, this.width, this.height);
    this.move(x, y, width, height);
    this.createContents();
    this.setBackgroundType(this._backgroundType);
    this.refreshContents();
};

あとは他人のプラグインを改変した場合は、どこかに改変したことを書いておいた方がいいと思います。このプラグインがまた別の人の手に渡ったときに面倒くさいことになるので。



この手の処理はよく書くので参考までに少し関数型っぽい書き方も書いときます(フォーラムのエディタで直接書いてるので間違ってたら適当に修正してください)。

とりあえず入力を文字列で受け取ったら真っ先にパースします。

JavaScript:
const parseInteger = s => Number.parseInt(s, 10);

const parseIntSource = s => {
    const RE_IMM = /^(?:[1-9][0-9]*|0)$/;
    const RE_VAR = /^\\V\[(\d+)\]$/i;
    if (RE_IMM.test(s)) {
        const value = parseInteger(s);
        return { type: 'imm', value };
    } else {
        const match = s.match(RE_VAR);
        if (match !== null) {
            const id = parseInteger(match[1]);
            return { type: 'var', id };
        } else {
            throw new Error(`unexpected token: ${s}`);
        }
    }
};

{
    const id = parseInteger(args[0]);
    const x = parseIntSource(args[1]);
    const y = parseIntSource(args[2]);
    const width = parseIntSource(args[3]);
    const height = parseIntSource(args[4]);
    const text = args[5];
    const backgroundType = 0;
    const config = { x, y, width, height, text, backgroundType };
    $gameScreen.showSimpleWindow(id, config);
}

次にウィンドウの外観を変更しうる要素は何かを考えます。もしこの要素に何か変化があれば、ウィンドウの内容を更新(refresh)しなければなりません。

今回の場合にはパースした設定からこんな感じで計算できるはずです。

JavaScript:
Window_Simple.prototype.evalIntSource = function (source) {
    switch (source.type) {
        case 'imm': return source.value;
        case 'var': return $gameVariables.value(source.id);
        default: throw new Error(`invalid source type: ${source.type}`);
    }
};

Window_Simple.prototype.makeWindowProps = function () {
    const config = $gameScreen.simpleWindow(this._id);
    const x = this.evalIntSource(config.x);
    const y = this.evalIntSource(config.y);
    const width = this.evalIntSource(config.width);
    const height = this.evalIntSource(config.height);
    const { text, backgroundType } = config;
    return { x, y, width, height, text, backgroundType };
};

あとは要素が変化していないかどうか毎フレーム確認して、変化があれば更新します。

JavaScript:
const deepEqual = (a, b) => {
    const arrayEqual = (a, b, eq) => a.length === b.length && a.every((v, i) => eq(v, b[i]));
    const pojoEqual = (a, b, eq) => {
        const hasOwnProperty = Object.prototype.hasOwnProperty;
        const has = (obj, key) => hasOwnProperty.call(obj, key);
        const akeys = Object.getOwnPropertyNames(a);
        const bkeys = Object.getOwnPropertyNames(b);
        return akeys.length === bkeys.length && akeys.every(k => has(b, k) && eq(a[k], b[k]));
    };
    const isPojo = x => Object.getPrototypeOf(x) === Object.prototype;

    if (Object.is(a, b)) return true;
    if (typeof a !== typeof b) return false;
    if (typeof a !== 'object') return false;
    if (a === null || b === null) return false;
    if (Array.isArray(a)) return Array.isArray(b) && arrayEqual(a, b, deepEqual);
    if (isPojo(a)) return isPojo(b) && pojoEqual(a, b, deepEqual);
    return false;
};

Window_Simple.prototype.update = function () {
    Window_Base.prototype.update.apply(this, arguments);
   
    this.updateWindowVisual();
};

Window_Simple.prototype.updateWindowVisual = function () {
    const props = this.makeWindowProps();
    if (!deepEqual(this._props, props)) {
        this._props = props;
        this.refresh(props);
    }
};

制御文字が絡むのでちょっと面倒くさいですが、autoRefresh対応も同じような感じでできます。
 

げれげれ

ユーザー
いつもありがとうございます。
取り急ぎご指摘の箇所を修正しました。

@KJC さん
ほんと度々すみません、以下に修正版を添付します。
これがほんとの最後・・・になるといいな?


@fspace さん
①セーブデータにインスタンスを含む場合のクラスのスコープ
moveメソッド
③改変情報
の内、②、③については腹落ちしているのですが、①についてまだ理解しきれてない部分が
ありそうなので、もしかしたら以前にfspceさんが立てたスレに質問に行くかもしれません。
その際はよろしくお願いいたします m(_ _)m

まずはじっくり読んでじっくり考えてみます。

関数型のくだりについてはまだ自分がその域に全然到達していないので
読み物として興味深く拝見しようと思います。
(何年か前からトレンドとして良くみかけますね、関数型!)
 

Attachments

  • TMSimpleWindow.js
    11.9 KB · 閲覧: 2
最後に編集:
  • Like
Reactions: KJC

DarkPlasma

ユーザー
Game_Screen はセーブデータに含まれることを書いておいたほうが良かったですね。
セーブデータに含む独自クラスのインスタンスを展開するには、 JsonEx からそのクラスの定義が見えている必要があります。
その理解のためには JsonEx._decode を読む必要があるんですが、MVは循環参照対策で複雑なコードになっているので、MZのコードを読むほうが理解はしやすいと思います。
 

fspace

ユーザー
①セーブデータにインスタンスを含む場合のクラスのスコープ
moveメソッド
③改変情報
の内、②、③については腹落ちしているのですが、①についてまだ理解しきれてない部分が
ありそうなので、もしかしたら以前にfspceさんが立てたスレに質問に行くかもしれません。
①についてはDarkPlasmaさんの書いている通りで、JavaScriptではなくコアスクリプトのJsonExの問題ですね。もっと言うと、JSONにプロトタイプ情報をどう記録するかという問題です。

関数型のくだりについてはまだ自分がその域に全然到達していないので
読み物として興味深く拝見しようと思います。
(何年か前からトレンドとして良くみかけますね、関数型!)
あらためて見直すと関数型というのはちょっと言い過ぎだったかもしれません。DarkPlasmaさんがオブジェクト指向的な書き方を提案されていたので、データに注目した書き方もできるよ、という意味で書きました。極力何かを書き換えたりせず、データから別のデータを計算したり、データの不一致をもって変化を検出したりしているところに注目すると、オブジェクト指向よりもシンプルに見えてくるんじゃないかと思います。

まあ、オブジェクト指向だけがプログラミングの方法じゃないよ、ということで。
 

げれげれ

ユーザー
本来のスレッド依頼者であるKJCさんを差し置いてスレを埋めるのも気が引けるので
二日ほど様子を見ておりましたが、反応がないようなので。

@KJC さん
熟達者のお二人から指導をいただいているので、たぶん一つ手前の添付分で問題ないはずです。
気が向きましたら使ってみてください。

その理解のためには JsonEx._decode を読む必要があるんですが、MVは循環参照対策で複雑なコードになっているので、MZのコードを読むほうが理解はしやすいと思います。
もっと言うと、JSONにプロトタイプ情報をどう記録するかという問題です。

ありがとうございます、読みました。
JavaScript:
if (value["@"]) {
  const constructor = window[value["@"]];
  if (constructor) {
    Object.setPrototypeOf(value, constructor.prototype);
  }
}

エンコード時にコンストラクター関数の名前を@プロパティとして一時的に埋め込み、
デコード時にそれをprototypeに繋ぎ直してるんですね。
この手のツクール固有の実装に関する知見を蓄積するには、もっとツクールのコアと向き合わないとダメですね。
最近の学習がテキストやwebにある練習問題のような座学に偏っていたので、
少し配分を見直してみようと思いました。

またよろしくお願いいたします。
 

KJC

ユーザー
返信遅くなりまして大変申し訳ありません、KJCです。

多くの方にスレッドをご確認いただけたようで、自分の力の及ばないところに限って色々と無茶な注文をしてしまったのかなと縮こまりつつ、げれげれ様に幾度と試行錯誤いただいたTMSimpleWindowをただ今試用してまいりました。
先日載せたようなエラー画面などは表示されることなく、変数で指定した値が無事シンプルウィンドウとして動作しております…!

ただ、気になった点としましては、プラグインコマンド "eraseWindow ●" を "eraseWindow \v[n]" という形で使用すると、該当idのシンプルウィンドウがうまく消去されないようです。

(実際の書き方)id1~9までのシンプルウィンドウを消去させたいつもりです
◆変数の操作:#0207 SimpleWindowDel = 0
◆ループ
◆変数の操作:#0207 SimpleWindowDel += 1
◆ウェイト:1フレーム
◆プラグインコマンド:eraseWindow \v[207]
◆条件分岐:SimpleWindowDel ≥ 9
◆ループの中断

:分岐終了

:以上繰り返し


推奨される方法ではないのかもですが、プラグイン内から $gameScreen.eraseSimpleWindow(n); というスクリプトを見つけ、素人ながら試しに $gameScreen.eraseSimpleWindow($gameVariables.value(n)); を直接実行させたところ、こちらの方法では表示させていたウィンドウが消去されました。

(実際の書き方)
◆変数の操作:#0207 SimpleWindowDel = 0
◆ループ
◆変数の操作:#0207 SimpleWindowDel += 1
◆ウェイト:1フレーム
◆スクリプト:$gameScreen.eraseSimpleWindow($gameVariables.value(207));
◆条件分岐:SimpleWindowDel ≥ 9
◆ループの中断

:分岐終了

:以上繰り返し
(※どちらもTMSimpleWindow以外のプラグインはoffでテストいたしました)


まったく想定されていないような使用方法を試してしまっているのだろうなと思うと、何度も報告ばかりとなりとても申し訳ないです…
上記のようなスクリプトの直接実行に問題がない、またこれ以上プラグインに手を加えるのは困難だということであれば、ありがたくこの状態にて使用を続けさせていただく予定です。
プラグインコマンド "eraseWindow \v[n]" を機能させるのは難しいでしょうか…?
(当方のテスト方法・使用方法にそもそもミスがあるということでありましたら、ご指摘ください)
 

げれげれ

ユーザー
おっ、無事に動いたようでほっとしました。

プラグインコマンド "eraseWindow ●" を "eraseWindow \v[n]" という形で使用すると、該当idのシンプルウィンドウがうまく消去されないようです。
すみません、これに関しては普通に対応してませんね。
「座標などの部分を」ということでしたので、ウインドウのレクタングル(x,y,width,height)にのみ
手を加えております。
で、ウインドウのIDも変数で指定できる機能をご希望ということですね。
レクタングルとは弄る箇所が微妙に違ってくるので、どこをどの程度修正すべきか少し検討してみます。

$gameScreen.eraseSimpleWindow($gameVariables.value(n))
これだとウインドウのメタ情報と齟齬が起きないか少し気になってきました。
ここも含めてちょっと検討します。

(2021/08/21 13時 こっそり追記)
→上記スクリプトでの操作、きちんとメタ情報まで書き換えた上で動作していました。
結論として問題なさそうです。
 
最後に編集:
  • Like
Reactions: KJC

KJC

ユーザー
げれげれ様、お世話になっております。
シンプルウィンドウの変数を用いた使用方法については今のところ、座標や大きさの指定部分をご対応いただいてるということで、了解しました。
おかげでウィンドウ表示の調整がすっかり行いやすく、大助かりこの上ないです…!ありがとうございます!

おっしゃる通り、ウインドウのIDも変数指定が可能になればいいな…と願う次第です。
eraseWindow 1,eraseWindow 2,eraseWindow 3…と1行ずつプラグインコマンドを挿入するのが妙に大変だという個人的な使用感から思い至りました。
相変わらず一方的に押し付けてしまう形で恐縮ですが、諸々ご検討いただけるということで、何卒よろしくお願いいたします。
 

げれげれ

ユーザー
できました。ご都合の良いタイミングでご確認お願いいたします。

ウインドウIDは情報として保持されないし、処理の流れも一か所(realSimpleWindowId)に合流していたので
そんなに弄る必要はありませんでした。
たぶん問題ないはずです。(もはや説得力のない発言)
 

Attachments

  • TMSimpleWindow.js
    12.3 KB · 閲覧: 3
  • Like
Reactions: KJC

KJC

ユーザー
げれげれ様、お世話になっております。
早急にご対応いただいていたにも関わらず今しがた気が付く展開となってしまい、大変失礼いたしました…!
度々ログインをしていたわりに、肝心な部分に限って見落としておりました…申し訳ありません…

更新していただいたプラグインのほう動作確認いたしました。
変数"\v[n]"でピクチャidの指定も見事行えるようになり、格段に上がった利便性のおかげで抱えていたストレスからも解放されました…!

数々こちらの要望に手厚くお応えいただきまして、とてもとても光栄です。
心より、お礼申し上げます。
またスレッドをご確認いただいた方々も含め、有識者の皆様ありがとうございます…!
 
トップ