Page 1 of 2

Collision issue with lower FPS

Posted: Fri Feb 14, 2014 1:22 pm
by aidenn
Hi,

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
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. :)

Re: Collision issue with lower FPS

Posted: Fri Feb 14, 2014 1:40 pm
by Azhukar
aidenn wrote: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.
You need to move your player right outside the tile once he collides with it.

Your tiles are all the same size, it should be easy to check for the closest free X and Y coordinates to move your player to upon collision.

I recommend you also structure your code, separate different functionality to different functions and/or files. It will make your project more manageable in the future.

Re: Collision issue with lower FPS

Posted: Fri Feb 14, 2014 8:17 pm
by aidenn
Azhukar wrote:You need to move your player right outside the tile once he collides with it.

Your tiles are all the same size, it should be easy to check for the closest free X and Y coordinates to move your player to upon collision.
Thanks. But what about that visible jerk while pushing the player outside?
Azhukar wrote:I recommend you also structure your code, separate different functionality to different functions and/or files. It will make your project more manageable in the future.
Yeah, I aim to do this as soon as I get collisions right. :)

Re: Collision issue with lower FPS

Posted: Fri Feb 14, 2014 8:19 pm
by Azhukar
aidenn wrote:But what about that visible jerk while pushing the player outside?
If you detect collision and in the same frame move the player to the right spot there will be no jerk. If you still get a jerk, post your code again.

Re: Collision issue with lower FPS

Posted: Fri Feb 14, 2014 8:37 pm
by aidenn
There is a jerk while moving the player outside the bottom tile (but only visible in 60 FPS, not in 500 FPS), it's in the code already.

Re: Collision issue with lower FPS

Posted: Fri Feb 14, 2014 8:56 pm
by Azhukar
aidenn wrote:There is a jerk while moving the player outside the bottom tile (but only visible in 60 FPS, not in 500 FPS), it's in the code already.
I can't seem to replicate it. Can you describe what you mean by jerk?

What I did manage to find:
Jumping unto the left level wall can cause you to climb it outside the level.
Sometimes you do not nullify your x velocity so your character keeps sliding.

Re: Collision issue with lower FPS

Posted: Sat Feb 15, 2014 12:41 pm
by aidenn
Yeah, I noticed that. As for the jerk, it doesn't happen all the time:

https://dl.dropboxusercontent.com/u/743494/mehmeh.mp4

Note that it also happens when jumping straight up, so it's not due to jumping diagonally in the video. ;)

As for other findings, yeah, I've seen them too, but as you said - it's due to not pushing the player outside the tile horizontally. However, it does not happen without Vsync in 500 FPS, so I didn't notice it before and the first thing I noticed after enabling Vsync was that vertical jerk when pushing the player outside the bottom tile. At first I thought it's just because pushing out happens a frame too late, but it also happens (the sinking) if I set the collison detection code to act earlier by searching for more pixels downwards, which is weird.

Re: Collision issue with lower FPS

Posted: Sat Feb 15, 2014 4:37 pm
by Azhukar
aidenn wrote:Yeah, I noticed that. As for the jerk, it doesn't happen all the time:

https://dl.dropboxusercontent.com/u/743494/mehmeh.mp4
It seems you correct the player position the frame after moving the player, or rather correct it partially over several frames. Correcting it fully in the same frame should resolve that issue.

Re: Collision issue with lower FPS

Posted: Sat Feb 15, 2014 6:07 pm
by aidenn
Yay, I fixed it by moving gravity code before collision code, but I don't understand why it didn't work when it was the other way around. Let's simplyfy it:

1. if a collidable tile is one pixel beneath the player - set velocity to 0, move player to exactly one pixel above said tile
2. player y-position = player y-position + (velocity * dt)

However, 2 shouldn't do anything if the velocity is 0, yet it sometimes does that jerk, which stops only if I do "2" first and "1" second. Why is that?

Re: Collision issue with lower FPS

Posted: Sat Feb 15, 2014 6:16 pm
by Azhukar
aidenn wrote:Yay, I fixed it by moving gravity code before collision code, but I don't understand why it didn't work when it was the other way around. Let's simplyfy it:

1. if a collidable tile is one pixel beneath the player - set velocity to 0, move player to exactly one pixel above said tile
2. player y-position = player y-position + (velocity * dt)

However, 2 shouldn't do anything if the velocity is 0, yet it sometimes does that jerk, which stops only if I do "2" first and "1" second. Why is that?
What it did previously:
Check for collision -> collision not detected
Move player according to velocity -> player ends up in a tile -> draw

What it does now:
Move player according to velocity -> player ends up in a tile
Check for collision -> collision detected, move player out of tile -> draw