One method is to have the head define a trail as it moves and position all other nodes at positions along this trail.
In this method you need to define the position of the body parts as a constant distance along the snake from the head.
So on each frame you want to;
- update the position of the head.
- add the current position of the head to a list of previous positions it occupied
- for each node of the body
- get/calculate the distance the body should be away from the head
- traverse the trail by that distance
- set the body position to this position on the trail
The following is a C++03 program which shows how this can be achieved. It isn't perfect, but it demonstrates the idea;
#include <deque>
#include <string>
#include <sstream>
#include <iostream>
#include <math.h>
struct Vector3
{
Vector3(double x = 0, double y = 0, double z = 0) :
x(x),
y(y),
z(z)
{
}
double x;
double y;
double z;
Vector3 operator +(const Vector3 v) const
{
return Vector3
(
x + v.x,
y + v.y,
z + v.z
);
}
Vector3 operator -(const Vector3 v) const
{
return Vector3
(
x - v.x,
y - v.y,
z - v.z
);
}
Vector3 operator *(double scalar)
{
return Vector3
(
x * scalar,
y * scalar,
z * scalar
);
}
double magnitude()
{
return sqrt(x*x + y*y + z*z);
}
void normalise()
{
double mag = magnitude();
if (mag != 0.0)
{
x /= mag;
y /= mag;
z /= mag;
}
}
std::string toString()
{
std::stringstream ss;
ss << "(" << x << ", " << y << ", " << z << ")";
return ss.str();
}
};
class Trail
{
public:
struct Point
{
Vector3 position;
double distanceToNext;
};
Trail() :
m_maxPoints(0),
m_points()
{}
size_t maxPoints() const
{
return m_maxPoints;
}
void maxPoints(size_t size)
{
m_maxPoints = size;
if (m_points.size() >= m_maxPoints && m_maxPoints != 0)
{
m_points.resize(m_maxPoints, Point());
}
}
void addPoint(Vector3 vec)
{
Point p;
p.position = vec;
if (!m_points.empty())
{
p.distanceToNext = (vec - m_points.front().position).magnitude();
}
else
{
p.distanceToNext = 0.0;
}
addPoint(p);
}
void addPoint(Point point)
{
if (m_points.size() >= m_maxPoints && m_maxPoints != 0)
{
m_points.resize(m_maxPoints - 1, Point());
}
m_points.push_front(point);
}
Vector3 positionFromHead(double distance) const
{
for (size_t i = 0; i < m_points.size() - 1; ++i)
{
const Point& p = m_points[i];
if (distance > p.distanceToNext)
{
distance -= p.distanceToNext;
}
else
{
// Lerp between last and current points.
Vector3 offset = m_points[i + 1].position - p.position;
offset.normalise();
offset = offset * distance;
return p.position + offset;
}
}
if (!m_points.empty()) // is last point?
{
return m_points.back().position;
}
return Vector3(); // list is empty
}
private:
size_t m_maxPoints;
std::deque<Point> m_points;
};
int main(int argc, char* argv[])
{
Trail trail;
trail.addPoint(Vector3(50, 0, 0));
trail.addPoint(Vector3(10, 0, 0));
trail.addPoint(Vector3(0, 0, 0));
Vector3 posA = trail.positionFromHead(5);
std::cout << " 5 units from head - Expecting (5, 0, 0) and got " << posA.toString() << "\n";
Vector3 posB = trail.positionFromHead(10);
std::cout << "10 units from head - Expecting (10, 0, 0) and got " << posB.toString() << "\n";
Vector3 posC = trail.positionFromHead(25);
std::cout << "25 units from head - Expecting (25, 0, 0) and got " << posC.toString() << "\n";
return 0;
}
Of particular importance is this method which actually works out the position based on the distance;
Vector3 positionFromHead(double distance) const
{
for (size_t i = 0; i < m_points.size() - 1; ++i)
{
const Point& p = m_points[i];
if (distance > p.distanceToNext)
{
distance -= p.distanceToNext;
}
else
{
// Lerp between last and current points.
Vector3 offset = m_points[i + 1].position - p.position;
offset.normalise();
offset = offset * distance;
return p.position + offset;
}
}
if (!m_points.empty()) // is last point?
{
return m_points.back().position;
}
return Vector3(); // list is empty
}
In this instance each position on the trail is stored with a distance to the next one but this could be calculated on the fly. It's calculated in the addPoint() methods.
Note: don't use that Vector3 class. It was created only for this example to keep the dependencies down.
If the distance requested is longer than the list you'll just be given the position of the end of the trail.
If this were to be used you would need to make sure that you set maxPoints to an appropriate number or use 0 if you want it to be unbounded. To be clear, the maxPoints doesn't need to correspond to the number of bodies in your snake, you just want it to store enough to make a trail equal to the length of the snake.
You may want to consider limiting the max length with a max total distance instead of a max number of points. Also, if you set it to unbounded (0) it will consume more memory every frame so only use that when experimenting.
In practice you might do something similar but not exactly the same thing.
for(i = 1;i<snake_elements_count;i++) snake_element_position[i] = snake_element_position[i-1];
wheresnake_element_position[0]
is the heads position. Run thefor
loop every time you change the heads position. – Raxvan Feb 05 '15 at 11:59snake_element_position
tosnake_element_direction
and you're almost done. you would have to store some key position every time the direction changes to avoid floating point errors. but for the first run this should do just fine. – Raxvan Feb 05 '15 at 12:06