Bilder per sekund

Hva er nok?

Raskere er ikke alltid bedre

Hva er nok?

Den typen spill jeg skal lage, trenger ikke mange FPS. Men tilbake til begynnelsen: hva er det? Det er antallet frames (skjermbilder) som vises etter hverandre i løpet av 1 sekund.

Med 30 FPS viser du 30 bilder etter hverandre på 1 sekund. Har du for få FPS, ser du hakkete bilder som ikke er flytende (det er ikke alltid tilfellet).

Akkurat nå er målet mitt å bruke minst 60 FPS, og som du ser, er det også tilfellet 😄

FPS i spillet

FPS i spillet

Beholde kontrollen

Jeg har lest flere artikler om hva som er best og enklest. Det varierer med spilltype og plattform. Siden jeg velger pålitelighet, og fordi det er et enkelt 2D-spill, har jeg valgt å låse framerate på 60.

Til det bruker jeg https://github.com/bjornbytes/tick med en egen tilpasning oppå, slik at det akkurat nå ser slik ut.

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

Grunnen til at jeg gjør dette, er at jeg da kan gå ut fra at det alltid kjører i 60 FPS, og at det ikke skjer rare ting hvis noen har et skjermkort som kjører 10000 FPS. Med denne teknikken holder det seg ‘alltid’ stabilt.

Deltatime

I nesten alt som beveger seg, bruker jeg deltatime for å unngå at bevegelser kommer ut av synk.

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

De fleste skjermer har også vsync, og det har jeg i hvert fall slått på i Love2d.

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

Til slutt

Jeg vet ennå ikke 100 % om det jeg har funnet ut her, stemmer i praksis. Det får jeg først vite når det er testet på ulike datamaskiner og enheter. Helt riktig for alle systemer får man det nok aldri. Det finnes selvfølgelig også datamaskiner som er altfor trege, slik at spillet ikke fungerer optimalt. Akkurat nå eksperimenterer jeg med å vise en melding i slike tilfeller.

    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
    )