Logo Signal From The Stars

Frames Per Second

Wat is genoeg?

Martin avatar
  • Martin
  • 4 min read
Sneller is niet altijd beter

Wat is genoeg?

Het type spel wat ik ga maken heeft niet veel FPS nodig. Maar even terug naar het begin, wat is het. Het is het aantal frames (scherm afbeeldingen) die getoond worden achter elkaar in 1 seconde.

Wanneer je zegt 30 FPS dan toon je in 1 seconde 30 afbeeldingen achter elkaar. Wanneer je een te laag aantal FPS hebt dan zie je schokkende beelden, die niet vloeiend zijn (dit is niet altijd het geval).

Op dit moment is mijn doel om minimaal 60 FPS te gebruiken en zoals je ziet is dat ook het geval 😄

Ingame fps

Ingame fps

Controle behouden

Na meedere artikelen gelezen te hebben over wat nu het beste is en het eenvoudigste. Het verschilt per type spel en platform. Aangezien ik voor betrouwbaarheid kies en omdat het een simpele 2D game is, heb ik gekozen om de framerate vast te zetten op 60.

Hiervoor gebruik ik https://github.com/bjornbytes/tick en daar weer een aanpassing bovenop zodat het er momenteel zo uitziet.

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

De rede dat ik dit doe is zodat ik er vanuit kan gaan dat altijd op 60FPS moet werken, en er geen rare dingen gebeuren indien er iemand een videokaart heeft die 10000FPS gebruikt. Met deze techniek blijft het ‘altijd’ stabiel.

Deltatime

In bijna alles wat beweegt gebruik ik deltatime om te voorkomen dat bewegingen out of sync gaan lopen.

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

Ook hebben de meeste monitoren de vsync optie, ik heb dit i.i.g. aangezet in Love2d.

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

Tot slot

Ik weet nog niet 100% of dat wat ik hier bedacht heb klopt met de praktijk, dit zal ik pas ervaren nadat er op verschillende computers en devices een en ander getest is. Helemaal correct voor alle systemen ga je het denk ik nooit krijgen. Er zijn natuurlijk ook nog computers die veel te traag zijn en waardoor het spel niet optimaal werkt. Op dit moment ben ik aan het experimenteren om in die gevallen een melding te laten zien.

    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
    )