Billeder i sekundet

Hvad er nok?

Hurtigere er ikke altid bedre

Hvad er nok?

Den type spil, jeg skal lave, har ikke brug for mange FPS. Men lad os starte forfra: Hvad er det? Det er antallet af frames (skærmbilleder), der vises efter hinanden på 1 sekund.

Ved 30 FPS viser du 30 billeder efter hinanden på 1 sekund. Har du for få FPS, ser du hakkende billeder, der ikke er flydende (det er ikke altid tilfældet).

Lige nu er mit mål at bruge mindst 60 FPS, og som du kan se, er det også tilfældet 😄

FPS i spillet

FPS i spillet

Bevare kontrollen

Jeg har læst flere artikler om, hvad der er bedst og nemmest. Det afhænger af typen af spil og platformen. Da jeg vælger pålidelighed, og fordi det er et simpelt 2D-spil, har jeg valgt at låse framerate fast på 60.

Til det bruger jeg https://github.com/bjornbytes/tick med en tilpasning ovenpå, så det lige nu ser sådan ud.

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

Grunden til, at jeg gør det, er, at jeg så kan gå ud fra, at det altid kører med 60 FPS, og at der ikke sker mærkelige ting, hvis nogen har et grafikkort, der kører 10000 FPS. Med denne teknik forbliver det ‘altid’ stabilt.

Deltatime

I næsten alt, der bevæger sig, bruger jeg deltatime for at undgå, at bevægelser kommer ud af 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

De fleste skærme har også vsync-muligheden, som jeg i hvert fald har slået til i Love2d.

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

Til sidst

Jeg ved endnu ikke 100 %, om det, jeg har fundet på her, passer med virkeligheden. Det finder jeg først ud af, når det er testet på forskellige computere og enheder. Helt korrekt til alle systemer får man det nok aldrig. Der findes selvfølgelig også computere, der er alt for langsomme, så spillet ikke kører optimalt. Lige nu eksperimenterer jeg med at vise en besked i de tilfælde.

    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
    )