Economical Snake Tail Logic

Started by
4 comments, last by frob 7 years, 1 month ago

Hi GameDevs,

I'm writing my own revamped version of snake with SFML and C++.

I'm not using an array board but instead store positions with vector<float> because the snake can move in 8 directions and the movement needs to be smooth.

The problem area for my tail logic is as follows:

Note, there's a function that lengthens the vector as it eats


void MakeTail()
{
    //sets initial value for tail vector equal to current snake pos(x,y)
    tail_x.at(0) = snake.x;
    tail_y.at(0) = snake.y;


    //makes next vector pos equal to the previous
    //note, this vector is filled backwards
    for (int i = n_tails; i > 0; i--) 
    //n_tails = tail segments * 6, explained after code
    {
        tail_x.at(i) = tail_x.at(i-1);
        tail_y.at(i) = tail_y.at(i-1);
    }
}

My problem:

1. I want the tail segments drawn to be ~6 pixels apart, so I draw every 6th position - which means I must add 6 vector positions for each tail segment

2. The game gets incredibly slow past 30+ tail segments due to the amount of math needed per frame

I'm wondering if anyone has any ideas on a different, more economical way to store tail positions as I've hit a brick wall trying to solve this issue.

Thanks,

AdosH

Advertisement
While shifting the tail positions inside the vector IS inefficient, I would be very surprised if it's actually causing a performance hit on its own. I would expect perf issues at a hundred thousand (maybe).

Is your compiler optimizing the build? Have you tried using a profiler yet?


1. I want the tail segments drawn to be ~6 pixels apart, so I draw every 6th position - which means I must add 6 vector positions for each tail segment


If one tail segment can be drawn from one position (this is typical in snake games), you only need to store one position per segment. Then you draw the segment based on that position. You don't need to put every pixel in the vector.



As far as the intent of your original question goes, you want to make a 'circular buffer'.

The basic idea is:

- Keep an array, but don't shift the elements around.
- The array should start out with the maximum possible length that the snake could ever get. You don't really even need a vector, but you can use one if you want.
- Keep a "head" and "tail" index into the array.

- Each time the snake moves:
-- Increment head and tail indices. When they go past the end of the array, set them to 0.
-- Replace element[head] with the new head position.
-- Don't do anything to element[tail-1] unless you are using some sort of retained-mode rendering scheme where you have to manually remove things to stop rendering them.

- The snake consists of element[head] through element[tail] (wrapping around if necessary). All other elements of the vector are unused until the snake grows enough.
- You will only ever overwrite one element of the array per move - placing the head.

Thank you Nypyren for your quick response,

I'm running the program with Code::Blocks in debug mode and it appears that the process is limited to 20% CPU, with some 'back of the envelope' math:


(30 segments * 6 vector positions) * (2 vectors) * 60 FPS = 21600 calcs/sec

It makes sense that the program hits that performance issue.

I think your idea can greatly reduce the amount of calculations needed, so I will try rewrite the tail logic that way.

Thanks for the* pointer!

Have you tried using a profiler yet?

QFE

First thing you do when hitting a performance problem is measure.

First check where the problem actually is, before trying to fix it, if only to avoid code that doesn't need fixing.

EDIT: 21K calculations isn't much in a second

Have you tried using a profiler yet?

QFE

First thing you do when hitting a performance problem is measure.

First check where the problem actually is, before trying to fix it, if only to avoid code that doesn't need fixing.

EDIT: 21K calculations isn't much in a second

Hi Alberth thanks for your reply,

I downloaded a Windows C++ profiler called 'Very Sleepy CS' and ran a test though I'm not sure how to interpret profiler data so I can't give you any input on that right now. I'll need to read up a tutorial on it later

I think its noteworthy to mention, it turns out the program defaults to using integrated graphics, and I have no problems when running it with my graphics card. Though this doesn't excuse inefficient programming

But yes I do agree with you QFE is the best way to go as a beginner for now, perhaps spending more time on finishing a presentable game and polishing the performance later is better

Consider that a CPU can do on the order of 12 billion floating point operations per second:

12 000 000 000 Number it can do per second

21 000 Number you claim it is struggling with per second

It is far more likely you're making some big errors. Maybe you are calling it thousands of times more than you think you are calling it. Maybe you are spending time doing other things you don't realize, like allocations or other memory management through not reserving buffers or creating/destroying things.

Among the first thing to look for in a profiler are the number of calls to functions in the tree. If you expect to see 50 calls but see 20,000 calls, that's a good sign there is an issue with unexpected calls.

Another of the first things to look for are the time spent within a specific function. If you find that you're spending a ton of time waiting for memory allocation or similar then it suggests other unexpected or hidden work.

This topic is closed to new replies.

Advertisement