画面外のスプライトを更新しない処理軽減(高速化)
ワールドマップなどの大きなマップを扱う場合、画面の表示されていない場所にイベントが設置される状況が起こりうる。このとき、RPGツクールVXAceでは離れたイベントは移動などの動作を更新しないという処理負荷軽減がデフォルトで行われている。
Game_Eventクラスの near_the_screen? メソッドがそれにあたる。
#--------------------------------------------------------------------------
# ● 画面の可視領域付近にいるか判定
# dx : 画面中央から左右何マス以内を判定するか
# dy : 画面中央から上下何マス以内を判定するか
#--------------------------------------------------------------------------
def near_the_screen?(dx = 12, dy = 8)
ax = $game_map.adjust_x(@real_x) - Graphics.width / 2 / 32
ay = $game_map.adjust_y(@real_y) - Graphics.height / 2 / 32
ax >= -dx && ax <= dx && ay >= -dy && ay <= dy
end
デフォルトのスクリーンサイズは544pix * 416pixで、横17チップ*縦13チップになる。画面中央から横幅12チップ、縦幅8チップの範囲を可視領域付近と判定しており、大体横は画面外3チップ、縦は画面外1チップ程度を超える場所にイベントがある場合は、そのイベントは移動しないようになっている(dx,dyの引数を変更すれば挙動を確認できる)。
しかし、実際に大きなマップを作ってみると、イベントから離れてもあまり処理負荷が落ちていないと感じる。要因はいろいろあると思うが、とりあえずマップの更新に関して何が処理負荷になっているか検証した。
【条件】
マップサイズ:290×200(かなり大き目を想定)
イベント数:106
イベント内容:場所移動のみ
この状態で、主人公がマップ上を歩行するときに、1回のscene.updateにかかる各処理の処理時間を、高精度CPUカウンタ(WindowsのAPI)を用いてマイクロ秒単位で計測した。Scene_Mapのupdateメソッドを引用する。
#--------------------------------------------------------------------------
# ● フレーム更新
#--------------------------------------------------------------------------
def update
super
$game_map.update(true)
$game_player.update
$game_timer.update
@spriteset.update
update_scene if scene_change_ok?
end
①super
Scene_Baseのupdate_basicにあたり、Graphics.update,Input.update,ウィンドウ更新を含むが、Graphics.updateがフレームレートまでウェイトをかけており処理負荷が見え辛いため今回は負荷の対象に含めない。
②$game_map.update(true)
マップの更新。乗り物、イベント、スクロールなどの更新を含む。
③$game_player.update
プレイヤーの移動更新。
④$game_timer.update
タイマー(ゲーム内で表示するもの)があれば更新。
⑤spriteset.update
イベントやピクチャなどのスプライトの位置などを更新する。
⑥update_scene
シーン遷移があった場合のみの処理
60fpsの場合、1フレームは16.66ms以内で処理を終わらせなければ、フレームスキップ(がくつき)が発生する。②③④⑤が和が16ms以内であればおそらく通常の歩行時にフレームスキップが起ころうことはない。とはいえ、処理は早めに終わる(軽い)ほうがCPUへの負荷が少なく、軽いにこしたことはない。
自分のPC(Core i5 670 3.4GHz)で計測した結果②③④⑤は、マップのどこを歩いていても大体以下のようになった。()内は1フレーム16.66msに対する処理時間の割合
②0.510ms(3.1%)
③0.058ms(0.3%)
④0.014ms(0.1%)
⑤1.620ms(9.7%)
合計 2.202ms (13.2%)
②はマップサイズとイベント数に依存するため改善の余地はあるが、とにかく⑤が重い。しかも、画面にイベントが表示されていないときでも処理時間が変わらないため、画面外のスプライトに対しても更新を行っているのではないかと予想できる。実際RGSSの処理を見るとそうなっている。(Sprite_Characterクラスのupdateメソッド)
ここで、Sprite_Characterクラスについても near_the_screen? メソッドをつくり、画面外のスプライトは更新しないようにしてみた。
#--------------------------------------------------------------------------
# ● 画面の可視領域付近にいるか判定(Game_Eventを参考に)
# dx : 画面中央から左右何マス以内を判定するか
# dy : 画面中央から上下何マス以内を判定するか
#--------------------------------------------------------------------------
def near_the_screen?(dx = 12, dy = 8)
ax = @character.screen_x/32 - Graphics.width / 2 / 32
ay = @character.screen_y/32 - Graphics.height / 2 / 32
ax >= -dx && ax <= dx && ay >= -dy && ay <= dy
end
引数のdx,dyは必要に応じて調整してもいい。@characterのスクリーン位置情報を参照するのがポイント。あとはSprite_Characterクラスのupdateメソッドに、
#画面外のスプライトは更新しない
return if !near_the_screen?
を追加すればいい。
処理を追加した結果、イベントから離れた場所では
②0.507ms(3.0%)
③0.050ms(0.3%)
④0.013ms(0.1%)
⑤0.684ms(4.1%)
合計 1.254ms (7.5%)
になり、全体で1msくらい改善された。CPU処理負荷もタスクマネージャで確認しても下がっているのが確認できた。
この方式では、横100pixなどの巨大なキャラグラフィックを持つイベントが、スクリーンに近づくまで表示されないといった不都合が考えられるが、32*32に収まるイベントしかないことがわかっている場合は、イベントが少ない場所での処理負荷軽減につながるだろう。
巷ではほかにもたくさんの負荷軽減処理が考え出されているが、とりあえず少ない変更で効果が大きい部分から手を加えてみるのが良さそうに思う。システムの根幹の部分は、下手に多くの箇所を変えると流用性が低下するし、思いもよらなかったバグを生み出しやすい。