I'm making a skating game and I'm having trouble figuring out how to snap the player onto grinds and rails in between multiple points. My plan was to snap the player onto a point in between the two points using lerp, check which direction the player is facing using the dot product to compare direction of the rail from its first point to second and its second point to first and move them in direction that they are closer to.

But I'm having trouble with the first step. If the rail is more than two points how do I determine which two points do I use for lerping?


4 Answers 4


You must find the grind segment that's closest to the player.

The first thing you need is a function to find the closest point on a segment as an interpolation value of 2 points we'll call i.

// return interpolant value "between" segment_point1 (0.0f) and segment_point2 (1.0f) 
// values go outside this range when past the end of a segment
float ClosestPointOnLine(Vector3 player, Vector3 segment_point1, Vector3 segment_point2)
  Vector3 segment_vector = (segment_point2 - segment_point1);

  float length_squared = DotProduct(segment_vector, segment_vector);

  return DotProduct(player - segment_point1, segment_vector) / length_squared;

Note: You may want to check if length_squared is zero but we're assuming your level does not have any zero-length segments.

If the value returned is < 0 the player is before segment_point1, if > 1 then the player is past segment_point2.

You can then find the exact point on the segment by doing Lerp(segment_point1, segment_point2, i); where i is the returned value of the above function (in red on the image).

But what we really want is the point on the segment clamped between 0 and 1 (in yellow), this is why the above function returns an interpolation value and not an actual point.

float i = ClosestPointOnLine(player, segment[s].first, segment[s].second);
Vector3 point_on_segment = Lerp(segment_point1, segment_point2, Clamp(i, 0, 1));

What you then do is calculate the distance between the point on the segment and the player for every segment (grinding edge) in the area and find the closest one.

Then if the closest distance is less than a grind distance threshold, you can snap to that edge.

enter image description here

In this image, we'd snap to the B segment. (In gray is the extended segment A to show how i is outside of 0..1 for that segment before clamping)

That's it.

Additionally for movement on the rails:

  • Use i to move the player along that segment rather than in 3D.
  • Use the segment length to adjust the speed by dividing it by the segment length (i is relative to the segment, not world space).
  • Use the DotProduct of the segment direction (segment.second-segment.first) and the player's movement direction to figure out in which direction and speed to move i on the segment.
  • When i moves out of the (0..1) range switch to the previous <0 or next >1 grind segment
  • snap off and back in 3D if it's the last one (either ends) in the chain

Outside of this question: You need to build grind segment chains to know which segment comes next when grinding and use some way to limit the search for grind segment chains such as an octree or other method to avoid going over the >10000 segments in the entire level.


Make all of the points of any grindable rail part of their own array.

Use collision detection to see if a player is touching the rail (initiating grind).

Retreive the array and loop through each of its points to find the 2 points closest to the players point of contact, a simple distance check should suffice (keep in mind that circular grinds could create problems, depending on the distance between each point).

Use the characters speed and direction to do the rest, the path can be determined by the points in the array.

Hope it helped!


you can check the distances between the points in the rail compared to your skater, then whichever is greater than playerVector but closest is the next point, and whatever is lesser than it but least distance is the previous point


You will want to test if the player collides with the rail; and if so, snap the player onto the rail.

2D Illustration

2d snapping process

Here, the blue line represents the rail; the red circle represents the original position of the player (the circle could be any shape); the maroon circle represents the position of the player after being snapped to the rail; and the purple line represents the direction of translation/movement. This line is perpendicular to the rail, so the player is snapped to the closest point possible. As the illustration demonstrates, the player must move to the center of the rail in one direction, while staying centered on the line of translation. This could be calculated using the following pseudocode:

// form equation of line of the rail
float rail_slope = rail_dir.y / rail_dir.x;
// y - y1 = m*(x - x1) => y = m*x + (m*-x1 + y1)
//                        y = m*x +       b
float rail_y_int = rail_slope * -a_point_on_rail.x + a_point_on_rail.y;  // y-intercept, or |b|

// form equation of line of translation
float translation_slope = - 1 / rail_slope;   // a perpendicular line's slope is the opposite reciprocal
// the player will always be on the line of translation
float translation_y_int = translation_slope * -player_pos.x + player_pos.y;

// now find the intersection between the two lines, which is the new location of the player
// f(x) = m1*x + b1; g(x) = m1*x + b1;
// intersection: m1*x + b1 = m2*x + b2 => m1*x - m2*x = b2 - b1
// => x (m1 - m2) = b2 - b1 => x = (b2 - b1) / (m1 - m2);
// y = m1*x
float snapped_x = (translation_y_int - rail_y_int) / (rail_slope - translation_slope);
float snapped_y = rail_slope * snapped_x;
player_pos = new Vector2(snapped_x, snapped_y);

3D Illustration

The player before being snapped to the rail: 3d snapping process: stage 1

The player after being snapped to the rail, with the plane of translation: 3d snapping process: stage 2

As the illustration demonstrates, the player must move to the center of the rail, while staying on the plane of translation. I don't know 3D geometry as of now, so I cannot provide the pseudocode for 3D.


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .