Problem with movement of snake in planned snake game

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
mynameisnotpaul
Prole
Posts: 10
Joined: Sun Jan 01, 2017 4:25 am

Re: Problem with movement of snake in planned snake game

Post by mynameisnotpaul »

s-ol wrote:
mynameisnotpaul wrote:
s-ol wrote: This is also what I would recommend, except you describe it in a bit complicated of a way - you are just talking about substraction. I usually do it like this:

Code: Select all

local time = 0

function love.update (dt)
  time = time + dt
  if time >= time_to_do_stuff then
    -- do stuff
    time = time - time_to_do_stuff
  end
end
I tried implementing this code but it just results in my snake thing not moving at all. I changed the code a bit from the OP but I still don't know why it doesn't work. This is the code for reference:

Code: Select all

...
function love.update (dt)
  time = time + dt
  ...
  if time > snakeHead.speed then
    time = 0
  end
end

You didn't implement what I mentioned in my comment by the way. It's not a big deal for a snake game, but it can already be a noticeable effect and make the game easier to play for some players than for others, depending on hardware.

For example, imagine someone is playing at a constant 60 fps. That means dt is 1/60, and so after 6 frames "time" is at 0.1, but nothing happens yet, and then at 7 frames you reset the timer to 0 and do something.
So for one, your time is actually inaccurate, with a 'speed' of 0.1 and 60fps it takes not 6/60 = 0.10 but 7/60 = 0.1166s per loop.

That's already a lot of drift, but it's not that bad since if you designed the game and tested it, it doesn't really matter if when you mean 0.1 you actuall get 0.116 seconds, as long as it's consistent. But it's not:

Let's do the same example for someone who plays at 50fps:
1/50 * 5 = 0.1
but because you used a > instead of a >= it will loop only at 6/50 again, which is 1.2s, again different.

If you change it and add the >= these two examples will behave the same, but for framerates that don't evenly multiply up to your time of 0.1, the "time = 0" approach is going to be inaccurate anyway, and if dt is changing all the time aswell. For example with 45fps:

4/45 = 0.088 (< 0.1)
5/45 = 0.111 (< 0.1)

now if you subtract 0.1 from the "time" variable, the next iteration will have exactly 0.1 seconds to run, but with the time = 0, you are forgetting the fact that 0.011 seconds have passed already.

All these rather small numbers don't look like much of a difference, but your game is already harder at some speeds than on others, and if you write code like this it probably will be extremely hard to build something multiplayer.
Ohhh okay. I was wondering why you had the subtraction thing, thanks.
zorg wrote:
mynameisnotpaul wrote:
zorg wrote:Note that this will result in the snake's position having non-integer coordinates most of the time, but that can be solved with drawing it out floored in love.draw. (You can also floor it to a "bigger" grid, like, every 20 pixels or so: math.floor(n/20)*20 for example)
Hi, thanks for helping I fixed everything else you said but could you explain what you mean by the flooring part with examples of my code? It would probably make me understand much better.
A very brief example:

Code: Select all

local square = {} -- Our object
local grid = 10 -- pixels
local speed = 10 -- pixels/second

function love.load()
  -- Let's define it to start in the window's top-left corner with a width and height of 10 pixels.
  square.x = 0
  square.y = 0
  square.w = 10
  square.h = 10
end

function love.update(dt)
  -- Let's just have the square move downwards constantly
  -- This will make the y coordinate part have a fractional part, and is basically the "real" position of the object.
  square.y = square.y + speed * dt -- dt makes it framerate independent, so it will always go with a speed of 10 px/s
  
  -- Also let's wrap it around, if we want to treat the game area as a torus...
  if square.y >= love.graphics.getHeight() then
    square.y = square.y - love.graphics.getHeight() -- or just make it = 0, but this is more "correct".
  end
end

function love.draw()
  -- Here comes the trickery, we only want to draw the square on the grid in a way, that it snaps to the grid's size that we defined.
  local x,y = math.floor(square.x/grid)*grid, math.floor(square.y/grid)*grid -- We only modify y above, but this is useable for both axes.
  love.graphics.rectangle('fill',x,y,square.w,square.h)
end
Edit: See s-ol's post below for what i mean the last meaningful line in love.update being "more correct".
I think I mainly understand now, but I'm confused as to how math.floor works here. If it rounds the decimal to the closest lower integer, wouldn't it keep lowering it to 0? Because if math.floor() rounds down, and square.y = square.y + 10*0.0001 would make square.y = 0.001, and when that is /10 and floored, wouldn't that just go back to 0?

