Hi, I've been searching for a good way to implement smooth tile based movement to my Snake game. For now I'm using something along those lines:
It seems like this method is not very efficient and the movement might be jerky, at least for me.
It would be great if someone could provide some examples of good way to implement it.
I've tried your game on my PC and printing out the f values (defined on line 11) during the game loop it becomes apparent that the value of f is set abruptly to 1.00 ever so often.
I would not use Lerp for calculating the position as this would interpolate between two fixed points (and void any 'surplus' movement that definitively appears).
I'll try to explain by example. If your rectangle is moving by steps of 0.3 (f=0.3) then you are getting this:
step1: 0.0
step2: 0.3
step3: 0.6
step4: 0.9
step5: 1.0 (when in fact the rectangle should be moved beyond the Lerp range by 0.2).
So in fact by setting the f to 1 (in cases when it should be larger than this) you are instructing the rectangle to sort of pause at certain points and this is what breaks the smooth movement.
I hope this helps. If I was to program this movement I would simply multiply the direction by time elapsed, add this to the actual position and do a kind of line sweep between the old and the new position to determine which tiles I have moved over (for cases where the snake would cross multiple tiles in a single game loop step) so these tiles could be added to the snake's tail.
I'm not sure if I understand your idea correctly but wouldn't movement not be aligned to the grid then? By interpolating between two fixed points I'm sure that it will be aligned properly.
I see your point. The possible solution to this that I can see, while keeping the Lerp, is to keep any eventual value, of 'f' variable, above the 1.0 in a new variable and include this new variable in the next f value calculation that will occur (at next step of game loop)?
Possibly something like this:
float f = old_f_value+snake->animationTime * snakeSpeed; if (f >= 1.0f) { snake->animationTime = 0.0f; old_f_value=f-1.0f; f = 1.0f; pressSignal = false; } else { old_f_value=0.0f; }
p.s. Sorry, I have not tested this yet so no guarantee it works.
So I tried your method and now movement is actually more choppier than before.
Sorry to hear that. Bump from my end then..
Think about what you're actually doing. Clipping 'f' is essentially setting a maximum speed.
And then interpolating between the current tile and the next is effectively limiting the speed to one tile per update.
You should be able to animate anything with any time delta, simply based on the snake speed.
Drop the lerp, and drop the 1 tile per frame limit and set it to the snake speed.
Then adjust your collision detection to use a sweep check as advised above. This is easy because you're sweeping an AABB along an axis, so it remains an AABB. And it's only in one direction so calculating the intercept time with an object rectangle like the snake's food or body segments is easy.
Just a note, in the original snake game, it starts off very slow and gradually increases speed. As well, the snake gains a body segment for every fruit or two or three that it eats. So the game is still slightly incomplete at this point.
This problem got me interested enough to write the movement function myself. It works OK though it is probably not the most elegant way of making this work. If you with to try it, the function is:
That's a lot of code to do such a simple thing....
Could you tell me what should I search to get more information about this method? It would be nice to understand your code
Sure, let me demystify it a little bit.
Sweep just means extend the shape in its forward movement direction. Search for sweep test / sweep check , sweep collision check , etc...
This is slightly psuedocode but you should get the idea. Make an enum to store the direction, and some const data on the direction of movement for each direction by xy values and then make a rectangle struct, and fill in a couple functions like Sweep, Overlap(Edge&), Overlap(Rectangle&) and so on.
After a while I came up with fairly simple method of calculating time which is needed to travel to the closest grid-aligned point. It's being triggered when direction has changed. Everything is nicely aligned and the movement is smooth (ofc Logic is to be fixed) but I don't know what I should do when getting close to the edges. I tried aligning to the edges - CELL_SIZE independently from pressSignal but it's hard to change direction when you've got only one frame for this Any ideas?
Your logic looks sound.
If the intercept time to the edge is less than the update delta, then it has collided. You know it will only hit the edge if it is traveling towards it, and you know how far away it is, so t = dz/speed .
Try making a turn just before the edge. This is what I don't know how to handle. Aligning to the edge - CELL_SIZE doesn't seem to help at all probably because it happens only in one tick before colliding. Sorry for misunderstending, I feel like I didn't explain it well. And by the way, is cpu usage high after launching it on your computer too?
The solution should be simple. Make your movement check before your update.
I'm really close to solving my problem. However, I discovered one bug and I don't know how to solve it. Here is a short example so you can test it yourself. For example, try going down and press (almost at once) A/S and then W. Square goes directly upwards which is bad for my snake logic. How can I prevent this behaviour?
The solution is easy. Make it travel at least one square before allowing it to change direction, and if it tries to 'eat itself' then kill it, that's a bad move.
Here's an example to check for if the snake is eating itself (tries to move the opposite direction it is going) :
Okay, fixed it. Thank you for your help and patience