BioErrorLog Tech Blog

試行錯誤の記録

EbitengineにおけるFPSとTPSの違いを理解する

EbitengineにおけるFPS (Frames Per Second)とTPS (Ticks Per Second)の意味と挙動の違いをまとめます。

はじめに

Ebitengineにおけるゲーム更新の概念として、

  • FPS (Frames Per Second)
  • TPS (Ticks Per Second)

の2つがあります。

当初これらの違いが分からず戸惑ったので、挙動と違いを備忘録にまとめます。

EbitengineにおけるFPSとTPSの違い

概要

まずFPSとTPSの特徴を整理します。

FPS TPS
概要 1秒間の描画処理更新頻度 1秒間のゲームロジック更新頻度
実装 Draw()がFPSに基づいて呼ばれる Update()がTPSに基づいて呼ばれる
制御 実行環境のディスプレイ設定によってFPSが決まる Ebitengine側での設定値(例:SetTPS())によってTPSが決まる

FPSはDraw()呼び出し頻度を指す描画処理、TPSはUpdate()呼び出し頻度を指すゲームロジック更新、と捉えると理解しやすいです。

また重要な特徴として、FPSはプレイヤーの実行環境(ディスプレイのリフレッシュレート設定)によって決まる値であり、Ebitengine側で制御するものではない、という点があります。

TPS/Update()の方はEbitengine側で制御する値(デフォルト60tps/SetTPS()で設定可能)なので、処理の呼び出し頻度に影響するゲームロジックは基本的にUpdate()で実装することになります。

検証

では、実際にFPSとTPSを制御し、それに基づいたDraw()/Update()の呼び出し回数を調べてみます。

下記のような簡単な検証コードを用意しました。

これを用いて、TPS設定とディスプレイリフレッシュレートの各条件下における、TPS/FPSの取得値およびUpdate()/Draw()の呼び出し回数を調べてみます。

github.com

package main

import (
    "log"
    "time"

    "github.com/hajimehoshi/ebiten/v2"
)

const (
    TPS = 60
)

type Game struct {
    updateCount int
    drawCount   int
    perSec      time.Time
}

func (g *Game) Update() error {
    now := time.Now()
    g.updateCount++

    // Debug print per sec
    if now.Sub(g.perSec) >= time.Second {
        log.Printf("TPS: %.2f, FPS: %.2f", ebiten.ActualTPS(), ebiten.ActualFPS())
        log.Printf("Update() was called in this sec: %d times", g.updateCount)
        log.Printf("Draw() was called in this sec: %d times\n\n", g.drawCount)

        g.updateCount = 0
        g.drawCount = 0
        g.perSec = now
    }

    return nil
}

func (g *Game) Draw(screen *ebiten.Image) {
    g.drawCount++
}

func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
    return 640, 480
}

func main() {
    game := &Game{
        perSec: time.Now(),
    }

    ebiten.SetWindowSize(640, 480)
    ebiten.SetWindowTitle("Show FPS and TPS with Update()/Draw()")
    ebiten.SetTPS(TPS)
    if err := ebiten.RunGame(game); err != nil {
        log.Fatal(err)
    }
}

TPS60, ディスプレイリフレッシュレート60

まずはTPSを60に設定し、ディスプレイリフレッシュレートを60に設定した場合の結果がこちら:

2024/11/25 14:41:54 TPS: 59.92, FPS: 59.92
2024/11/25 14:41:54 Update() was called in this sec: 60 times
2024/11/25 14:41:54 Draw() was called in this sec: 60 times

2024/11/25 14:41:55 TPS: 60.09, FPS: 60.09
2024/11/25 14:41:55 Update() was called in this sec: 61 times
2024/11/25 14:41:55 Draw() was called in this sec: 61 times

2024/11/25 14:41:56 TPS: 59.98, FPS: 59.98
2024/11/25 14:41:56 Update() was called in this sec: 61 times
2024/11/25 14:41:56 Draw() was called in this sec: 61 times

TPS、FPSともに60を指し、Update()Draw()も両方60回/秒の頻度で呼ばれていることが分かります。

TPS100, ディスプレイリフレッシュレート60

次にTPSを100に設定し、ディスプレイリフレッシュレートを変わらず60に設定した場合の結果がこちら:

2024/11/25 14:46:47 TPS: 100.32, FPS: 60.00
2024/11/25 14:46:47 Update() was called in this sec: 100 times
2024/11/25 14:46:47 Draw() was called in this sec: 60 times

2024/11/25 14:46:48 TPS: 100.41, FPS: 60.05
2024/11/25 14:46:48 Update() was called in this sec: 100 times
2024/11/25 14:46:48 Draw() was called in this sec: 60 times

2024/11/25 14:46:49 TPS: 99.92, FPS: 59.95
2024/11/25 14:46:49 Update() was called in this sec: 100 times
2024/11/25 14:46:49 Draw() was called in this sec: 60 times

TPS値は100になり、Update()も1秒間に100回呼び出されていることがわかります。

一方でディスプレイリフレッシュレートは60から変更していないので、取得されたFPS値およびDraw()の呼び出し回数は依然60/秒のまま変わりません。

TPS60, ディスプレイリフレッシュレート50

では最後に、TPSは60のまま、ディスプレイリフレッシュレートを50に変更してみます。

2024/11/25 15:04:59 TPS: 60.00, FPS: 50.00
2024/11/25 15:04:59 Update() was called in this sec: 60 times
2024/11/25 15:04:59 Draw() was called in this sec: 50 times

2024/11/25 15:05:00 TPS: 59.86, FPS: 50.05
2024/11/25 15:05:00 Update() was called in this sec: 61 times
2024/11/25 15:05:00 Draw() was called in this sec: 51 times

2024/11/25 15:05:01 TPS: 59.94, FPS: 49.95
2024/11/25 15:05:01 Update() was called in this sec: 60 times
2024/11/25 15:05:01 Draw() was called in this sec: 50 times

FPS値およびDraw()の呼び出し回数が50/秒になりました。

プレイヤー実行環境のディスプレイ設定によって、FPS値とDraw()呼び出し頻度が左右されていることがわかります。

一方、TPS値/Update()呼び出し頻度は、FPS値に関係なく設定値60のままです。


以上、先述したFPS/TPSの特性が今回の検証によっても再現されました。

おわりに

今回はEbitengineにおけるTPSとFPSの特徴を整理し、その裏付けを取る簡単な検証をしました。

個人的に雰囲気でしか理解していない概念だったので、良い勉強になりました。

どなたかの参考になれば幸いです。

[関連記事]

www.bioerrorlog.work

参考