My contributions to Lightspeed

Lightspeed menu screenshot

I implemented a procedural spline-based track generator in Unreal Engine 5 using C++.

The Class “ATrackGenerator” is as a Blueprint in the level and generates a full closed loop track when pressing the GenerateTrack button in the details panel.

This system supports:

  • Randomized track shape

  • SplineMesh track building

  • Start/Finish line position

  • Randomized obstacle spawning

— generate/Clear buttons

2 buttons in details panel

clear/generate buttons in details panel

UFUNCTION(CallInEditor, Category="Track Generator")
void GenerateTrack();

UFUNCTION(CallInEditor, Category="Track Generator")
void ClearTrack();

— Procedural spline generation The track is generated by building spline points in local space, using a seeded

FRandomStream, then closing and updating the spline.

int32 UseSeed = bRandomizeEveryGenerate
	? (int32)(FDateTime::UtcNow().GetTicks() & 0x7fffffff)
	: Seed;

LastGeneratedSeed = UseSeed;
FRandomStream Stream(UseSeed);

Spline->ClearSplinePoints(false);

for (int32 i = 0; i < NumControlPoints; ++i)
{
	const float T = (float)i / (float)NumControlPoints;
	const float Theta = T * 2.f * PI;

	float R = BaseRadius;
	for (int32 k = 1; k <= Harmonics; ++k)
	{
		R += Amp[k] * FMath::Sin((k + 1) * Theta + Phase[k]);
	}

	FVector P(R * FMath::Cos(Theta), R * FMath::Sin(Theta), 0.f);
	P.Z = BaseHeight + HeightVariation * FMath::Sin(HeightFreq * Theta + HeightPhase);

	P = YawRot.RotateVector(P);
	P += CenterOffset;

	Spline->AddSplinePoint(P, ESplineCoordinateSpace::Local, false);
	Spline->SetSplinePointType(i, ESplinePointType::Curve, false);
}

Spline->SetClosedLoop(true, false);
Spline->UpdateSpline();

— Track meshes using SplineMeshComponents Meshes are spawned along the spline using a calculated segment count and stable tangents.

const FVector StartDir = Spline->GetDirectionAtDistanceAlongSpline(StartDist, ESplineCoordinateSpace::Local);
const FVector EndDir   = Spline->GetDirectionAtDistanceAlongSpline(EndDist, ESplineCoordinateSpace::Local);

const FVector StartTan = StartDir * TangentLen;
const FVector EndTan   = EndDir   * TangentLen;

SplineMesh->SetStartAndEnd(StartPos, StartTan, EndPos, EndTan, true);
SplineMesh->SetSplineUpDir(FVector::UpVector, false);
SplineMesh->bSmoothInterpRollScale = true;

— Auto segment sizing + safety cap The generator can compute effective segment length from the mesh bounds (including a forward-scale multiplier) and clamp the segment count.

float MeshLen = 2.f * Extent.X;
float ForwardScale = TrackPieceScale.X;

EffectiveSegmentLength = FMath::Max(50.f, (MeshLen * ForwardScale) * MeshLengthMultiplier);

const int32 DesiredSegments = FMath::CeilToInt(SplineLen / EffectiveSegmentLength);
const int32 NumSegments = FMath::Clamp(DesiredSegments, 1, MaxMeshSegments);

— Start/Finish line as a spawned Actor The start/finish line is spawned from an assignable StartFinishClass and placed at spline distance 0 with offsets.

start/finish

FTransform T = Spline->GetTransformAtDistanceAlongSpline(0.f, ESplineCoordinateSpace::World, true);

FRotator R = T.GetRotation().Rotator();
R.Yaw += StartFinishYawOffset;
T.SetRotation(R.Quaternion());

T.AddToTranslation(T.GetRotation().RotateVector(StartFinishLocalOffset));

SpawnedStartFinishActor = World->SpawnActor<AActor>(StartFinishClass, T, Params);

— Random obstacle spawning Obstacles spawn as BP actors along the spline with spacing and scaling controls.

obstacle class

float D = Stream.FRandRange(StartSafeDistance, Len - StartSafeDistance);

FTransform T = GetTransformAtDistance(D, Lateral, ObstacleVerticalOffset);

AActor* Obstacle = World->SpawnActor<AActor>(ObstacleClass, T, Params);
Obstacle->SetActorScale3D(FVector(
	Stream.FRandRange(ObstacleScaleMin.X, ObstacleScaleMax.X),
	Stream.FRandRange(ObstacleScaleMin.Y, ObstacleScaleMax.Y),
	Stream.FRandRange(ObstacleScaleMin.Z, ObstacleScaleMax.Z)
));

ATrackGenerator is an editor-driven tool, it generates a randomized closed loop spline track, builds stable spline meshes with performance safeguards, supports scalable segments, spawns a start/finish actor for collision/logic, and can spawn scalable randomized obstacle actors along the track.

-Jamie

Here are some Lightshot Images!

lightspeed screenshot 1

lightspeed screenshot 2

lightspeed screenshot 3

lightspeed screenshot 4