0
\$\begingroup\$

I am working on a 2-D snake game in C++ with the SFML library. I would like to smooth out the movement of my snake as it looks extremely choppy currently.

How the Snake moves

The snake is made up of a list of parts. If I want to move the snake, I simply remove one part from the back (.pop_back()) and then add a new part in the front accrding to the velocity. This works perfectly. The speed of the snake must be the same as the distance between the parts. So if the distance is 15, the will be 15. But a speed of 15 is extremely high, so I need to only update the snake every few frames or use sf::Sleep(). Since the snake moves such a high distance every frame, the movement doesn't look smooth at all. And since the speed must be equal to the distance between the parts, reducing the speed doesn't work too. How can I come about tackling this?

\$\endgroup\$
7
  • \$\begingroup\$ What's different with this question and the one you posted before? What's the issue with what has been discussed in the chat following the question? Please avoid deleting a question and reposting it, use the edit feature instead. \$\endgroup\$
    – Vaillancourt
    Oct 17, 2020 at 16:12
  • \$\begingroup\$ @Vaillancourt I posted a new question because i felt my previous one wasn't clear. The movement is still choppy :( \$\endgroup\$
    – user144375
    Oct 17, 2020 at 16:15
  • 3
    \$\begingroup\$ Okay, well when there are no answers, it's generally better to edit the question instead of deleting and reposting. \$\endgroup\$
    – Vaillancourt
    Oct 17, 2020 at 16:17
  • 1
    \$\begingroup\$ In particular, deleting and reposting instead of editing can look like an attempt to spam and circumvent moderation from the perspective of the algorithms the site uses, and cause automated restrictions to apply to your account. Editing avoids any false alarms like this. \$\endgroup\$
    – DMGregory
    Oct 17, 2020 at 16:32
  • \$\begingroup\$ I was writing an answer and then the question was deleted, I assumed it was solved, and let the black hole take what I had written. I'm busy now, probably I'll answer later. \$\endgroup\$
    – Theraot
    Oct 17, 2020 at 17:05

2 Answers 2

0
\$\begingroup\$

This solution is for grid based games. The idea is to have "turns". A turn will happen regardless of whatever or not there is input or not. At the end of the turn the avatar will be aligned to the grid, and a turn always takes the same time.

Pseudo-code:

var current_time = get_current_time();
var elapsed_time = current_time - start_time;
update_input(ref input);
if (elapsed_time > turn_duration)
{
    elapsed_time -= turn_duration;
    start_time += turn_duration;
    for (var part_index = 0; part_index < tail.length - 1; part_index++)
    {
        var part = tail[part_index];
        var next_part = tail[part_index + 1];
        part.start_position = part.end_position;
        part.end_position = next_part.start_position;
    }

    var final_part = tail[tail.length - 1];
    final_part.start_position = final_part.end_position;
    final_part.end_position = head.start_position;
    head.start_position = head.end_position;
    head.end_position = compute_next_position(head.start_position, input);
}

head.current_position = interpolate(head.start_position, head.end_position, elapsed_time);
for (var part_index = 0; part_index < tail.length; part_index++)
{
    var part = tail[part_index];
    part.current_position = interpolate(part.start_position, part.end_position, elapsed_time);
}

Thus, for every part, and for the head, we are storing where it was at the start of the "turn", and where it will be at the end of the turn. We use the elapsed time since the start of the turn to compute the current position via interpolation.

When, the turn ends, we use the input and the last position of the head to compute the next position of the head.

You can buffer input if you like. You can treat no input as going in the same direction if you like.

The code above can be modded to interrupt the motion. That would require to have an end_time and check against that, instead of turn_duration. Then you can update the motion as soon as input comes, and still have the turn end at the same instant. However, I do not think that is appropriate for snake.

\$\endgroup\$
0
\$\begingroup\$

(I can copy this over to your original question if you'd like to undelete that one and delete/close this one instead)

I'd solve this by storing an array of the grid points the snake head has run through, and the next point it's on its way toward.

Then we'll store a progress variable between 0 and 1, that represents how far the snake has progressed from its old position to its new position.

We'll number our snake parts from 0 for the head up to snakeSize - 1 for the last tail segment. In our positionHistory array, 0 will be the point the head is moving toward now, 1 will be the point the head just left, and snakeSize will be the point the last tail segment just vacated.

We can update the state like so:

progress += deltaTime / secondsToCrossOneTile;

if (progress >= 1.0f) {
     // TODO: Handle activating a new tail segment here 
     //       when finishing the previous growth phase.

     // TODO: Handle eating a pellet here and beginning a new growth phase.

     for (int i = snakeSize; i > 0; --i) {
         positionHistory[i] = positionHistory[i - 1];
     }

     positionHistory[0] = PickNextPoint();

     progress -= 1.0f;
}

(You could also store your history as a circular buffer, and just shift the starting offset instead of copying all the data over one notch)

Now every frame we can run through each piece in the list, using this progress value as an interpolation weight between its old position and the new position.

for(int i = 0; i < snakeSize; i++) {
    snakePart[i].position = lerp(positionHistory[i+1], positionHistory[i], progress);
}

When you want to add a segment to the snake, you place it at the position of the last tail segment, and leave it stationary there until the next time we hit our progress >= 1.0f condition. Then you append it to the snakePart collection and extend the position history an extra notch.

\$\endgroup\$

You must log in to answer this question.