Bilder per sekund

Hur mycket räcker?

Snabbare är inte alltid bättre

Hur mycket räcker?

Den typ av spel jag ska göra behöver inte många FPS. Men tillbaka till början: vad är det? Det är antalet frames (skärmbilder) som visas efter varandra under 1 sekund.

Med 30 FPS visar du 30 bilder efter varandra på 1 sekund. Om du har för få FPS ser du hackiga bilder som inte är flytande (det är inte alltid så).

Just nu är mitt mål att använda minst 60 FPS, och som du ser är det också så 😄

FPS i spelet

FPS i spelet

Behålla kontrollen

Jag har läst flera artiklar om vad som är bäst och enklast. Det skiljer sig mellan speltyper och plattformar. Eftersom jag väljer pålitlighet och det är ett enkelt 2D-spel har jag valt att låsa framerate på 60.

För det använder jag https://github.com/bjornbytes/tick med en egen anpassning ovanpå, så att det just nu ser ut så här.

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

Anledningen är att jag då kan utgå från att det alltid körs i 60 FPS och att inget konstigt händer om någon har ett grafikkort som kör 10000 FPS. Med den här tekniken förblir det ‘alltid’ stabilt.

Deltatime

I nästan allt som rör sig använder jag deltatime för att undvika att rörelser hamnar ur 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 flesta skärmar har också vsync, och det har jag i alla fall slagit på i Love2d.

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

Till sist

Jag vet ännu inte till 100 % om det jag har kommit på här stämmer i praktiken. Det får jag veta först när det har testats på olika datorer och enheter. Helt rätt för alla system kommer man nog aldrig att få det. Det finns förstås också datorer som är alldeles för långsamma, så att spelet inte fungerar optimalt. Just nu experimenterar jag med att visa ett meddelande i sådana fall.

    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
    )