イラスト

ツクールMV:一つのマップに大量のイベントを設置するとめちゃくちゃ重くなる現象の対策

なんだかんだRPGツクールとかいうゲームが一番面白いなあ、とかいう今日この頃です。

さて、そんなとき一つの問題に直面しました。
「超巨大なマップ」をつくりたかったんです。
そしてそのマップには「大量のイベント」が蠢いてます。
するとどうでしょう。

クソ重い。

これはどげんかせんといかん。
20FPSまで下がるのはまずいですよ。
イベントが多すぎるのが問題なのは間違いありません。
かといって……これ以上は減らせない!

結論から言うと、原因はスイッチの更新のたびにマップ上の全イベントをrefreshしているせいでした。
というわけで、refreshの範囲を限定しましょう。

対策①:更新するイベントをプレイヤー周辺のみにする

「あれ、似たようなのもともと実装されてなかったっけ?」と調べたらGame_EventにisNearThePlayer()という関数があり、キャラの移動に関してはプレイヤー周囲20タイル以内に限定するという処理がありました。

ただし、updateは範囲外でもなされるようです。

Game_Map.prototype.updateEvents = function() {
   this.events().forEach(function(event) {
       if(event.isNearThePlayer()) event.update();
   });
   this._commonEvents.forEach(function(event) {
       event.update();
   });
};

というわけでこう書きます。

さて、軽くなったかな……?
う、うーん……多少はマシになったけど……まだ重いな……。

対策②:セルフスイッチでリフレッシュするのはそのイベントだけでいい

つくっていたのはアクションRPGみたいなやつでした。
で、テストプレイをしていて気づいたのです。

「敵が倒れる瞬間」にめちゃくちゃ重くなり、「敵の数が減るごとに」軽くなっていることに。

「敵が倒れる」処理にはセルフスイッチを使っています。
つまり原因はここです。
セルフスイッチがONになるとマップ上の全イベントがrefreshされるのです。

なんて無駄なことを!
セルフスイッチで変化があるのはそのイベントだけです。
というわけで、refreshするイベントを限定する処理を書きましょう。

Game_SelfSwitches.prototype.setValue = function(key, value) {
   if (value) {
       this._data[key] = true;
   } else {
       delete this._data[key];
   }
   var eventId = key[1];
   this.onChange(eventId);
};
Game_SelfSwitches.prototype.onChange = function(eventId) {
   $gameMap.requestRefresh(eventId);
};
Game_Map.prototype.requestRefresh = function(eventId) {
   this._needsRefresh = true;
   this._needsRefreshAppoint = eventId || 0;
};
Game_Map.prototype.refresh = function() {
   var appoint = this._needsRefreshAppoint;
   this.events().forEach(function(event) {
       if(appoint > 0 && appoint != event.eventId()) return;
       event.refresh();
   });
   this._commonEvents.forEach(function(event) {
       event.refresh();
   });
   this.refreshTileEvents();
   this._needsRefresh = false;
};

こうです。
結果……めちゃくちゃ軽くなりました

この処理はふつうに汎用性があると思うのでここに共有したいと思います。
と、あとから調べたらすでに似たような議論はされてましたが……キニシナイ!

・追記(2019/04/20)

指摘を受けて気づきましたが、上の書き方だと同フレームでセルフスイッチの処理が複数重なると前のがrefreshされない可能性がありますね。

Game_SelfSwitches.prototype.onChange = function(eventId) {
   if($gameMap.event(eventId)) $gameMap.event(eventId).refresh();
};

というわけでこうです。よりシンプルに。
$gameMap._needsRefreshのことは忘れてください。必要ならその場でrefreshすればいいじゃない!

と、多分これで問題ないはず……。
なにかありそうな場合は報告いただけると助かります。

この記事が気に入ったらサポートをしてみませんか?