4. C++ API
This page covers common patterns for working with MetaRoad data from C++ at runtime: reading the road layout, querying lane positions, evaluating attributes, and extending the plugin with custom attribute and zone types.
Module:
MetaRoad(runtime). Add it to your module’sPublicDependencyModuleNamesin the.Build.csfile.
4.1. Finding Road Components
URoadSplineComponent is a standard UActorComponent. Use normal UE traversal to find instances in the world:
// Iterate all actors in the world that contain a URoadSplineComponent
for (TActorIterator<AActor> It(GetWorld()); It; ++It)
{
URoadSplineComponent* Road = It->FindComponentByClass<URoadSplineComponent>();
if (Road)
{
// work with Road
}
}
Or, if you placed roads inside known actors, keep direct references in your game code.
4.2. Reading the Road Layout
URoadSplineComponent owns an FRoadLayout which holds the array of FRoadLaneSection objects:
const FRoadLayout& Layout = RoadSpline->GetRoadLayout();
for (int i = 0; i < Layout.Sections.Num(); ++i)
{
// Always use GetLeftRighLanes — Left[] or Right[] may be empty
// in asymmetric layouts (Side == Left or Side == Right).
auto [LeftLanes, RightLanes] = Layout.GetLeftRighLanes(i);
for (const FRoadLane& Lane : RightLanes)
{
// Lane.LaneIndex > 0 = right side
}
for (const FRoadLane& Lane : LeftLanes)
{
// Lane.LaneIndex < 0 = left side
}
}
To get a specific lane directly:
// Returns nullptr if the combination is invalid
const FRoadLane* Lane = RoadSpline->GetRoadLane(SectionIndex, LaneIndex);
Lane index conventions:
0(MetaRoad::ZeroLaneIndex) — invalid / reference centerline, no width> 0— right of the reference spline< 0— left of the reference spline
4.3. Evaluating Lane Positions
4.3.1. World position at a given arc length
// Alpha: 0.0 = inner edge (nearest center), 1.0 = outer edge
// Note: function name has a typo ("Poistion") — it is Blueprint-exposed and cannot be renamed.
FVector Pos = RoadSpline->EvalLanePoistion(
SectionIndex, LaneIndex,
/*S=*/ 500.0,
/*Alpha=*/ 0.5, // lane center
ESplineCoordinateSpace::World);
4.3.2. Full road position (position + orientation)
FRoadPosition RoadPos = RoadSpline->GetRoadPosition(
SectionIndex, LaneIndex,
/*Alpha=*/ 1.0, // outer edge
/*SOffset=*/ 500.0,
ESplineCoordinateSpace::World);
FVector WorldLoc = RoadPos.Location;
FQuat WorldQuat = RoadPos.Quat; // forward = lane direction
double S = RoadPos.SOffset;
double R = RoadPos.ROffset; // positive = right of center
4.3.3. Arc-length range of a lane
URoadSplineComponent::FRang Range = RoadSpline->GetLaneRang(SectionIndex, LaneIndex);
double StartS = Range.StartS;
double EndS = Range.EndS;
4.3.4. Hit-testing against lane geometry
FRoadHitResult Hit;
if (RoadSpline->LineTrace(Start, End, Hit))
{
FVector HitPt = Hit.HitPoint;
int32 SectionIdx = Hit.SectionIndex;
int32 LaneIdx = Hit.LaneIndex;
}
4.4. Lane Attribute Queries
Attributes are stored per lane as a map from descriptor class to FRoadLaneAttribute:
#include "Assets/RoadLaneAttributeDescriptor.h"
#include "Assets/RoadLaneAttributeSpeed.h" // example
const FRoadLane* Lane = RoadSpline->GetRoadLane(SectionIndex, LaneIndex);
if (!Lane) return;
const FRoadLaneAttribute* SpeedAttr =
Lane->Attributes.Find(URoadLaneAttributeSpeed::StaticClass());
if (SpeedAttr && SpeedAttr->CanEvaluate())
{
FRoadLaneAttributeValueSpeed SpeedVal =
SpeedAttr->Evaluate<FRoadLaneAttributeValueSpeed>(SOffset);
float MaxSpeedMS = SpeedVal.MaxSpeed; // metres per second
}
Evaluate<T>() returns the stepped or interpolated value at the requested arc-length position, depending on whether the value type overrides CanInterpolate().
4.5. Custom Attribute Types
Custom attributes let you attach arbitrary per-lane data (e.g. traffic density, vegetation type, surface roughness) that varies along the lane.
4.5.1. Step 1 — Define the value struct
// MyRoadAttribute.h
#include "RoadLaneAttribute.h"
#include "MyRoadAttribute.generated.h"
USTRUCT(BlueprintType)
struct FMyRoadAttributeValue : public FRoadLaneAttributeValue
{
GENERATED_USTRUCT_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MyAttribute)
float Density = 1.0f;
// Stepped evaluation (default). Override to return true for cubic blending.
virtual bool CanInterpolate() const override { return false; }
// Called only when CanInterpolate() == true.
// Alpha [0,1]: 0 = at this key, 1 = at the next key.
virtual bool Interpolate(const UScriptStruct* ValueType,
const FRoadLaneAttributeValue* Other, float Alpha,
FRoadLaneAttributeValue* Out) const override
{
// Example linear blend:
const FMyRoadAttributeValue* OtherVal = static_cast<const FMyRoadAttributeValue*>(Other);
FMyRoadAttributeValue* OutVal = static_cast<FMyRoadAttributeValue*>(Out);
OutVal->Density = FMath::Lerp(Density, OtherVal->Density, Alpha);
return true;
}
// Lateral position [0,1] for rendering the key handle in the editor.
// 0.0 = inner edge, 0.5 = lane center (default), 1.0 = outer edge.
virtual double GetKeyAlpha() const override { return 0.5; }
};
4.5.2. Step 2 — Register the descriptor
// URoadLaneAttributeDescriptor subclass — acts as the "type key" for the attribute map.
#include "Assets/RoadLaneAttributeDescriptor.h"
#include "MyRoadAttributeDescriptor.generated.h"
UCLASS()
class UMyRoadAttributeDescriptor : public URoadLaneAttributeDescriptor
{
GENERATED_BODY()
public:
virtual TConstStructView<FRoadLaneAttributeValue> GetAttributeValueTemplate() const override
{
static FMyRoadAttributeValue Default;
return TConstStructView<FRoadLaneAttributeValue>::Make(Default);
}
};
Once this class exists, it is automatically available in the editor’s Attribute Modes menu (no further registration needed).
4.5.3. Step 3 — Evaluate at runtime
const FRoadLane* Lane = RoadSpline->GetRoadLane(SectionIndex, LaneIndex);
if (const FRoadLaneAttribute* Attr = Lane->Attributes.Find(UMyRoadAttributeDescriptor::StaticClass()))
{
FMyRoadAttributeValue Val = Attr->Evaluate<FMyRoadAttributeValue>(SOffset);
float Density = Val.Density;
}
4.6. Custom Zone Types
Zone types define the surface category of a lane (Driving, Sidewalk, Marking, etc.). To register a custom type:
Open Edit → Project Settings → Plugins → MetaRoad
In Road Zone Types, add a new entry with a unique
FNamekeyFill in
FRoadZoneTypeDetails: material, decal material, material priority, editor color
The type is then available in the Zone Type dropdown on any FRoadLane or FRoadZone.
From C++, read the registered types via:
#include "MetaRoadSettings.h"
const UMetaRoadSettings* Settings = GetDefault<UMetaRoadSettings>();
const TMap<FName, FRoadZoneTypeDetails>& ZoneTypes = Settings->RoadZoneTypes;
4.7. UpdateLayout() Lifecycle
After any programmatic change to RoadLayout.Sections[] (adding/removing sections or lanes, changing SOffset), call:
RoadSpline->UpdateRoadLayout(); // rebuilds back-pointers (SectionIndex, LaneIndex, OwnedRoadLayout)
RoadSpline->UpdateLaneSectionBounds(); // recomputes SOffsetEnd based on current spline length
RoadSpline->TrimLaneSections(); // removes sections outside [0, SplineLength]
RoadSpline->MarkRoadStateDirty(); // queues procedural regeneration
Skipping UpdateRoadLayout() leaves LaneIndex, SectionIndex, and OwnedRoadLayout stale — subsequent queries will return wrong results.