Unreal Engine: Using Spline Mesh Components in C++

Spline mesh components are very useful for making things such as pipes, cables and ventilation ducts as well as more complex objects such as roads, fences, train tracks, etc, this article will show you how to make a basic component with a start, middle and end mesh.

Before you start a few notes on making good meshes, you want their length to be roughly equal to the distance between spline points, the smaller you go the greater the smoothness, at an increased performance cost of course, make sure you keep the position the same between meshes.

Setting up

Firstly you want to add your main components to your AActor class, these being a USceneComponent which we will use as our root component, the USplineComponent which we will modify as desired as well as the UStaticMesh objects and an optional UMaterial, I've also added a createSplineMesh() function.

You will also need to override the OnConstruction event, this is where your SplineMeshComponents will be built, this cannot be done in the constructor.

protected:
virtual void OnConstruction(const FTransform &Transform) override;
public:
void createSplineMesh();

UPROPERTY()
USceneComponent* C_Scene;
UPROPERTY()
USplineComponent* C_Spline;
UPROPERTY(EditAnywhere, Category = Mesh)
UStaticMesh *SingleMesh;
UPROPERTY(EditAnywhere, Category = Mesh)
UStaticMesh *StartMesh;
UPROPERTY(EditAnywhere, Category = Mesh)
UStaticMesh *MiddleMesh;
UPROPERTY(EditAnywhere, Category = Mesh)
UStaticMesh *EndMesh;
UPROPERTY(EditAnywhere, Category = Material)
UMaterial *Material;

In the constructor we setup our basic components, set the root component and attach our spline to it.

AChryseus_SplineMesh::AChryseus_SplineMesh()
{
  C_Spline = CreateDefaultSubobject(TEXT("Spline"));
  C_Scene = CreateDefaultSubobject(TEXT("SceneRoot"));;
  RootComponent = C_Scene;
  C_Spline->SetupAttachment(RootComponent);
  RootComponent->SetMobility(EComponentMobility::Type::Static);
  C_Spline->SetMobility(EComponentMobility::Type::Static);
  this->SetActorEnableCollision(true);
  PrimaryActorTick.bCanEverTick = true;
}

For the OnConstruction event I simply call the createSplineMesh() function but if you want you can put the code all in here.

void AChryseus_SplineMesh::OnConstruction(const FTransform& Transform)
{
  this->createSplineMesh();
}

Creating the Spline Mesh Components

First we want to setup some important variable and get the number of points in the spline, I've also added a check here so a minimum of 2 points is required.

FVector locStart;
FVector tanStart;
FVector locEnd;
FVector tanEnd;
int32 numPoints = C_Spline->GetNumberOfSplinePoints();
if (numPoints < 2)
{
  UE_LOG(LogTemp, Display, TEXT("Spline has too few points"));
  return;
}

Next we need to create the USplineMeshComponent for every point, minus 1 so we don't create too many.

for (int32 i = 0; i < (numPoints -1); i++)
{

}

In this loop we create the USplineMeshComponent using NewObject(this) rather than CreateDefaultSubobject which can only be used in the constructor, since SplineMeshComponents will need to be recreated on every change it cannot be done in the constructor anyway, it's also important that we register the component as it's not done automatically, finally CreationMethod = EComponentCreationMethod::UserConstructionScript; is required for it to work.

USplineMeshComponent *splineMesh = NewObject(this);
splineMesh->RegisterComponent();
splineMesh->CreationMethod = EComponentCreationMethod::UserConstructionScript;

We can then get the location and tangent at the appropriate spline points, set the mobility and perhaps most important the forward axis of your mesh, I.E what direction it faces, the mesh will also need to be set depending on the number of spline points and the current point, for a simple two point solution we only need one mesh, for more than two need a start, middle and end mesh.

C_Spline->GetLocationAndTangentAtSplinePoint(i, locStart, tanStart, ESplineCoordinateSpace::Local);
C_Spline->GetLocationAndTangentAtSplinePoint(i+1, locEnd, tanEnd, ESplineCoordinateSpace::Local);
splineMesh->SetMobility(EComponentMobility::Type::Static);
splineMesh->SetForwardAxis(ESplineMeshAxis::X);
if (numPoints == 2) // Two point spline
{
  splineMesh->SetStaticMesh(SingleMesh);
}
else if (numPoints > 2 && i == 0) // Start of spline
{
  splineMesh->SetStaticMesh(StartMesh);
}
else if (numPoints > 2 && i == (numPoints - 2)) // End of spline
{
  splineMesh->SetStaticMesh(EndMesh);
}
else // Middle points
{
  splineMesh->SetStaticMesh(MiddleMesh);
}

Finally we set the material (if needed), collision settings and the start and end points, it is then attached to the root component.

splineMesh->SetMaterial(0, Material);
splineMesh->SetCollisionObjectType(ECollisionChannel::ECC_WorldStatic);
splineMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
splineMesh->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block);
splineMesh->SetStartAndEnd(locStart, tanStart, locEnd, tanEnd);
splineMesh->AttachToComponent(RootComponent, FAttachmentTransformRules::FAttachmentTransformRules(EAttachmentRule::KeepRelative, true));

That's all that's required to get this working, if you wish to make dynamic changes in game then you will need to do so in BeginPlay() and Tick(), easiest way to do this is to insert the Spline Mesh Components in to a TArray and go from there.


Comments

Comments, ideas and criticism welcome.




No comments at the moment, why not make one ?