表紙の写真

ゲームループ(1)

ゲームループは、ゲームに特化したループ処理のことを指します。ゲームループには2つの役割があります。

(1) 1ループの処理時間を一定にする
(2) 描画のちらつきを抑える

前者は同期処理と呼ばれ、後者はティアリング防止処理と呼ばれます。

同期処理

1回のゲームループで掛かる時間というのは、処理しているキャラクタ数などによってまちまちです。処理するキャラクタ数が減ったり増えたりすると、それに合わせてゲームの進行が速くなったり遅くなったりします。

たとえばシューティングゲームを遊んでいたとして、ゲーム進行が画面上に登場しているキャラクタ数により、速くなったり遅くなったりしてしまうと、本来なら一定のスピードで飛んでくるはずの敵の弾も、ゲーム進行に合わせて速くなったり遅くなったりすることになってしまいます。

ゲーム進行が極端に変化してしまうと、 敵の弾が一瞬止まったかと思ったら、いきなり自機の目の前に迫っていた、ということにもなりかねません。これではゲームを遊んでいるのか、逆にゲームに遊ばれているのかが判りません。

このようにゲーム進行が速くなったり遅くなったり、つねに変化していたのでは、 プレイヤーはゲーム進行速度の変化に対応することに追われてしまい、肝心のゲームを堪能することができなくなってしまいます。

そこでゲームの進行が一定になるように保つ処理を作ります。これを同期処理と呼びます。同期処理の基本はウェイト処理です。まず、ゲーム進行速度を一定にするために、1回のループで処理する時間を決めます。次にループ処理の最後で、その決めた時間に達するまでウェイトを入れます。


フレームレート

ゲームループ1回で行われる画面の描画は、1フレームとカウントします。1秒間に描画されるフレーム数をフレームレートと呼びます。単位はfps(Frame Per Seconds)です。

たとえば、50fpsのゲームであれば1秒間に50フレームの画面描画がされるということになります。50fpsのゲームでは、1フレームにかかる処理時間は次の計算式で求められます。

1[秒] ÷ 50[fps] = 0.02[秒] = 20[ミリ秒]

1回のゲームループが20ミリ秒未満の場合、たとえば17ミリ秒しかかからなかったとすれば、同期処理で3マイクロ秒のウェイトを入れてフレームレートを一定に保つようにします。

ゲームプログラムではフレームレートが高いほど、 1ループを短い時間で処理しなければならないことになります。


垂直同期信号

一般的なゲームでは60fpsを保つようにプログラムを組むことが多いのですが、これはモニタのハードウェア信号に同期させているためです。この信号を垂直同期信号と呼びます。

垂直同期信号は2種類あります。ひとつが「垂直表示期間開始」時に送られる信号、もうひとつが「垂直帰線期間開始」時に送られる信号です。

モニタは画面の左上から画面右下にかけて、1ピクセルずつ描画しています。1行描画し終わると次の行を描画します。これをモニタの最下段まで繰り返します。右下まで描画が終わると、左上のホームポジションまで描画位置を戻します。

画面の左上から描画を始めるときに送られる信号が垂直表示期間開始で、右下まで描画が終わって左上のホームポジションまで描画位置を戻し始めるときに送られる信号が垂直帰線期間開始です。

高速に描画されているので人間の目には綺麗に描画されているように見えています。しかし、書き換えている途中で画面を更新してしまうと、すでに書き換えた部分と新しく書き換えてしまった部分で不整合が起こります。これがティアリングと呼ばれる現象です。


ティアリング防止処理

ティアリングが起こらないようにするには、垂直帰線期間開始の信号を参照して描画処理を行います。実際のモニタが更新されていない間に次の画面描画を終わらせてしまうのです。

具体的にはループの最後で次の垂直帰線期間開始の信号が来るまでウェイトを入れます。

垂直同期信号を利用すると、ティアリング防止処理を行いながら同期処理を行うことができるので一石二鳥です。


OpenSiv3Dでの処理

OpenSiv3Dの同期処理は設定が簡単です。Graphics::SetTargetFrameRateHzメソッドを呼び出します。引数にはnoneあるいは任意の数値をdouble型で与えることができます。

引数にnoneを与えた場合、アプリケーションを実行した環境へ接続されているモニタのリフレッシュレートを利用します。モニタのリフレッシュレートはGraphics::GetDisplayRefreshRateHzメソッドで取得することができます。noneを指定した場合、垂直同期信号による同期処理を行います。結果、自動的にティアリング防止処理が行われます。

引数に任意の数値を与えた場合は、指定のリフレッシュレートになるよう、同期処理が実行されます。垂直同期信号は参照しなくなるため、ティアリングが起こるようになります。

リフレッシュレートを固定する

モニタのリフレッシュレートが異なる環境でも指定したリフレッシュレートでアプリケーションを実行させたい場合は、ディスプレイのリフレッシュレートを取得し、指定のリフレッシュレートではない場合に、リフレッシュレートを更新するようにします。

次のソースコードはアプリケーションで実行するリフレッシュレートを固定するためのサンプルです。

#include <Siv3D.hpp>
void Main()
{
   if (static_cast<int>(Graphics::GetDisplayRefreshRateHz()) != 60)
   {
       Graphics::SetTargetFrameRateHz(60);
   }
   while (System::Update())
   {
       ClearPrint();
       Print << Profiler::FPS();
   }
}

ClearPrintメソッドは画面描画された文字列をクリアします。PrintメソッドはOpenSiv3D標準の文字描画を行うために用意されています。

Profiler::FPSメソッドは現在の画面描画がどれくらいのリフレッシュレートで行われているのかを取得することができるので、Printメソッドで表示させています。




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