Collision issue with lower FPS
Posted: Fri Feb 14, 2014 1:22 pm
Hi,
This is my game: https://dl.dropboxusercontent.com/u/743494/MehMeh.love
It's my first semi-proper attempt at making game logic and first collision detection code. Now, it works extremely well with 500 FPS, but as soon as I turn on Vsync all kinds of stuff go wrong. I tried correcting it in various ways, but I couldn't make it behave.
The first issue you can notice with Vsync on (60 FPS) is that the player sinks a bit into the ground for one frame before sticking properly. I tried to correct it by checking more pixels downwards, but it looks like the 1-frame sink is happening when the sticking code runs -- hero.y = (mapY - 2) * 32 -- without it it's ok, but if I only zero the velocity, the place the player stays on is randomly +/- a few pixels up or down, which is even worse.
The second issue is that with Vsync on you can actually jump inside a wall if you take a running jump and, if you keep up pressed, you'll slide out of bounds. I assume it's because left/right collision detection is also a bit wonky, but since I only zero the velocity, it does not autocorrect the player and hence it's getting glued to the top of the tile underneath the one he's sinking into.
If anyone could point me somewhere with that I'd be grateful.
This is my game: https://dl.dropboxusercontent.com/u/743494/MehMeh.love
Code: Select all
function love.load(arg)
scaleAmount = love.graphics.getHeight()/480
base = {}
base.scale = 2
hero = {}
hero.sprite = love.graphics.newImage("assets/heroSprite.png")
hero.sprite:setFilter("linear", "nearest")
hero.width = 16
hero.height = 16
hero.spriteGrid = anim8.newGrid(hero.width, hero.height, hero.sprite:getWidth(), hero.sprite:getHeight())
hero.spriteAnimation = {
walk = anim8.newAnimation(hero.spriteGrid('2-5', 1), 0.1),
stand = anim8.newAnimation(hero.spriteGrid('1-1', 1), 0.1)
}
hero.x = 100
hero.y = 100
hero.vx = 0
hero.vy = 0
hero.speed = 175
hero.scale = base.scale
hero.onGround = false
tile = {}
tile[0] = love.graphics.newImage("assets/clear.png")
tile[1] = love.graphics.newImage("assets/placeholder16x16.png")
tile[1]:setFilter("linear", "nearest")
tile.width = 16 * base.scale
tile.height = 16 * base.scale
map = {
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{ 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{ 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{ 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{ 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1},
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
}
map.height = 15
map.width = 25
map.x = 0
map.y = 0
map.offsetX = 0
map.offsetY = 0
--map.displayHeight = 30
--map.displayWidth = 30
end
function love.draw()
love.graphics.push()
love.graphics.scale(scaleAmount,scaleAmount)
love.graphics.print("x: " .. hero.x
.. "\ny: " .. hero.y
.. "\nvx: " .. hero.vx
.. "\nvy: " .. hero.vy
.. "\nonGround: " .. tostring(hero.onGround)
.. "\nfps: " .. love.timer.getFPS(),
64, 32)
love.graphics.setBackgroundColor(0,0,0)
drawMap()
hero.spriteState:draw(hero.sprite, hero.x, hero.y, 0, hero.scale, base.scale, hero.width/2)
love.graphics.pop()
end
function drawMap()
for y = 1, map.height do
for x = 1, map.width do
love.graphics.draw(
tile[map[y + map.y][x + map.x]],
((x - 1) * tile.width) + map.offsetX,
((y - 1) * tile.height) + map.offsetY,
0, base.scale)
end
end
end
function love.update(dt)
hero.spriteState = hero.spriteAnimation.stand
if not love.keyboard.isDown("left") and not love.keyboard.isDown("right") then
if (hero.state == "walkLeft" and hero.vx > -5) or (hero.state == "walkRight" and hero.vx < 5) then
hero.vx = 0
end
if hero.vx < 0 then
hero.vx = hero.vx + 1000*dt
end
if hero.vx > 0 then
hero.vx = hero.vx - 1000*dt
end
end
if hero.vx < 0 then
leftPixelX = hero.x - 2
leftPixelY = hero.y
for y = leftPixelY + 5, leftPixelY + tile.height - 2 do
mapX = math.ceil(leftPixelX / tile.width)
mapY = math.ceil(y / tile.height)
leftTile = map[mapY][mapX]
if leftTile == 1 then
hero.vx = 0
end
end
end
if hero.vx > 0 then
rightPixelX = hero.x + tile.width + 2
rightPixelY = hero.y
for y = rightPixelY + 5, rightPixelY + tile.height - 2 do
mapX = math.floor(rightPixelX / tile.width)
mapY = math.ceil(y / tile.height)
rightTile = map[mapY][mapX]
if rightTile == 1 then
hero.vx = 0
end
end
end
hero.x = hero.x + hero.vx*dt
if hero.vy < 0 then
topPixelX = hero.x
topPixelY = hero.y + 4
for x = topPixelX -1, topPixelX + 1 do
zmapX = math.ceil(x / tile.width)
mapY = math.ceil(topPixelY / tile.height)
topTile = map[mapY][zmapX]
if topTile == 1 then
hero.vy = 0
end
end
end
bottomPixelX = hero.x
bottomPixelY = hero.y + tile.height + 1
touchGround = false
for x = bottomPixelX - 1, bottomPixelX + 1 do
bmapX = math.ceil(x / tile.width)
mapY = math.ceil(bottomPixelY / tile.height)
bottomTile = map[mapY][bmapX]
if bottomTile == 1 then
touchGround = true
end
end
if touchGround then
hero.onGround = true
hero.vy = 0
hero.y = (mapY - 2) * 32
else
hero.onGround = false
hero.vy = hero.vy + (1000 * dt)
end
hero.y = hero.y + (hero.vy * dt)
if love.keyboard.isDown("left") then
hero.state = "walkLeft"
hero.spriteState = hero.spriteAnimation.walk
hero.scale = -base.scale
if hero.vx < -hero.speed then
hero.vx = -hero.speed
else
hero.vx = hero.vx - 800*dt
end
elseif love.keyboard.isDown("right") then
hero.state = "walkRight"
hero.spriteState = hero.spriteAnimation.walk
hero.scale = base.scale
if hero.vx > hero.speed then
hero.vx = hero.speed
else
hero.vx = hero.vx + 800*dt
end
end
if love.keyboard.isDown("up") then
if hero.onGround then
hero.y = hero.y - 1
hero.vy = hero.vy - 400
hero.onGround = false
end
end
hero.spriteState:update(dt)
end
The first issue you can notice with Vsync on (60 FPS) is that the player sinks a bit into the ground for one frame before sticking properly. I tried to correct it by checking more pixels downwards, but it looks like the 1-frame sink is happening when the sticking code runs -- hero.y = (mapY - 2) * 32 -- without it it's ok, but if I only zero the velocity, the place the player stays on is randomly +/- a few pixels up or down, which is even worse.
The second issue is that with Vsync on you can actually jump inside a wall if you take a running jump and, if you keep up pressed, you'll slide out of bounds. I assume it's because left/right collision detection is also a bit wonky, but since I only zero the velocity, it does not autocorrect the player and hence it's getting glued to the top of the tile underneath the one he's sinking into.
If anyone could point me somewhere with that I'd be grateful.