Quadros por segundo

Quanto é suficiente?

Mais rápido nem sempre é melhor

Quanto é suficiente?

O tipo de jogo que vou fazer não precisa de muitos FPS. Mas voltando ao começo: o que é isso? É o número de quadros (imagens na tela) mostrados em sequência em 1 segundo.

Com 30 FPS, você mostra 30 imagens em sequência em 1 segundo. Com FPS baixo demais, você vê imagens travadas, que não são fluidas (nem sempre é o caso).

No momento meu objetivo é usar pelo menos 60 FPS e, como você pode ver, é o que acontece 😄

FPS no jogo

FPS no jogo

Mantendo o controle

Li vários artigos sobre o que é melhor e mais simples. Depende do tipo de jogo e da plataforma. Como escolho confiabilidade e como é um jogo 2D simples, decidi fixar o framerate em 60.

Para isso uso https://github.com/bjornbytes/tick com um ajuste meu por cima, e no momento está assim.

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

Faço isso para poder partir do princípio de que tudo sempre roda a 60 FPS e que nada estranho acontece se alguém tiver uma placa de vídeo que chega a 10000 FPS. Com essa técnica fica ‘sempre’ estável.

Deltatime

Em quase tudo o que se move uso deltatime para evitar que os movimentos saiam de sincronia.

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

A maioria dos monitores também tem a opção vsync, que de qualquer forma ativei no Love2d.

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

Para terminar

Ainda não sei 100% se o que pensei aqui funciona na prática. Só vou descobrir depois de testar em vários computadores e dispositivos. Acho que nunca vai ficar perfeito para todos os sistemas. Claro que também existem computadores lentos demais, nos quais o jogo não roda da melhor forma. No momento estou experimentando mostrar um aviso nesses casos.

    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
    )