Scalaでゲームプログラミング - vol5 FPSを制御してみる -


FPSって何ですか。そんな人は以下の記事を見てください。


上記のシステムを実装してみました。


時間計測にはcurrentTimeMillisを利用してミリ秒単位の時間を取得しています。

private def getTime() = System.currentTimeMillis()


FPSの値を設定できるようになっていてデフォルトの設定値が60です。
また、FPSが60なのでループ全体で16.666秒かかるようにウェイトをかけます。

rivate val mInterval : Double = if(fps!=0) 1000.0/fps else 0 
fps.scala
class FPS(fps:Double)
{
    def this() = this(60.0)

    //-----------------------------------------------------
    // initialize
    //-----------------------------------------------------
    private val mInterval : Double = if(fps!=0) 1000.0/fps else 0 
    private var mTime : Long = getTime()

    private val mFpsSize  : Int = 60    // sampling times
    private var mFpsCount : Int = 0        // loop counter
    private var mFpsArray : Array[Long]  = new Array[Long](mFpsSize)
    private var mFps :Double = 0
    val mIdealFps = fps

    /*-----------------------------------------------------
    // get fps value
    //
    // @param None
    // @return Double
    -----------------------------------------------------*/
    def get() : Double = mFps
    def getWithFormat() : String = mFps.toInt.toString()

    /*-----------------------------------------------------
    //  update fps data and wait
    //
    //  @param None
    //  @return Boolean
    -----------------------------------------------------*/
    def update() : Boolean = 
    {
        Thread.sleep(0)
        
        val pinterval = ( mFpsCount.toDouble     * mInterval )
        val ninterval = ( (mFpsCount+1).toDouble * mInterval )
        val ptime = mTime + pinterval.toLong // prev time
        val ntime = mTime + ninterval.toLong // next time

        var over = false
        
        if( ntime <= getTime() )
        {
            over = true
        }
        else
        {
            while( ntime > getTime() ) Thread.sleep(0)
        }

        mFpsArray(mFpsCount) = getTime() - ptime

        mFpsCount = (mFpsCount+1)%mFpsSize

        //-------------------------------------------------
        // caluculate fps rate value
        //-------------------------------------------------
        if( mFpsCount == 0 )
        {
            mFps = mFpsArray.foldLeft(0L){ _ + _ }.toDouble
            mFps = mFps / mFpsSize.toDouble
            mFps = 1000.0 / ( if(mFps!=0) mFps else 1.0)

            mTime = getTime()
        }

        over
    }

    private def getTime() = System.currentTimeMillis()
}


update関数を毎フレーム呼ぶことでFPSを計測及びウェイトを挿入しています。


まず、目標時間を設定します。それがntimeですね。
ntimeは0回目60回目120回目...の計測を起点として16.666*回数で取得しています。

val pinterval = ( mFpsCount.toDouble     * mInterval )
val ninterval = ( (mFpsCount+1).toDouble * mInterval )
val ptime = mTime + pinterval.toLong // prev time
val ntime = mTime + ninterval.toLong // next time


ntimeに到達するまでウェイトをかけます。

while( ntime > getTime() ) Thread.sleep(0)


また、ptimeは一つ前の目標時間です。(ptimeとの時差によりfpsを計測しています。)
scalaの配列は()で扱います。(HSPみたいですね)

mFpsArray(mFpsCount) = getTime() - ptime

最後に、60回ほどサンプリングを取ったら平均をとってmFpsにセットします。

if( mFpsCount == 0 )
{
    mFps = mFpsArray.foldLeft(0L){ _ + _ }.toDouble
    mFps = mFps / mFpsSize.toDouble
    mFps = 1000.0 / ( if(mFps!=0) mFps else 1.0)

    mTime = getTime()
}


時に、返し値overは初回の計測でntimeを超えていた場合はoverフラグをセットして
メインループの方でフレームスキップ(描画スキップ)に活用します。


メインループでの利用


メインループでは以下のように利用しています

while( !isEnd() )
{
    try{ update() } catch { case ex => throw ex }

    move()
    if( !fps.update() ) draw()

    Debugger.printTitle( "FPS="+fps.getWithFormat() )
}

fps.update()のところで描画スキップをしています。
ゲーム中に一番時間のかかるのは描画であることが多いですね。
(draw()に擬似乱数入れたりするとリプレイの時にバグることもあるので注意)


これで、ゲームプログラムの本当に基本的なところを作りました。
次からはいよいよ描画などの機能を追加していきます。