We’re setting up a “rail grinding” skateboard behavior which works as expected when the spline on which the player is “grinding” is level, but which returns anomalous values when the spline changes elevation, specifically when it crests a hill - ascending and then descending again.
Here’s the relevant snippet from our movement component’s PhysSkateboardGrinding():
What we’ve observed:
- Incoming InVelocity contains expected value
- SpeedAlongTangent contains reasonable & expected values
- DeltaDistance appears correct
- FutureDistanceAlongSpline seems to represent an expected new position
- Clamping doesn’t appear to be the problem as we can get stuck far from the spline’s end.
- NewPositionOnSpline, when the spline is changing elevation, seems to get “stuck” at certain points, returning roughly the same location the character already occupies even though FutureDistanceAlongSpline appears properly updated.
Are we doing something incorrect in the way we’re advancing along the spline?
`// Project the input velocity onto the spline tangent
const float SpeedAlongTangent{
static_cast(FVector::DotProduct(InVelocity, GrindData.CurrentGrindTangent))
};
GrindData.CurrentGrindSpeed = SpeedAlongTangent; // Write to the CurrentGrindSpeed member variable
// #TODO: TEST - The hypothesis to test here is whether instead of calculating delta by scaling the spline tangent
// on which we’re currently standing by the speed along the tangent, we can instead project ahead on the spline to
// find the next point we would be at based on our grind speed, and then calculate a delta to that point.
const float DeltaDistance{ GrindData.CurrentGrindSpeed * DeltaSeconds };
const float FutureDistanceAlongSpline{ GrindData.CurrentGrindPoint + DeltaDistance };
const float ClampedDistanceAlongSpline{ FMath::Clamp(FutureDistanceAlongSpline, 0.0f, GrindData.SplineLength) };
// #TODO: NewPositionOnSpline appears to be where we’re getting unexpected values when the spline crests a hill.
const FVector NewPositionOnSpline{
GrindData.GrindSpline->GetLocationAtDistanceAlongSpline(ClampedDistanceAlongSpline, ESplineCoordinateSpace::World)
};
constexpr float GrindClearanceBuffer{ 10.0f }; // Buffer to ensure character is above the rail
const float OffsetZ = CharacterOwner->GetCapsuleComponent()->GetScaledCapsuleHalfHeight() + GrindClearanceBuffer;
const FVector TargetLocation = NewPositionOnSpline + FVector(0, 0, OffsetZ);
#if ENABLE_DRAW_DEBUG
if (UHooliganSkateboardComponent::DrawSkateboardDebugInfo(this))
{
DrawDebugDirectionalArrow(World,
UpdatedComponent->GetComponentLocation(),
UpdatedComponent->GetComponentLocation() + InVelocity,
32.0f,
FColor::Blue,
false,
.1f);
const FString GrindSpeedText{
FString::Printf(TEXT(“CurrentGrindSpeed: %.2f, DeltaDistance: %.2f, FutureGrindPoint: %.2f”),
GrindData.CurrentGrindSpeed,
DeltaDistance,
FutureDistanceAlongSpline)
};
DrawDebugString(World,
UpdatedComponent->GetComponentLocation() + FVector(0, 0, 50.0f),
GrindSpeedText,
nullptr,
FColor::Emerald,
0.1f);
DrawDebugPoint(World, NewPositionOnSpline, 30.0f, FColor::Magenta, false, .1f);
DrawDebugPoint(World, TargetLocation, 30.0f, FColor::Green, false, .1f);
}
#endif
// Compute delta based on adjusted velocity
const FVector AdjustedVelocity{ GrindData.CurrentGrindTangent * SpeedAlongTangent };
FVector Delta{
bTestNewGrindMethod
? (TargetLocation - UpdatedComponent->GetComponentLocation())
: (DeltaSeconds * AdjustedVelocity)
};
// END TEST, with A|B switch in place to allow us to retain the old math until this gets worked out.`