Logo Signal From The Stars

Frames Per Second

What is enough?

Martin avatar
  • Martin
  • 4 min read
Faster is not always better

What is enough?

The type of game I am going to make does not need many FPS. But back to the beginning, what is it. It is the number of frames (screen images) that are shown in a row in 1 second.

When you say 30 FPS you show 30 images in a row in 1 second. When you have too low FPS you see jerky images, that are not smooth (this is not always the case).

At the moment my goal is to use at least 60 FPS and as you can see that is the case 😄

Ingame fps

Ingame fps

Keeping control

After reading multiple articles about what is best and easiest. It differs per type of game and platform. Since I choose reliability and because it is a simple 2D game, I chose to fix the framerate at 60.

For this I use https://github.com/bjornbytes/tick and another adjustment on top of that so that it currently looks like this.

local tick = {
  rate = 1 / 60
}

local timeAccumulator = 0

function love.run()
  love.load(love.arg.parseGameArguments(arg), arg)
  love.update(0)
  love.timer.step()

  return function()
    -- Handle events.
    love.event.pump()
    for name, a, b, c, d, e, f in love.event.poll() do
      if name == "quit" and not (love.quit and love.quit()) then
        return a or 0
      end
      love.handlers[name](a, b, c, d, e, f)
    end

    -- Get delta time.
    local _, _, flags = love.window.getMode()
    local dt = love.timer.step()

    if flags.vsync > 0 and flags.refreshrate > 0 then
      local perfectDt = 1 / flags.refreshrate

      if dt < perfectDt * 1.3 and dt > perfectDt / 1.3 then -- If dt is somewhat near perfect, force it to be actually perfect.
        dt = perfectDt
      else
        dt = math.min(dt, 1 / 20) -- In case the game is lagging, let it lag (but let the game think it's at least 20 FPS). Not clamping dt may put us in a situation where we can't do all required ticks in time and the game will more or less freeze.
      end
    end

    -- Update game.
    timeAccumulator = timeAccumulator + dt
    local didUpdate = false

    while timeAccumulator >= tick.rate do
      timeAccumulator = timeAccumulator - tick.rate
      didUpdate = true

      -- local updateStartTime = love.timer.getTime()
      love.update(tick.rate)
      -- love.timer.sleep(20*(love.timer.getTime()-updateStartTime)) -- DEBUG: Emulate a slower computer.
    end

    -- Draw game.
    if didUpdate then
      love.graphics.origin()
      love.graphics.clear(love.graphics.getBackgroundColor())
      love.draw()
      love.graphics.present()
    end

    if not (flags.vsync > 0 and didUpdate) then
      love.timer.sleep(.001) -- Not needed if vsync is on and we called present(), as present() will have slept for us.
    end
  end
end

The reason I do this is so I can assume it should always run at 60FPS, and no weird things happen if someone has a video card that uses 10000FPS. With this technique it will ‘always’ stay stable.

Deltatime

In almost everything that moves I use delta time to prevent movements from getting out of sync.

---Move point to destination
---@param dt number DeltaTime
---@return integer? x,integer? y,boolean? stopped
function Move:updateToDestination(dt)
    if self.vDestination == nil then
        return
    end

    local dist = self.vPosition:dist(self.vDestination)
    local step = self.speed * dt

Vsync

Also most monitors have the vsync option, I have turned this on in Love2d.

t.window.vsync = 1 -- Vertical sync mode (number)

Finally

I don’t know yet 100% whether what I have thought up here is correct in practice, I will only experience this after testing on various computers and devices. I think you will never get it completely correct for all systems. Of course there are also computers that are much too slow and as a result the game does not work optimally. At the moment I am experimenting to show a message in those cases.

    local checkFpsSeconds = .1
    local minimalFpsNeeded = 55
    local warningAfterSeconds = 4.0
    local startLoadingTime = love.timer.getTime()
    self.tick =
        Tick.recur(
        function()
            local fps = love.timer.getFPS()

            if tonumber(love.timer.getTime() - startLoadingTime) > warningAfterSeconds then
                loadingScreen.textObj:setText("You need " .. tostring(minimalFpsNeeded - fps) .. " FPS more.")
            end

            if fps > minimalFpsNeeded then
                self.tick:stop()

                -- everything is fast
            end
        end,
        checkFpsSeconds
    )

Loading external comment system...

βœ‰οΈ Stay informed!

Receive the latest news for free and motivate me to make this adventure a success!