Still, thanks a lot for helping :) !
User avatar
zorg
Party member
Posts: 3465
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: Problem with movement of snake in planned snake game

Post by zorg »

It would, if the code in love.draw would actually overwrite square.y, which it doesn't. square.y will eventually be greater than the value of grid (10), so floor(11/10)*10 will be equal to 10 then, then soon 20, etc.

Think of whatever i do in love.draw as just the visual part, that's totally separate from whatever it calculated in love.update.
Me and my stuff :3True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.
User avatar
s-ol
Party member
Posts: 1077
Joined: Mon Sep 15, 2014 7:41 pm
Location: Cologne, Germany
Contact:

Re: Problem with movement of snake in planned snake game

Post by s-ol »

zorg wrote: Think of whatever i do in love.draw as just the visual part, that's totally separate from whatever it calculated in love.update.
I wouldn't put it this way because that's a common misconception I see lots of people on r/love2d have: love does not have a seperate "drawing system" and a "data system" or something like that, it will never remember any drawing data for you or draw something you didn't explicitly draw that frame.

Your intuition is right @mynameisnotpaul, if you floor your coordinates continuously, you're going to lose distance and accuracy or even stop moving completely. The reason the flooring doesn't cause this problem is simple though, take a look at the code again:

Code: Select all

function love.update(dt)
  -- Let's just have the square move downwards constantly
  -- This will make the y coordinate part have a fractional part, and is basically the "real" position of the object.
  square.y = square.y + speed * dt -- dt makes it framerate independent, so it will always go with a speed of 10 px/s
  
  -- Also let's wrap it around, if we want to treat the game area as a torus...
  if square.y >= love.graphics.getHeight() then
    square.y = square.y - love.graphics.getHeight() -- or just make it = 0, but this is more "correct".
  end
end

function love.draw()
  -- Here comes the trickery, we only want to draw the square on the grid in a way, that it snaps to the grid's size that we defined.
  local x,y = math.floor(square.x/grid)*grid, math.floor(square.y/grid)*grid -- We only modify y above, but this is useable for both axes.
  love.graphics.rectangle('fill',x,y,square.w,square.h)
end
"math.floor" is only called in draw, and the results are stored in a local variable. the original square.x/square.y are never touched, and so the "high precision" values are still there for the next frame to improve on. To experience the negative effects you would have to be doing something like this:

Code: Select all

  square.x, square.y = math.floor(square.x/grid)*grid, math.floor(square.y/grid)*grid
  love.graphics.rectangle('fill',square.x,square.y,square.w,square.h)

s-ol.nu /blog  -  p.s-ol.be /st8.lua  -  g.s-ol.be /gtglg /curcur

Code: Select all

print( type(love) )
if false then
  baby:hurt(me)
end
User avatar
zorg
Party member
Posts: 3465
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: Problem with movement of snake in planned snake game

Post by zorg »

You're right, it can be mis-interpreted; i only meant that my code separated the two parts of the code, since löve does give you two callback functions (love.update and love.draw), which are plain functions that you define, and löve calls for you, hence the name.

That said, you don't need to do that either, yay freedom. :3

(Also, just to nitpick, it can also be made to work the way you said, though with canvases.)
Me and my stuff :3True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.
mynameisnotpaul
Prole
Posts: 10
Joined: Sun Jan 01, 2017 4:25 am

Re: Problem with movement of snake in planned snake game

Post by mynameisnotpaul »

Oh, okay, I understand a lot better now.

One more question though. Isn't dt the time in between 2 frames? I know timer.getDelta does that and dt seems to stand for delta but I'm not sure.
User avatar
pgimeno
Party member
Posts: 3656
Joined: Sun Oct 18, 2015 2:58 pm

Re: Problem with movement of snake in planned snake game

Post by pgimeno »

Yes. dt stands for delta time or, if you're math- and physics-oriented, differential of time.
mynameisnotpaul
Prole
Posts: 10
Joined: Sun Jan 01, 2017 4:25 am

Re: Problem with movement of snake in planned snake game

Post by mynameisnotpaul »

Oh right. Yeah we did delta stuff in science class, no idea how I forgot about that.
Post Reply

Who is online

Users browsing this forum: Google [Bot] and 10 guests