There's a great question here that helps me a little bit in what I want to do, and it explains it quite well:
How To Approach 360 Degree Snake
Basically, I want to have a smooth 360-degrees Snake game, where the snake follows along. I can easily do the same thing as described in the question above (video example), but as you can see that kind of "drags" or "slides" the snake along, which is not what I'm looking for.
I want the entire snake to follow the entire path that the head of the snake follows.
The only solution I can think of is to make some kind of "buffer" to store the rotations of the head, and then "lag" that behind on the other bodies.
Since I think that will end up way too complex, what's the correct way of implementing it?
Here's a screenshot of how my snake looks like right now, to get an idea of what exactly I want to move:
Answer
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
#include
#include
#include
#include
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 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.
No comments:
Post a Comment