Khung hình mỗi giây

Bao nhiêu là đủ?

Nhanh hơn không phải lúc nào cũng tốt hơn

Bao nhiêu là đủ?

Thể loại trò chơi tôi sắp làm không cần nhiều FPS. Nhưng hãy quay lại từ đầu: nó là gì? Đó là số khung hình (hình ảnh trên màn hình) được hiển thị liên tiếp trong 1 giây.

Khi nói 30 FPS, nghĩa là trong 1 giây hiển thị 30 hình liên tiếp. Nếu FPS quá thấp, bạn sẽ thấy hình bị giật, không mượt (không phải lúc nào cũng vậy).

Hiện tại mục tiêu của tôi là dùng tối thiểu 60 FPS và như bạn thấy, đúng là như vậy 😄

FPS trong trò chơi

FPS trong trò chơi

Giữ quyền kiểm soát

Sau khi đọc nhiều bài viết về cách nào tốt nhất và đơn giản nhất, tôi thấy nó tùy vào thể loại trò chơi và nền tảng. Vì tôi chọn sự ổn định và đây là một trò chơi 2D đơn giản, tôi quyết định cố định framerate ở mức 60.

Để làm vậy tôi dùng https://github.com/bjornbytes/tick và thêm một chỉnh sửa của riêng mình, hiện tại nó trông như sau.

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

Tôi làm vậy để có thể mặc định rằng trò chơi luôn chạy ở 60 FPS, và không có chuyện lạ xảy ra nếu ai đó có card đồ họa chạy tới 10000 FPS. Với kỹ thuật này mọi thứ ’luôn’ ổn định.

Deltatime

Với hầu hết mọi thứ chuyển động, tôi dùng deltatime để tránh chuyển động bị lệch nhịp.

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

Phần lớn màn hình cũng có tùy chọn vsync, và dù sao tôi cũng đã bật nó trong Love2d.

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

Lời kết

Tôi vẫn chưa chắc 100% rằng những gì tôi nghĩ ra ở đây có đúng với thực tế không. Tôi chỉ biết được sau khi thử nghiệm trên nhiều máy tính và thiết bị khác nhau. Tôi nghĩ sẽ không bao giờ đúng hoàn toàn cho mọi hệ thống. Dĩ nhiên cũng có những máy tính quá chậm khiến trò chơi chạy không tối ưu. Hiện tôi đang thử hiển thị một thông báo trong những trường hợp đó.

    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
    )