3

I created a simple code using Love2d that display a map created with Tiled (pixel art style), and draw a square as a player who can freely move across the map. However there is some jitter or flickering that occur only when I move the player. It's even more visible when the map use texture that show many lines (like fence, window etc.).

For movement I use:

 local moveX, moveY = 0, 0
    if love.keyboard.isDown("right") then moveX = 1 end
    if love.keyboard.isDown("left")  then moveX = -1 end
    if love.keyboard.isDown("down")  then moveY = 1 end
    if love.keyboard.isDown("up")    then moveY = -1 end

    local norm = math.sqrt(moveX^2 + moveY^2)

    if norm > 0 then
        player.x = player.x + (moveX / norm) * player.speed * dt
        player.y = player.y + (moveY / norm) * player.speed * dt
    end

However if I replace the delta time dt with a fixed value of 0.01 and a player speed of 100 it's working well, but as soon as i use a real dt or a fixed dt of 0.01 with another speed (like 150) there is some flickering.

How can I move across a simple Tiled map in Love2D without flickering?

Here is my full code, the map.lua is just a simple tiled map exported to .lua within tiled.

local sti = require("sti")

local map
local player = {}
local camera = { x = 0, y = 0 }

-- Résolution virtuelle fixe
local virtualWidth, virtualHeight = 400, 320
local screenWidth, screenHeight = 800, 640

local canvas

function createCanvas()
    canvas = love.graphics.newCanvas(virtualWidth, virtualHeight)
    canvas:setFilter("nearest", "nearest")
end

function love.load()
    love.window.setMode(screenWidth, screenHeight, {
        resizable = true,
        vsync = true
    })

    createCanvas()

    map = sti("map.lua")

    -- Joueur représenté par un simple carré
    player.width = 32
    player.height = 32
    player.x = 0
    player.y = 0
    player.speed = 100
end

function love.update(dt)
   -- dt=0.01
    local moveX, moveY = 0, 0
    if love.keyboard.isDown("right") then moveX = 1 end
    if love.keyboard.isDown("left")  then moveX = -1 end
    if love.keyboard.isDown("down")  then moveY = 1 end
    if love.keyboard.isDown("up")    then moveY = -1 end

    local norm = math.sqrt(moveX^2 + moveY^2)

    if norm > 0 then
        player.x = player.x + (moveX / norm) * player.speed * dt
        player.y = player.y + (moveY / norm) * player.speed * dt
    end

    -- Caméra centrée sur le joueur
    camera.x = math.floor(player.x) - virtualWidth / 2
    camera.y = math.floor(player.y) - virtualHeight / 2

    map:update(dt)
end

function love.draw()
    love.graphics.setCanvas(canvas)
    love.graphics.clear()

    love.graphics.push()
    love.graphics.translate(-camera.x, -camera.y)

    map:draw(-camera.x, -camera.y)

    -- Dessiner le joueur (carré bleu)
    love.graphics.setColor(0, 0, 1)
    love.graphics.rectangle("fill", player.x, player.y, player.width, player.height)
    love.graphics.setColor(1, 1, 1)

    love.graphics.pop()
    love.graphics.setCanvas()

    -- Affichage du canvas avec mise à l’échelle fluide
    local scaleX = screenWidth / virtualWidth
    local scaleY = screenHeight / virtualHeight
    local scale = math.min(scaleX, scaleY)
    local offsetX = (screenWidth - virtualWidth * scale) / 2
    local offsetY = (screenHeight - virtualHeight * scale) / 2

    love.graphics.setColor(1, 1, 1, 1)
    love.graphics.draw(canvas, offsetX, offsetY, 0, scale, scale)
end

function love.resize(w, h)
    screenWidth = w
    screenHeight = h
end

You can download my simple map.lua here

1 Answer 1

2

Keep float positions for movement but round to integers during rendering to align with the pixel grid:

local camX = math.floor(camera.x + 0.5)
local camY = math.floor(camera.y + 0.5)
love.graphics.translate(-camX, -camY)
map:draw(-camX, -camY)
love.graphics.rectangle("fill", math.floor(player.x + 0.5), math.floor(player.y + 0.5), player.width, player.height)

Also, round canvas scaling offsets and use an integer scale factor to avoid estimating unknown data points using a set of data points that are known:

local scale = math.floor(math.min(scaleX, scaleY))
local offsetX = math.floor((screenWidth - virtualWidth * scale) / 2)
local offsetY = math.floor((screenHeight - virtualHeight * scale) / 2)
Sign up to request clarification or add additional context in comments.

3 Comments

round down specifically indeed, like you do with the math.floor(). The original code is rounding at some point, but it is unclear exactly how it happens.
actualy rounding the canvas scalling and also rounding player position player.x = player.x + math.floor( (moveX / norm) * player.speed * dt) did the trick. even without rounding at translate() and draw(). also removed the rounding at camera.x = player.x - virtualWidth / 2
good for you man. I'm happy that it worked for you

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.