Frames pro Sekunde

Wie viel ist genug?

Schneller ist nicht immer besser

Wie viel ist genug?

Die Art Spiel, die ich machen werde, braucht nicht viele FPS. Aber zurück zum Anfang: Was ist das? Es ist die Anzahl der Frames (Bildschirmbilder), die in 1 Sekunde hintereinander gezeigt werden.

Bei 30 FPS zeigst du in 1 Sekunde 30 Bilder hintereinander. Wenn du zu wenige FPS hast, siehst du ruckelnde Bilder, die nicht flüssig sind (das ist nicht immer so).

Im Moment ist mein Ziel, mindestens 60 FPS zu nutzen, und wie du siehst, ist das auch der Fall 😄

FPS im Spiel

FPS im Spiel

Die Kontrolle behalten

Ich habe mehrere Artikel darüber gelesen, was nun am besten und am einfachsten ist. Das unterscheidet sich je nach Spieltyp und Plattform. Da ich auf Zuverlässigkeit setze und es ein einfaches 2D-Spiel ist, habe ich die Framerate auf 60 festgelegt.

Dafür nutze ich https://github.com/bjornbytes/tick mit einer eigenen Anpassung obendrauf, sodass es im Moment so aussieht.

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

Ich mache das, damit ich davon ausgehen kann, dass es immer mit 60 FPS läuft und keine seltsamen Dinge passieren, wenn jemand eine Grafikkarte mit 10000 FPS hat. Mit dieser Technik bleibt es ‘immer’ stabil.

Deltatime

Bei fast allem, was sich bewegt, nutze ich Deltatime, damit Bewegungen nicht aus dem Takt geraten.

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

Die meisten Monitore haben auch die Option vsync, die ich in Love2d jedenfalls eingeschaltet habe.

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

Zum Schluss

Ich weiß noch nicht zu 100 %, ob das, was ich mir hier ausgedacht habe, in der Praxis stimmt. Das werde ich erst erfahren, wenn auf verschiedenen Computern und Geräten einiges getestet wurde. Ganz korrekt für alle Systeme wird man es wohl nie hinbekommen. Es gibt natürlich auch Computer, die viel zu langsam sind und auf denen das Spiel nicht optimal läuft. Im Moment experimentiere ich damit, in solchen Fällen einen Hinweis anzuzeigen.

    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
    )