Quick answer: After AddSplinePoint, call UpdateSpline. For explicit direction control, use SetTangentAtSplinePoint with the vector pointing in the desired forward direction. For SplineMeshComponents, pass GetLeaveTangentAtSplinePoint of the start and GetArriveTangentAtSplinePoint of the end.
Here is how to fix Unreal SplineComponent tangent wrong direction. You generate a road by adding spline points, deforming SplineMeshComponents between them. The first segment looks correct. The next one is flipped, the mesh reversed like the road points backward. Or the arrow gizmos in the editor show points facing opposite directions. The issue is almost always that tangents were never explicitly set, and Unreal’s auto-tangent code picks the wrong sign in some configurations.
The Symptom
A procedural spline mesh chain shows reversed segments. Individual spline points in the editor have arrow widgets pointing backward. Calling GetTangentAtSplinePoint returns a vector pointing opposite to what you expect. Characters using GetLocationAtDistanceAlongSpline travel correctly but GetRotationAtDistanceAlongSpline points them backward.
Variant: one-time construction is fine but re-edits in the editor flip tangents; or spline works in the editor but inverts in packaged builds.
What Causes This
No tangent set on added points. AddSplinePoint adds a point and Unreal computes a default tangent from neighbors. A brand-new point at the end of the spline has no “next,” so the tangent defaults to (0,0,0) or extrapolates weirdly.
UpdateSpline not called. After multiple AddSplinePoint calls, the spline is in an intermediate state. You must call UpdateSpline() to recompute tangents and cache derived data. Without this call, tangent queries return stale values.
Separate arrive/leave tangents. Each spline point has an Arrive tangent (entering) and Leave tangent (leaving). For smooth curves they are identical, but SetTangentAtSplinePoint only sets one side by default. Setting only arrive leaves leave as zero.
World vs local space confusion. AddSplinePoint(Pos, ESplineCoordinateSpace::World) expects world-space. Passing a local-space vector mirrors or shears the spline relative to the actor transform.
Negative scale on the actor. An actor with scale.X = -1 flips spline orientation. Tangents sign-flip. This shows up as “half my splines are reversed after I mirrored the level.”
The Fix
Step 1: Always call UpdateSpline after bulk edits.
Spline->ClearSplinePoints();
for (const FVector& P : Waypoints)
{
Spline->AddSplinePoint(P, ESplineCoordinateSpace::World, false);
}
Spline->UpdateSpline();
Passing bUpdateSpline = false to each AddSplinePoint call delays the expensive update until the end. Skip this trick and you still need a terminal UpdateSpline.
Step 2: Set explicit tangents where you care. The default heuristic works fine for smooth curves but struggles at endpoints and corners. Explicit tangents make intent clear:
for (int32 i = 0; i < Waypoints.Num(); ++i)
{
// Tangent points along the direction to next waypoint
FVector Tangent;
if (i < Waypoints.Num() - 1)
{
Tangent = Waypoints[i + 1] - Waypoints[i];
}
else
{
Tangent = Waypoints[i] - Waypoints[i - 1];
}
Spline->SetTangentAtSplinePoint(i, Tangent,
ESplineCoordinateSpace::World, false);
}
Spline->UpdateSpline();
SetTangentAtSplinePoint sets both arrive and leave to the same value. For mismatched corners use SetTangentsAtSplinePoint(Index, ArriveTangent, LeaveTangent, ...).
Step 3: For SplineMeshComponents, use the right endpoint tangents.
for (int32 i = 0; i < Spline->GetNumberOfSplinePoints() - 1; ++i)
{
USplineMeshComponent* SegMesh = NewObject<USplineMeshComponent>(this);
SegMesh->SetMobility(EComponentMobility::Movable);
SegMesh->SetStaticMesh(RoadSegmentMesh);
SegMesh->AttachToComponent(Spline, FAttachmentTransformRules::KeepRelativeTransform);
FVector StartPos = Spline->GetLocationAtSplinePoint(i, ESplineCoordinateSpace::Local);
FVector StartTan = Spline->GetLeaveTangentAtSplinePoint(i, ESplineCoordinateSpace::Local);
FVector EndPos = Spline->GetLocationAtSplinePoint(i + 1, ESplineCoordinateSpace::Local);
FVector EndTan = Spline->GetArriveTangentAtSplinePoint(i + 1, ESplineCoordinateSpace::Local);
SegMesh->SetStartAndEnd(StartPos, StartTan, EndPos, EndTan, true);
SegMesh->RegisterComponent();
}
The critical detail: Leave tangent at start, Arrive tangent at end. Using Arrive at start reverses the mesh. Using Leave at end stretches the mesh past the point.
Step 4: Verify by visualizing. Enable ShowFlag.Splines in the editor viewport. Spline points draw arrows indicating tangent direction. If an arrow points backward, that point’s tangent sign is wrong.
Arrive vs Leave: When They Differ
For smooth curves, Arrive and Leave are identical. For a kink (abrupt direction change), they differ:
// Sharp 90-degree corner
Spline->SetTangentsAtSplinePoint(
CornerIdx,
FVector::ForwardVector * 100, // Arrive from ahead
FVector::RightVector * 100, // Leave to the right
ESplineCoordinateSpace::World,
true
);
This produces a visible corner instead of a smooth blend.
Rotations Along Spline
For characters moving along splines that face the travel direction:
FVector Loc = Spline->GetLocationAtDistanceAlongSpline(
Distance, ESplineCoordinateSpace::World);
FRotator Rot = Spline->GetRotationAtDistanceAlongSpline(
Distance, ESplineCoordinateSpace::World);
Character->SetActorLocationAndRotation(Loc, Rot);
If the character faces backward, tangents are reversed at that distance range. Fix the underlying tangent rather than rotating the character 180 degrees — the latter breaks at the next segment.
“Leave at the start of a segment, Arrive at the end. Get that reversed and the mesh reverses too.”
Related Issues
For actor construction order issues, see Actor Replication Not Working. For procedural content more broadly, Texture Streaming Pool Exceeded covers related generation-heavy workflows.
UpdateSpline, explicit tangents, Leave at start and Arrive at end. Segments point forward.