以下の内容はhttps://en.bioerrorlog.work/entry/ebitengine-fps-tpsより取得しました。


The Difference Between FPS and TPS in Ebitengine

This article summarizes the meaning and behavior differences between FPS (Frames Per Second) and TPS (Ticks Per Second) in Ebitengine.

Introduction

Ebitengine provides two concepts for game updates:

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

At first, I couldn’t understand the difference between them, so I wrote down their behavior as a memo.

Note: This article was translated from my original post.

The Differences Between FPS and TPS in Ebitengine

Overview

First, let’s summarize their characteristics.

FPS TPS
Overview Frequency of rendering updates per second Frequency of game logic updates per second
Implementation Draw() is called based on FPS Update() is called based on TPS
Control Determined by the display settings of the runtime environment Determined by Ebitengine settings (e.g., SetTPS())

It’s easier to understand if you think of FPS as the drawing update frequency (Draw()), and TPS as the game logic update frequency (Update()).

Another important point is that FPS depends on the player’s display refresh rate settings and is not controlled by Ebitengine.

In contrast, TPS/Update() is controlled inside Ebitengine (default: 60 tps, configurable via SetTPS()), so game logic that depends on update frequency should generally be implemented in Update().

Testing

Now let’s adjust FPS and TPS and check how many times Draw() and Update() are called.

I prepared a simple test code like the one below.

Using this, we check the obtained TPS/FPS values and the actual call counts of Update()/Draw() under different TPS settings and display refresh‑rate conditions.

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)
    }
}

TPS 60, Display Refresh Rate 60

Here are the results when TPS is set to 60 and the display refresh rate is also 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

Both TPS and FPS are around 60, and Update() and Draw() are each called about 60 times per second.

TPS 100, Display Refresh Rate 60

Next, TPS is set to 100 while the display refresh rate stays at 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 becomes 100, and Update() is called 100 times per second.

Meanwhile, because the display refresh rate is still 60, FPS and the Draw() call count remain at 60.

TPS 60, Display Refresh Rate 50

Finally, TPS remains 60 but the display refresh rate is set to 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 and Draw() call frequency drop to 50/sec.

This shows that the player’s display settings affect FPS and Draw() frequency.

TPS/Update() call frequency remains at the configured 60, unaffected by FPS.


The characteristics described above are confirmed again through this test.

Conclusion

In this article, I summarized the characteristics of TPS and FPS in Ebitengine and verified them with simple tests.

These were concepts I had only understood vaguely, so this was a good learning experience.

I hope it helps someone!

[Related Articles]

en.bioerrorlog.work

References




以上の内容はhttps://en.bioerrorlog.work/entry/ebitengine-fps-tpsより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14