[solved] example request , canvas scale - camera - pixel perfect - simplified
Re: example request , canvas scale - camera - pixel perfect - simplified
I didn't intend for my example to show how scaled sprites can look better when using subpixels, but sure, you could use subpixels for that purpose I guess. (See Xeodrifter for somewhat interesting usage of scale.) Movement also become smoother, if sprite coordinates aren't rounded when drawn (or are rounded, but to the subpixels instead of the "full" pixels). Here's an example and comparison with parallax layers without and with usage of subpixels:
Notice how movements in the "new" version with subpixels are a bit smoother.
Your image with the "glitchy" text shows the issue with using nearest filtering, i.e. that some pixels look a lot wider and/or taller than their neighbors, which is what the linear+subpixel combination (which make pixel borders slightly blurry) is supposed to fix.
In the upper version the pixel sizes and lines are all wacky, while in the lower version the pixel sizes and lines look more uniform.
Notice how movements in the "new" version with subpixels are a bit smoother.
Your image with the "glitchy" text shows the issue with using nearest filtering, i.e. that some pixels look a lot wider and/or taller than their neighbors, which is what the linear+subpixel combination (which make pixel borders slightly blurry) is supposed to fix.
In the upper version the pixel sizes and lines are all wacky, while in the lower version the pixel sizes and lines look more uniform.
Tools: Hot Particles, LuaPreprocess, InputField, (more) Games: Momento Temporis
"If each mistake being made is a new one, then progress is being made."
"If each mistake being made is a new one, then progress is being made."
Re: example request , canvas scale - camera - pixel perfect - simplified
Short story: can you extend the first example with moving camera and screenToWorldMouseXY / worldToScreenMouseXY ?
So I can extract the code and re-use that, I have most of the code now but that part is still not working.
Long story:
After your example and some weeks later... I finally have implemented your code.
Most of the time I had problem with font scaling and get the correct mouse coordinates when scaling the window or using fullscreen.
I don't use a hardware cursor (image), but I draw somethings using the mouse coordinates.
But that is not the problem anymore, that is working.
Now I spend some time creating the moving 'camera' but now i'm confused.
Maybe your can solve this last part for me?
canvas.lua
Code: Select all
function Canvas:new(data)
self.name = data.name
self.x = data.x or 0
self.y = data.y or 0
self.width = data.width or love.graphics.getWidth()
self.height = data.height or love.graphics.getHeight()
self.subpixels = data.subpixels
self.integerScaling = data.integerScaling
-- the real canvas
self.canvas = love.graphics.newCanvas(self.width * self.subpixels, self.height * self.subpixels)
self.canvas:setFilter("nearest", "nearest")
-- calculated
self.scale = 1
self.scaledWidth = nil
self.scaledHeight = nil
entityManager:add(self)
end
function Canvas:setSubpixels(s)
self.subpixels = s
end
function Canvas:setX(x)
self.x = x
end
function Canvas:setY(y)
self.y = y
end
function Canvas:getX()
return self.x
end
function Canvas:getY()
return self.y
end
function Canvas:getSubpixels()
return self.subpixels
end
---
-- This is without the subpixels
function Canvas:getHeight()
return self.height
end
---
-- This is without the subpixels
function Canvas:getWidth()
return self.width
end
---
-- This is with the optional subpixels and current render
function Canvas:getRenderHeight()
return self:getCanvas():getHeight()
end
---
-- This is with the optional subpixels and current render
function Canvas:getRenderWidth()
return self:getCanvas():getWidth()
end
---
-- The calculated width scaled
function Canvas:getScaledWidth()
return self.scaledWidth
end
---
-- The calculated height scaled
function Canvas:getScaledHeight()
return self.scaledHeight
end
---
-- The love canvas object
function Canvas:getCanvas()
return self.canvas
end
---
-- The calculated scale factor
function Canvas:getScale()
return self.scale
end
-- return width and height
function Canvas:getDimensions()
return self.canvas:getDimensions()
end
function Canvas:scaleMath()
local _, _, windowWidth, windowHeight = love.window.getSafeArea()
local canvasWidth, canvasHeight = self:getDimensions()
-- Fill as much of the window as possible with the canvas while preserving the aspect ratio.
self.scale = math.min(windowWidth / canvasWidth, windowHeight / canvasHeight)
-- self.scale = windowHeight / canvasHeight -- This would fill the height and possibly cut off the sides.
if self.integerScaling then
self.scale = math.floor(self.scale * self:getSubpixels()) / self:getSubpixels()
self.scale = math.max(self.scale, 1 / self:getSubpixels()) -- Avoid self.scale =0 if the window is tiny!
end
self.scaledWidth = canvasWidth * self.scale
self.scaledHeight = canvasHeight * self.scale
-- center canvas
self:setX(math.floor((windowWidth - self.scaledWidth) / 2))
self:setY(math.floor((windowHeight - self.scaledHeight) / 2))
end
-- function Canvas:release(args)
-- end
return Canvas
Code: Select all
self.canvas = {}
self.canvas.width = 320
self.canvas.height = 180
self.canvas.integerScaling = true
self.canvas.subpixels = 4
Code: Select all
-- 1x 320x180
-- 2x 640x360
-- 4x 1280x720 -- < minimal target height
-- 6x 1920x1080 (fullHD)
-- 12x 3840x2160 (4K)
t.window.width = 1280 -- The window width (number)
t.window.height = 720 -- The window height (number)
I was trying to implement a follow player x,y like https://github.com/a327ex/STALKER-X
But then with less code, without all the functions, to understand what is happening, and I only need the follow player
Code: Select all
function Camera:follow(x, y)
self.targetX, self.targetY = x, y
end
function Camera:setBounds(x, y, w, h)
self.bound = true
self.bounds_min_x = x
self.bounds_min_y = y
self.bounds_max_x = x + w
self.bounds_max_y = y + h
end
function Camera:attach()
-- extend inside the main.lua below the canvas things
love.graphics.translate(math.floor(self.width / 2), math.floor(self.height / 2))
love.graphics.translate(-math.floor(self.x), -math.floor(self.y))
end
function Camera:update(dt)
if self.targetX == nil or self.targetY == nil then
return
end
self.x, self.y = self.targetX, self.targetY
-- if self.bound then
-- self.x = math.min(math.max(self.x, self.bounds_min_x + self.width / 2), self.bounds_max_x - self.width / 2)
-- self.y = math.min(math.max(self.y, self.bounds_min_y + self.height / 2), self.bounds_max_y - self.height / 2)
-- end
end
Code: Select all
function draw()
self.canvas:scaleMath()
-- Draw to the canvas
love.graphics.push("all")
love.graphics.setCanvas(self.canvas:getCanvas())
love.graphics.clear()
love.graphics.scale(self.canvas:getSubpixels())
self.camera:attach() -- at the moment without push/pop
self.world:draw()
love.graphics.pop()
love.graphics.clear(0, 0, 0)
love.graphics.draw(self.canvas:getCanvas(), self.canvas:getX(), self.canvas:getY(), 0, self.canvas:getScale())
end
function love.mousepressed(xOrg, yOrg, button, istouch, presses)
local x, y = xOrg, yOrg
if self.cursor then
x, y = self.cursor:getScreenToWorldXY(x, y)
end
self.world:mousePressedEvent(
{
x = x,
y = y,
button = button,
istouch = istouch,
presses = presses
}
)
end
function love.resize(w, h)
-- calculate so we have the new variables
self.canvas:scaleMath()
-- forward to other entities
self.world:windowResizeEvent(
{
w = w,
h = h
}
)
end
Code: Select all
function Cursor:getScreenToWorldXY(x, y)
return self:getScreenToWorldX(x), self:getScreenToWorldY(y)
end
function Cursor:getScreenToWorldX(x)
x = x or love.mouse.getX()
return ((x - self.gameCanvas:getX()) / self.gameCanvas:getSubpixels()) / self.gameCanvas:getScale()
-- I have to do something here with the self.camera.x
end
function Cursor:getScreenToWorldY(y)
y = y or love.mouse.getY()
return ((y - self.gameCanvas:getY()) / self.gameCanvas:getSubpixels()) / self.gameCanvas:getScale()
-- I have to do something here with the self.camera.y
end
- I click at the right side
- Player is moving to the right side (camera moves to the left side, looks good for now)
- The cursor position is not correct anymore
I do mean local x, y = self:getScreenToWorldXY() is not correct.
First I thought , just do things like this but it don't work.
Code: Select all
function Cursor:getScreenToWorldX(x)
x = x or love.mouse.getX()
x = ((x - self.gameCanvas:getX()) / self.gameCanvas:getSubpixels()) / self.gameCanvas:getScale()
if self.camera then
x = x - self.camera.x
end
end
Re: example request , canvas scale - camera - pixel perfect - simplified
You're probably not too far off.
I've extended my initial example program with the concept of a world coordinate system, a movable player, and a camera that follows the player (in love.update). Also, screenToWorld and worldToScreen functions.
I've extended my initial example program with the concept of a world coordinate system, a movable player, and a camera that follows the player (in love.update). Also, screenToWorld and worldToScreen functions.
- Attachments
-
- PixelArtRendering.20220328.love
- (13.07 KiB) Downloaded 321 times
Tools: Hot Particles, LuaPreprocess, InputField, (more) Games: Momento Temporis
"If each mistake being made is a new one, then progress is being made."
"If each mistake being made is a new one, then progress is being made."
Re: example request , canvas scale - camera - pixel perfect - simplified
Thanks very much! give me some time to check and process this
Re: example request , canvas scale - camera - pixel perfect - simplified
Yes everything is working now, it fits perfectly.
Going too refactor now.
Only added some extra things for the camera because it's something like a point and click. The player is nog in the middle.
Using the camera example code from someone else, with your code gives me this fix:
snippet init
world snippet
player snippet
So now the camera stops when the scene width is end.
Going to refactor now, before I forget things
Really cool !
Going too refactor now.
Only added some extra things for the camera because it's something like a point and click. The player is nog in the middle.
Using the camera example code from someone else, with your code gives me this fix:
Code: Select all
function Camera:update(dt)
if self.targetX == nil or self.targetY == nil then
return
end
self.x = damp(self.x, self.targetX, self.followSpeed, dt) -- Move towards player smoothly.
self.y = damp(self.y, self.targetY, self.followSpeed, dt) -- Do camera.x=player.x etc. for instant snap.
if self.bound then
self.x = math.min(math.max(self.x, self.bounds_min_x + self.width / 2), self.bounds_max_x - self.width / 2)
self.y = math.min(math.max(self.y, self.bounds_min_y + self.height / 2), self.bounds_max_y - self.height / 2)
end
end
function Camera:setBounds(x, y, w, h)
self.bound = true
self.bounds_min_x = x
self.bounds_min_y = y
self.bounds_max_x = x + w
self.bounds_max_y = y + h
end
function Camera:follow(x, y)
self.targetX, self.targetY = x, y
end
snippet init
Code: Select all
Camera(
{
x = 0,
y = 0,
width = config.canvas.width,
height = config.canvas.height
}
)
Code: Select all
camera:setBounds(0, 0, scene.width / 4, self.canvas.height) -- 180 height
Code: Select all
camera.follow(player.x,player.y)
Going to refactor now, before I forget things
Really cool !
Re: example request , canvas scale - camera - pixel perfect - simplified
@ReFreezed
I did some other work, and now i'm at a point that the player is moving to the right/left and the camera is following.
Everything works, but what I see is a little jitter.
When I download your example again and use the same settings as I use inside the pixel art game
Code: Select all
integerScaling = true
subpixels = 4
cameraspeed = 5
Inside my game I use a static framerate with this code
https://github.com/bjornbytes/tick
Code: Select all
frameTick.framerate = 60
frameTick.rate = 1 / 60
Is it possible that you code always give some jitter?
Can it be optimised because I'm using the tick code above?
Or will there alway be some images that jitter (like your example code).
I already use (as far as I know, need to check this again) on each drawing
Code: Select all
function floor(n, subpixels)
return math.floor(n * (subpixels or 1) + .5) / (subpixels or 1)
end
Re: [solved] example request , canvas scale - camera - pixel perfect - simplified
I don't see any jitter in the background in my example with the settings you posted, but jitter can be caused by numerous things. (See my reply in that other thread.) If you force dt to be 1/60 in love.update, does it make movement smoother (in the example)?
Tools: Hot Particles, LuaPreprocess, InputField, (more) Games: Momento Temporis
"If each mistake being made is a new one, then progress is being made."
"If each mistake being made is a new one, then progress is being made."
Re: [solved] example request , canvas scale - camera - pixel perfect - simplified
Nope that don't fix it.
I 'fix' it for the moment by using the following things
- don't update the camera.y, because i'm only follow moving objects from left <> right
- don't use the damp function
With this it more smooth.
Going to work with this for now, and test it later on other hardware.
I 'fix' it for the moment by using the following things
- don't update the camera.y, because i'm only follow moving objects from left <> right
- don't use the damp function
With this it more smooth.
Going to work with this for now, and test it later on other hardware.
Re: [solved] example request , canvas scale - camera - pixel perfect - simplified
You'll have to explain what exactly you mean by jitter, or show a video or something of what's happening, before I can provide any better help.
Ditching the damp function is probably a good idea as it's not very clever about the movement. In my games I've always just fixed the camera on the player, so there's never any "jitter" specifically for the player sprite on the screen.
Ditching the damp function is probably a good idea as it's not very clever about the movement. In my games I've always just fixed the camera on the player, so there's never any "jitter" specifically for the player sprite on the screen.
Tools: Hot Particles, LuaPreprocess, InputField, (more) Games: Momento Temporis
"If each mistake being made is a new one, then progress is being made."
"If each mistake being made is a new one, then progress is being made."
Re: [solved] example request , canvas scale - camera - pixel perfect - simplified
@ReFreezed
Can you pm me your mailadres ? I can't PM the game, there is no upload option, and I don't want to upload the game here.
Or maybe I can upload it somewhere like wetransfer and I PM you the link. Is that oke ?
Maybe you can see the problem, I did change the code, so the camera is moving now from left to the right +1 px and every time it stutters at some point.
Can you pm me your mailadres ? I can't PM the game, there is no upload option, and I don't want to upload the game here.
Or maybe I can upload it somewhere like wetransfer and I PM you the link. Is that oke ?
Maybe you can see the problem, I did change the code, so the camera is moving now from left to the right +1 px and every time it stutters at some point.
Who is online
Users browsing this forum: Ahrefs [Bot], Google [Bot] and 2 guests