【ツクールMV Tips】ゲーム動作軽量化に潜むsetValueと$gameMap.refresh()の罠
閉じる
閉じる

新しい記事を投稿しました。シェアして読者に伝えましょう

×

【ツクールMV Tips】ゲーム動作軽量化に潜むsetValueと$gameMap.refresh()の罠

2016-03-14 00:04
    あなたのタイムラインのロリババア担当、エイリアスエイクのterunonです。


    ゲーム動作軽量化についてのちょっとむずかしめのおはなしです。
    ここ数日その関連で頭を悩ませていたので、今回わたしが直面した問題に照らしてゲーム動作の軽量化についてお話したいと思います。

    ・Iavra Self Variables v3.2
    プレイヤーとの距離に応じてセルフスイッチを切り替える;Iavra 様
    http://forums.rpgmakerweb.com/index.php?/topic/48715-sensor-selfswitch/

    こちら、すごく便利なプラグインで、うちのエイリアスエイク然り、アクション要素を持つゲームにはほぼ必須と言っていいシロモノです。
    プレイヤーが近づいたらセルフスイッチが切り替わるといったことができるため、プレイヤーが近寄ったら挙動が変わるイベントを容易に作ることができます。
    (ここで移動ルートのところにスクリプトを組めば、エイリアスエイクのようにマップ上でプレイヤーを見つけると攻撃を仕掛けてくる敵を作ることが出来ます)

    しかし、このプラグイン、
    1フレームに一回、セルフスイッチ切り替えタグを設定した全イベントが $gameSelfSwitches.setValue(id,value)を実行するという、ものすごい爆弾を抱えていました。

    これの何が問題かというと、.setValueを使った変数・スイッチ・セルフスイッチの変更は、マップ全体の更新処理を伴うためです。
    (ちなみにイベントコマンドによる変更もすべて.setValueが使われています)

    Game_SelfSwitches.prototype.setValue = function(key, value) {
    if (value) {
    this._data[key] = true;
    } else {
    delete this._data[key];
    }
    this.onChange();   ←コレをたどっていきます。
    };

    Game_SelfSwitches.prototype.onChange = function() {
    $gameMap.requestRefresh();
    };

    Game_Map.prototype.requestRefresh = function(mapId) {
    this._needsRefresh = true;
    };


    Game_Map.prototype.refreshIfNeeded = function() {
    if (this._needsRefresh) {
    this.refresh();
    }
    };

    Game_Map.prototype.update = function(sceneActive) {  ←1フレーム(1/60秒)に1回行う処理
    this.refreshIfNeeded();
    if (sceneActive) {
    this.updateInterpreter();
    }
    this.updateScroll();
    this.updateEvents();
    this.updateVehicles();
    this.updateParallax();
    };

    何が起きるかというと、プレイヤーが近づいたら襲い掛かってくる敵イベントを一つでも置いていると、一秒間に60回setValueにより更新処理が誘発されることになります。

    これだけでもフレームレートが実機で15くらい落ちますが、まだなんとかカクカク動かせるレベルです。しかし、わたしのエイリアスエイクでは、何も知らずに更新処理($gameMap.refresh)に別のプラグインで処理を追加していたのがトドメを刺しました。

    2016.04.28追記:カクカクにトドメを刺したのは、RPGツクールMV ver 1.10 とChromeの競合だったようです。(エイリアスエイクは他ゲームより影響が大きかったので、無関係とも言えなそうですが…。)こちらは、RPGツクールMV ver1.20にて修正されました。

    (トリアコンタン様のオプション任意項目追加プラグイン v1.0 http://triacontane.blogspot.jp/2016/01/blog-post.html を使用していました。
    自由にオプション項目を追加し、セーブデータ間で特定変数を共有できるこちらも非常に優れたプラグインです。オプションで設定した変数のゲーム中への反映に、$gameMap.refreshを使用しています。)

    これにより、オプション項目の反映処理までも巻き添えで1秒間に60回無意味に実行されることになり、処理落ちでゲームがブラウザ上でまともに動作しなくなっておりました。


    今回の問題の対処法、それは言うまでもなくIavra Self Variables v3.2の改変で、setValueの毎フレーム連打を止めなければなりません。
    試行錯誤の結果、実際の解決策として、
    セルフスイッチをOFFにする処理はツクールの「移動ルートの指定」側で行うと割りきって、Iavra Self Variables v3.2内のセルフスイッチをOFFにする処理 selfs.call(this, false) をばっさりと切りました。


    LS.basicSensor = function (object) {
    var selfs = LS.selfSwitch;
    if (object == this.page()) { selfs = LS.selfSwitchComment; };
    if (LS.regionBlock()) { selfs.call(this, false); return; };

    var inRange = Math.abs(this.deltaXFrom($gamePlayer.x));
    inRange += Math.abs(this.deltaYFrom($gamePlayer.y));

    if (inRange <= LS.getRange(object)) {
    selfs.call(this, true);
    } else {
    // selfs.call(this, false); ←ここだけ変えた
    };

    };


    LS.selfSwitchComment = function (selfs) {
    $gameSelfSwitches.setValue([this._mapId, this._eventId, Lyson.Param.CommentSwitch], selfs);
    };

    ※セルフスイッチをONにする処理 selfs.call(this, true); の方は、セルフスイッチがONになるとイベントページが切り替わるため毎フレーム連打される心配はありません。

    実際は、別件の問題でこれだけで解決せず、あとひと処理ふた処理加えたのですが、自動生成マップを使うエイリアスエイク特有の問題だったのでそれについては割愛します。



    さて、今回のマップ更新処理問題は、Iavra Self Variables v3.2特有のお話ではありません。
    .setValueが曲者というお話をしたところなのですが、並列処理で変数操作を行っているゲームでも、同様の原理で1秒間に何回もマップ更新が行われ動作が重くなります

    うちではマップアクションのクールタイムが該当しました。

    ◆並列処理ループコモン
    条件分岐 スキルクールタイム >= 0
     変数の操作 スキルクールタイム -= 1
    (ほか処理いろいろ)

    ◆マップアクションコモン
    条件分岐:スキルクールタイム <= 0 かつノゾミが行動不能でなくMPが残っている
     (マップアクション処理いろいろ)
    それ以外:ブブーッ音のSE


    わたし自身、ツクマテでトリアコンタン様に教えてもらって改善したところなのですが、この.setValue(およびイベントコマンドによる変数操作)に伴うマップ更新を防ぐ方法として、変数操作部分を

    スクリプト:$gameVariables._data[変数ID] = 値

    で書くという方法があり、これで無用なマップ更新を防ぐことができます。


    以上によりかなり軽量化することができたのですが、今回さらにダメ押しとして、セルフスイッチの.setValue自体をいじって、「セルフスイッチをいじったイベントのみを更新し、何らかの理由でそれができなかったときのみ従来のマップ全体の更新を行う」というものに変えてみました。

    変更前
    Game_SelfSwitches.prototype.setValue = function(key, value) {
    if (value) {
    this._data[key] = true;
    } else {
    delete this._data[key];
    }
    this.onChange(); ←マップ全体更新
    };

    変更後

    Game_SelfSwitches.prototype.setValue = function(key, value) {
    if (value) {
    this._data[key] = true;
    } else {
    delete this._data[key];
    }
    $gameMap._events[key[1]].refresh(); ←セルフスイッチをいじったイベントのみ更新
    };

    で、結果はというと、なんとなく軽くなったような気はします……としか言いようがないのがつらいところ……。
    他にうちで行っている軽量化処理としては、Game_CharacterBase#nearTheScreen() を使ったマップ外のスプライトの更新停止があるのですが、これについてはまた機会があるときに。

    8/26 追記:
    セルフスイッチの改変について、以前上記では try-catch 構文というめっさ動作が遅い条件分岐を書いてましたが、すでに訂正しています。
    ついでに、ここで書いた軽量化よりも、もっと大きく効いた軽量化を紹介します。
    並列処理でボタンを押したかどうかの判定するとき、
    今まで「ボタン押しに反応する状態かどうか(転倒ステートのときは反応しない等)判定」→「ボタン押し行動のクールタイム中か判定」→「ボタンを押したか判定」としていたのを、
    「ボタン押したか判定」→「転倒ステートでなく、かつクールターム中でないか判定」と書き換えたら、毎フレームの判定回数がかなり減るためすごく軽くなりました。

    また、以下は体感としては微妙ですが、
    ダメージに合わせてマップ上のHPMPウィンドウを更新する処理について、毎フレームHPMPTPステートの変化を監視する仕様から、イベントでHP・MP・TP・ステートの増減を行った際に更新スイッチをONにする仕様にして、スイッチONだけ監視する仕様に変えたらこれまた軽くなった気がします。
    ともあれ、、毎フレーム更新する処理はちょっとでも処理を減らす工夫が大事、ということになりますね。



    以上、何かの参考になれば幸いです。
    本記事のスクリプトを参考・利用するのに制限はないですが、その際はエイリアスエイクの宣伝を手伝ってくれると嬉しいです。
    広告
    コメントを書く
    コメントをするには、
    ログインして下さい。