1. このサイトではcookie (クッキー) を使用しています。サイトの利用を継続した場合、cookieの使用に同意したものとみなさせていただきます。 詳しくはこちらをご覧ください。

スクリプト実行中にウェイト

WTR2019-09-02に開始した「プラグイン制作・技術」の中の討論

  1. WTR

    WTR ユーザー

    ダメ元で相談させてください…

    スクリプトでもプラグインに書いた関数でもいいんですが
    実行中にウェイトを入れたいです。

    より具体的には
    ピクチャの移動をスクリプトで実行したとき、移動完了までウェイトさせたい。
    ピクチャを相当使い倒してるつもりだったのですが
    $gameScreen.movePictureを複数回実行すると
    最期に評価された座標に向かって動いてしまうので、移動ルートを制御できないのが制約になっています。

    イベントコマンドでやればできることなんですが
    かなり作りこんでしまったのと、そもそも規模の問題でイベントコマンドで作るのが現実的ではない処理なので
    どうにかならないかと…

    無理っぽい話、という気はしてるんですがアドバイスを頂けないでしょうか。
     
    #1
  2. 剣崎宗二

    剣崎宗二 ユーザー

    ウェイトの所でスクリプトを切り離してみてはいかがでしょうか?
    同じ「スクリプト」イベントコマンドに全て記述するのではなく、スクリプトイベントコマンドを2つ以上設け、ウェイトの後のラインで切り離すイメージです。
    (ただウェイトがifやループの中に入っていないことが前提となります)
     
    #2
    WTR がいいね!しました
  3. WTR

    WTR ユーザー

    ありがとうございます。
    たしかに、普通に考えたらなぜそうしないのかと問われて当たり前な書き方だったのですが
    コトはもう少し複雑でして…

    実際の処理は全てプラグインファイルに記載してあり
    イベントコマンドのスクリプトは処理の起点となる関数を1つ呼ぶだけ、という形をとっています。
    もともとイベントコマンドで書いてあったのですが
    あまりにメンテナンス性が悪くてこれはどう考えても無理と思ったので見なおしをかけたところでした。
    すでに数千行になっているのでそれをまた分解して、というのは正直絶望的…

    ピクチャの移動に限らずですが、任意の箇所で処理を停止して、再開する、というのは
    やっぱり根本的に難しい話なんでしょうか…
     
    #3
  4. 剣崎宗二

    剣崎宗二 ユーザー

    プラグインを自分で書ける技術がある、という前提で、できる限りの「どうして難しいか」と「どうすればいいのか」を解説してみようと思います。
    わかりにくかったら申し訳ないです。

    何故難しいのか
    「全ての処理を一定時間止めて、その後再開する」ならば簡単なんです。秒数経過するまでwhileでループし続けるという方法でも可能です。
    ただ、これは文字通り「全て」の処理が止まってしまうので、停止している間はピクチャの移動も継続されませんし、アニメもメッセージも何もかもが止まります。要はその秒数の間システムがフリーズしているのと同じ事ですね。
    (音系はあるいは継続されるかもしれませんが…Javascriptはデータを入れるだけで再生本体を担当している訳ではないと記憶しているので)

    ピクチャの移動は結局は「1フレームずつ、その画像の座標をずらしている」という処理に他ならないので、複数フレームにまたがらない限り、ピクチャの移動処理を行うことは不可能なのです。(1フレームでやろうとすると一瞬でピクチャが目標地点にワープします)
    そして、同じfunctionの中にある処理はJavascriptの仕組み上、基本的に全て同じフレーム内で全て処理されるので…


    どうすればいいのか
    何をしようと少なくともfunction自体は「移動開始前」と「移動終了後」に切り分けないと話にならないと考えます。
    とありますが、上に説明した通り、1function内に全てのコードを入れる(=物理的に1フレーム内で処理される)のは物理的にピクチャの移動の仕組み(=多数フレームにまたがって処理される)と衝突するので、こればかりはやらないとダメかと考えます…

    最悪、本当に欲しい仕様を文書に起こし、しぐれんさんやまっつさん等、プログラムの受注をしているどなたかに依頼するのも一つの手かとは考えます…
    (実際しばらく前に私の受けた依頼にはそれ系の物があって、イベントコマンド組んだとされる600変数を使った大型システムをプラグインコード300行ほどに圧縮したので…)


    以上、ご参考になれば幸いです。
     
    #4
    WTR がいいね!しました
  5. WTR

    WTR ユーザー

    >ピクチャの移動は結局は「1フレームずつ、その画像の座標をずらしている」という処理に他ならないので

    なるほど、言われてみればその通りですね。
    全部のピクチャ移動命令に対処するのは困難だと思いますが
    凝ったことをしたい場合に限定して、一旦イベントコマンドを経由して戻ってくる、ができないかちょっと考えてみます。
    コレが正解な気がしてきた。

    出来るか出来ないかで言えば出来そうな気がしてきました。
    汎用的にわかり易く、は課題ですが…

    どなたかに依頼する、というのはまだその段階にはないと思っています。
    何が出来るか実験してる段階で、やりたいこと全体から見ればごく一部しか手を付けていないし
    苦労はしてますが考えること自体は楽しんでやってることでもあるので
    誰かに委ねるのは、少なくとも一通り動作するところまで自力でもっていって、その後の可能性の話かな…
    いやそのころには百万払うと言っても拒否される代物が出来上がっているかも…
     
    #5
  6. 守谷シゲ

    守谷シゲ ユーザー

    実際のソースコードを拝見したわけではないので、見当外れなことを書いていたらすみません。

    1. プラグインコマンドを実行した時に即時ピクチャの移動をスタックしておく。
    2. マップのupdate()内でスタックしたピクチャの移動を実行する。

    って感じで実現できると思います。

    どこで使うのかは分かりませんが、ピクチャなのでおそらくマップ画面で使うと仮定しての実装です。

    コード:
    (function() {
    
        // コマンド実行用スタック
        var commandStack = [];
        // ウェイトフレーム数
        var commandWaitCount = 0;
    
        var _Scene_Map_initialize_x8924h = Scene_Map.prototype.initialize;
        Scene_Map.prototype.initialize = function() {
            _Scene_Map_initialize_x8924h.call(this);
            commandStack = [];
            commandWaitCount = 0;
        };
    
        var _Scene_Map_update_o00au2 = Scene_Map.prototype.update;
        Scene_Map.prototype.update = function() {
            _Scene_Map_update_o00au2.call(this);
            
            if (commandWaitCount > 0) {
                commandWaitCount--;
            }
    
            // update()の時にコマンド実行用スタックにコマンドが入ってたら実行する
            // (ウェイト中も実行しない)
            if (commandStack.length > 0 && commandWaitCount <= 0) {
                var commandObj = commandStack.shift();
                var args = commandObj.args;
                $gameScreen.movePicture(args[0], args[1], args[2], args[3],args[4], args[5], args[6], args[7], args[8]);
                commandWaitCount = commandObj.waitCount;
            }
        };
    
        var _Game_Interpreter_pluginCommand =
                    Game_Interpreter.prototype.pluginCommand;
        Game_Interpreter.prototype.pluginCommand = function(command, args) {
            _Game_Interpreter_pluginCommand.call(this, command, args);
            if (command === 'SomeonePlguinCommand') {
                // ここで実行してる $gameScreen.movePicture を this.pCommandPush に
                // 書き換える。(引数は同じママで良いので一括置換で良いと思う)
                // $gameScreen.movePicture(1,0,-210,140,100,100,255,0,60);
                // $gameScreen.movePicture(1,0,480,240,100,100,255,0,60);
                this.pCommandPush(1,0,-210,140,100,100,255,0,60);
                this.pCommandPush(1,0,480,240,100,100,255,0,60);
            }
        };
    
        Game_Interpreter.prototype.pCommandPush = function(pictureId, origin, x, y, scaleX,
            scaleY, opacity, blendMode, duration) {
            commandStack.push({
                command: $gameScreen.movePicture,
                args: [pictureId, origin, x, y, scaleX,scaleY, opacity, blendMode, duration],
                waitCount: duration
            });
        }
    
    })();
    picturemove.gif
     
    #6
    WTR がいいね!しました
  7. WTR

    WTR ユーザー

    おぉう…すごい。

    ただ、具体例を具体的にしすぎて失敗したみたいで、本来やりたいことを説明しきれてなかったようです。。
    ピクチャの移動はあくまで一例で、本当は任意の関数の実行を順番通りに逐一完了を待って実行、がやりたかったのです。

    で、なんとかならないかと考えてみた結果…
    ウェイトを入れて実行したい処理を、まるごと文字列にしてcommandStackに積んで
    update()で無理やり関数として実行するという手法に行き当たりました。

    一応動いたんですが、とてつもなく力技感が溢れていて全く自信がないです。
    もしよければ追加コメント頂けないでしょうか。

    プラグイン
    コード:
    (function() {
    
        // コマンド実行用スタック
        var commandStack = [];
        // ウェイトフレーム数
        var commandWaitCount = 0;
    
        var _Scene_Map_initialize_x8924h = Scene_Map.prototype.initialize;
        Scene_Map.prototype.initialize = function() {
            _Scene_Map_initialize_x8924h.call(this);
            commandStack = [];
            commandWaitCount = 0;
        };
    
        var _Scene_Map_update_o00au2 = Scene_Map.prototype.update;
        Scene_Map.prototype.update = function() {
            _Scene_Map_update_o00au2.call(this);
           
            if (commandWaitCount > 0) {
                commandWaitCount--;
            }
    
    //        // update()の時にコマンド実行用スタックにコマンドが入ってたら実行する
    //        // (ウェイト中も実行しない)
    //       if (commandStack.length > 0 && commandWaitCount <= 0) {
    //           var commandObj = commandStack.shift();
    //           var args = commandObj.args;
    //           $gameScreen.movePicture(args[0], args[1], args[2], args[3],args[4], args[5], args[6], args[7], args[8]);
    //           commandWaitCount = commandObj.waitCount;
    //       }
    
           if (commandStack.length > 0 && commandWaitCount <= 0) {
               var commandObj = commandStack.shift();
    //           eval (commandObj.command);
               Function(commandObj.command)();
               commandWaitCount = commandObj.waitCount;
           }
        };
    
    //    var _Game_Interpreter_pluginCommand =
    //                Game_Interpreter.prototype.pluginCommand;
    //    Game_Interpreter.prototype.pluginCommand = function(command, args) {
    //        _Game_Interpreter_pluginCommand.call(this, command, args);
    //        if (command === 'SomeonePlguinCommand') {
    //            // ここで実行してる $gameScreen.movePicture を this.pCommandPush に
    //            // 書き換える。(引数は同じママで良いので一括置換で良いと思う)
    //            // $gameScreen.movePicture(1,0,-210,140,100,100,255,0,60);
    //            // $gameScreen.movePicture(1,0,480,240,100,100,255,0,60);
    //            this.pCommandPush(1,0,-210,140,100,100,255,0,60);
    //            this.pCommandPush(1,0,480,240,100,100,255,0,60);
    //        }
    //    };
    //
    //    Game_Interpreter.prototype.pCommandPush = function(pictureId, origin, x, y, scaleX,
    //        scaleY, opacity, blendMode, duration) {
    //        commandStack.push({
    //            command: $gameScreen.movePicture,
    //            args: [pictureId, origin, x, y, scaleX,scaleY, opacity, blendMode, duration],
    //            waitCount: duration
    //        });
    //    }
    
            Game_Interpreter.prototype.pCommandPush = function(command, wait) {
                commandStack.push({
                command: command,
                waitCount: wait
            });
        }
    
    })();
    イベントコマンド
    コード:
    $gameScreen.showPicture(1,  "Succubus", 1,   100, 100, 100, 100, 255, 0);
    
    this.pCommandPush('$gameScreen.movePicture(1, 1, 100, 200, 100, 100, 255, 0, 60)', 60);
    this.pCommandPush('$gameScreen.movePicture(1, 1, 200, 200, 100, 100, 255, 0, 60)', 60);
    this.pCommandPush('$gameScreen.movePicture(1, 1, 200, 300, 100, 100, 255, 0, 60)', 60);
    
    this.pCommandPush('console.log("complete!")', 60);
     
    #7
  8. 守谷シゲ

    守谷シゲ ユーザー

    なるほど。そこまで行くと事情が違います。別の解決方法を探ったほうが良いと思います。

    なぜならソレらはRPGツクールMVのコアですでに実装済みの処理であり、車輪の再発明だからです。
    (イベント命令が完了するまでウェイトなどの処理は Game_Interapter で実装されています)

    ピクチャの移動の一つのコマンドくらいなら力技で処理しても良かったのですが。

    提案できる選択肢は次の2つです。
    1. 数千行に及ぶ処理は明らかに最適化されていないので分割しコンパクトにする。(リファクタリングをする)
    2. 現在のスクリプトをプログラムでJSONに変換する。
    1番のリファクタリングについてですが、すでに数千行に膨らんでいるコードをどうこうしようとするより今が再構築のベストタイミングだと存じます。

    数千行と言えども1日1000行を解決していけば1週間で終わります。1日500行でも2週間です。

    とはいえ、それが面倒な場合は現在の複雑なスクリプトをJSONに変換するプログラムを書くことをオススメします。
    イベント命令はJSONファイルでdataフォルダに保存されています。

    多分一番良いのは data/CommonEvents.json に書き込むことでしょうか。

    コード:
    [
    null,
    {"id":1,"list":[{"code":0,"indent":0,"parameters":[]}],"name":"","switchId":1,"trigger":0},
    {"id":2,"list":[{"code":0,"indent":0,"parameters":[]}],"name":"","switchId":1,"trigger":0},
    {"id":3,"list":[{"code":0,"indent":0,"parameters":[]}],"name":"","switchId":1,"trigger":0},
    {"id":4,"list":[{"code":0,"indent":0,"parameters":[]}],"name":"","switchId":1,"trigger":0}
    ]
    これが初期設定の何もないコモンイベントの中身のJSONですが、コモンイベント1にピクチャの表示とピクチャの移動をRPGツクールMVで作ると、以下のようになります。

    コード:
    [
    null,
    {"id":1,"list":[{"code":231,"indent":0,"parameters":[1,"ushimitsu_icon",0,0,320,240,100,100,255,0]},{"code":232,"indent":0,"parameters":[1,0,0,0,200,50,100,100,255,0,60,true]},{"code":232,"indent":0,"parameters":[1,0,0,0,600,120,100,100,255,0,120,true]},{"code":0,"indent":0,"parameters":[]}],"name":"","switchId":1,"trigger":0},
    {"id":2,"list":[{"code":0,"indent":0,"parameters":[]}],"name":"","switchId":1,"trigger":0},
    {"id":3,"list":[{"code":0,"indent":0,"parameters":[]}],"name":"","switchId":1,"trigger":0},
    {"id":4,"list":[{"code":0,"indent":0,"parameters":[]}],"name":"","switchId":1,"trigger":0}
    ]
    イベントオブジェクトだけ注目すると

    コード:
    {"code":231,"indent":0,"parameters":[1,"ushimitsu_icon",0,0,320,240,100,100,255,0]}
    こんな感じです。code 231がピクチャの表示を表す数値でparametersが$gameScreen.showPictureの引数とほぼ一緒です。

    各イベント命令に対応するcodeの数字は rpg_objects.js の
    Game_Interpreter.prototype.commandなんちゃら を見れば分かります。

    スクリプトをJSONに変換するスクリプトはこんな感じでしょうか。

    コード:
    var list = [];
    
    // 1行ずつcommandに読み込む
    var command = '$gameScreen.showPicture(1, "picture", 0, 100, 200, 100, 100, 255, 0, 60)';
    
    if (command.indexOf('gameScreen.movePicture') > -1) {
      // ここらへんは正規表現を使って処理した方が格好良いかも
      command = command.replace('$gameScreen.movePicture(', '');
      command = command.replace(')', '');
      command = command.replace(' ', '');
      var args = command.split(',');
    
      // 置換処理
      var commandData = {
        code: 231,
        indent: 0,
        parameters: args
      };
     
      list.push(command);
     
      console.log(list);
    }
    最後にコンソールに吐き出されたlistオブジェクトの文字列をJSONファイルにコピペして終了。
    (直接ファイルを吐いても良い)

    こうしてJSONデータを直接吐き出したほうが良いですし、割とイベント命令で処理を組むのが面倒な場合はJSONデータを直接書いちゃう人も多いです。

    RPGツクールMVのUIをマウスでポチポチしながら処理を組むのは大変なのでスクリプトを書いたと思うのですが、スクリプトを書くのではなくJSONデータを直接書くという方法もあるのです。


    最後に

    どんな媒体でゲームを公開するのかによりますが、こういった手法は脆弱性を産むので、あまり良くはありません。
    文字列をスクリプトとして解釈するのは避けるべき処理です。(攻撃者の標的になるので)
     
    #8
    WTR がいいね!しました
  9. WTR

    WTR ユーザー

    ありがとうございます。
    あー痛い。耳がちぎれそうに痛い。
    実際、何度再発明したかわかりません。

    ですよね。
    攻撃者の標的に、といわれるとピンと来ないんですが
    少なくとも美しくないとは思ってました。
     
    #9

このページを共有