Hi, I'm new to Love and programming in general, and I've run into a problem that I hope you guys can help me out on. I'm trying to render scaled pixel art sprites so that they align with the grid of pixels they would be drawn at, had they not been scaled. If I use delta time to move a character, the character will move in increments smaller than the scaled pixel size so that they move out of alignment with the surrounding pixels.
I searched this topic before making this post and saw that some members recommended drawing everything to a canvas, then scaling that canvas to the desired resolution. However, I had problems with this method as I don't really understand how drawing to a canvas works, especially when the amount of items start to add up. If anyone could help me with this problem, or offer a different solution, it would be much appreciated.
Here's an example to demonstrate my problem, hopefully I attached it correctly. If you move the character slightly off screen you'll notice you can move the character so that a column or row of it's pixels can be split in an unappealing way, this effect would become more obvious with lots of objects on the screen. Use the arrow keys to move.
Pixel Perfect Rendering
Forum rules
Before you make a thread asking for help, read this.
Before you make a thread asking for help, read this.
Pixel Perfect Rendering
- Attachments
-
- game.love
- (3.51 KiB) Downloaded 259 times
Re: Pixel Perfect Rendering
One hacky solution could be:
The point being that your simulation uses floating point numbers
but when rendering we round the position to the nearest integer.
This may produce strange jitters when moving diagonally.
A better option for old-school games
could be to use integers for the simulation with a fixed time step.
In which case you can ignore 'dt' and you don't have to round positions.
Code: Select all
local _floor = math.floor
function love.draw()
love.graphics.scale(windowScaleX, windowScaleY)
love.graphics.setColor(255, 255, 255)
love.graphics.rectangle("fill", 0, 0, 320, 180)
-- round
local x = _floor(player.x + 0.5)
local y = _floor(player.y + 0.5)
love.graphics.draw(player.image, x, y)
end
but when rendering we round the position to the nearest integer.
This may produce strange jitters when moving diagonally.
A better option for old-school games
could be to use integers for the simulation with a fixed time step.
In which case you can ignore 'dt' and you don't have to round positions.
Code: Select all
local accum = 0 -- accumulator
local interval = 1/30 -- 30 FPS (fixed interval step)
function love.update(dt)
accum = accum + dt
local skip = 10 -- maximum frameskip
while accum >= interval and skip > 0 do
skip = skip - 1
accum = accum - interval
-- input
-- 1 pixel per frame or 30 pixels per second
if love.keyboard.isDown("left") then
player.x = player.x - 1
end
if love.keyboard.isDown("right") then
player.x = player.x + 1
end
if love.keyboard.isDown("up") then
player.y = player.y - 1
end
if love.keyboard.isDown("down") then
player.y = player.y + 1
end
-- game logic
end
end
Last edited by ivan on Fri Feb 19, 2016 10:24 am, edited 2 times in total.
Re: Pixel Perfect Rendering
Thanks ivan! That works perfectly. I didn't anticipate the solution being so simple.
-
- Citizen
- Posts: 52
- Joined: Wed Dec 23, 2015 4:03 pm
Re: Pixel Perfect Rendering
You can scale the object coordinates, as well. That is, you work (for example) with coordinates in the range [0, 100] then, prior blitting, truncate and scale the position by 3 and here you are.
Re: Pixel Perfect Rendering
Drawing everything to a canvas and then scaling that canvas afterwards is definitely the better solution. I don't recommend what ivan suggested because (a) it forces everything to move in exact integer magnitudes (b) it ties your game logic to a specific framerate.
EDIT: I've actually had a look around and have seen some reasonable arguments of why it may be a good idea to use a fixed framerate (or tickrate, more specifically) so point "b" may not be such a big deal afterall. However, I still believe that drawing to a canvas, then drawing that canvas scaled at the end is correct.
Something very close to this should work:
EDIT: I've actually had a look around and have seen some reasonable arguments of why it may be a good idea to use a fixed framerate (or tickrate, more specifically) so point "b" may not be such a big deal afterall. However, I still believe that drawing to a canvas, then drawing that canvas scaled at the end is correct.
Something very close to this should work:
Code: Select all
local canvas = love.graphics.newCanvas(320, 240)
local scale = 3
function love.draw()
love.graphics.setCanvas(canvas)
-- Do all drawing here as usual.
-- You should round all love.graphics.draw coordinates or you will get visual bugs around the edges of your sprites.
-- eg. love.graphics.draw(player.image, math.floor(x + 0.5), math.floor(y + 0.5))
love.graphics.setCanvas()
love.graphics.draw(canvas, 0, 0, 0, scale, scale);
end
Who is online
Users browsing this forum: Google [Bot], Majestic-12 [Bot] and 4 guests