Кадры в секунду
Сколько достаточно?

Сколько достаточно?
Игре того типа, который я делаю, много FPS не нужно. Но вернёмся к началу: что это такое? Это количество кадров (изображений на экране), которые показываются один за другим за 1 секунду.
30 FPS означает, что за 1 секунду показываются 30 изображений подряд. Если FPS слишком мало, картинка дёргается и выглядит не плавно (так бывает не всегда).
Сейчас моя цель использовать не меньше 60 FPS, и, как видите, так и есть 😄

FPS в игре
Сохранять контроль
Я прочитал несколько статей о том, что лучше и проще. Это зависит от типа игры и платформы. Поскольку я выбираю надёжность, а игра простая 2D, я решил зафиксировать частоту кадров на 60.
Для этого я использую https://github.com/bjornbytes/tick и свою доработку поверх, так что сейчас это выглядит так.
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
Я делаю это для того, чтобы можно было исходить из того, что игра всегда работает на 60 FPS и ничего странного не происходит, если у кого-то видеокарта выдаёт 10000 FPS. С этой техникой всё ‘всегда’ остаётся стабильным.
Deltatime
Почти во всём, что движется, я использую deltatime, чтобы движения не рассинхронизировались.
---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
У большинства мониторов есть и опция vsync, которую я в любом случае включил в Love2d.
t.window.vsync = 1 -- Vertical sync mode (number)
Напоследок
Я пока не уверен на 100 %, что всё придуманное здесь сработает на практике. Это станет ясно только после тестов на разных компьютерах и устройствах. Думаю, идеально для всех систем это никогда не получится. Конечно, есть и слишком медленные компьютеры, на которых игра работает не оптимально. Сейчас я экспериментирую с тем, чтобы в таких случаях показывать предупреждение.
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
)