Images par seconde

Combien, c’est assez ?

Plus rapide n’est pas toujours mieux

Combien, c’est assez ?

Le type de jeu que je vais faire n’a pas besoin de beaucoup de FPS. Mais revenons au début : qu’est-ce que c’est ? C’est le nombre d’images (images à l’écran) affichées les unes après les autres en 1 seconde.

À 30 FPS, on affiche 30 images à la suite en 1 seconde. Avec trop peu de FPS, on voit des images saccadées, pas fluides (ce n’est pas toujours le cas).

Pour l’instant, mon objectif est d’utiliser au moins 60 FPS et, comme vous le voyez, c’est bien le cas 😄

FPS dans le jeu

FPS dans le jeu

Garder le contrôle

J’ai lu plusieurs articles sur ce qui est le mieux et le plus simple. Ça dépend du type de jeu et de la plateforme. Comme je privilégie la fiabilité et qu’il s’agit d’un simple jeu en 2D, j’ai choisi de fixer le framerate à 60.

Pour cela, j’utilise https://github.com/bjornbytes/tick, avec une adaptation par-dessus, ce qui donne actuellement ceci.

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

Je fais ça pour pouvoir partir du principe que tout tourne toujours à 60 FPS, et pour éviter des choses bizarres si quelqu’un a une carte graphique qui monte à 10000 FPS. Avec cette technique, ça reste ’toujours’ stable.

Deltatime

Dans presque tout ce qui bouge, j’utilise le deltatime pour éviter que les mouvements se désynchronisent.

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

La plupart des écrans ont aussi l’option vsync, que j’ai en tout cas activée dans Love2d.

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

Pour finir

Je ne sais pas encore à 100 % si ce que j’ai imaginé ici correspond à la pratique. Je ne le saurai qu’après quelques tests sur différents ordinateurs et appareils. Je pense qu’on n’arrivera jamais à un résultat parfait sur tous les systèmes. Il y a bien sûr aussi des ordinateurs beaucoup trop lents, sur lesquels le jeu ne tourne pas de façon optimale. En ce moment, je fais des essais pour afficher un message dans ces cas-là.

    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
    )