I am using the ULineBatchComponent in an C++ Actor class to visualize a large collection of points and lines.
Example of the component working in a simulated standalone game:
The actor header contains:
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/LineBatchComponent.h"
#include "MyVisualizer.generated.h"
UCLASS()
class MYPROJECT_API AMyVisualizer : public AActor
{
GENERATED_BODY()
// Master array with all the point data
TArray<FVector2D> Points;
// Array Of indices that map to groups of lines in Points array
TArray<int32> DrawGroupIndices;
// Array keeping track of coloring info
TArray<char> LineColor;
// Array Storing lines to be drawn
TArray<FBatchedLine> Lines;
public:
// Sets default values for this actors properties
AMyVisualizer();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
\\ ...
UFUNCTION(BlueprintCallable)
int DrawLines(int32 LineGroupNumber, bool ShowPoints, float PointSize);
int DrawPoints(int32 LineGroupNumber, float PointSize);
// Line Batch Component
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Debug")
class ULineBatchComponent* LineBatcher;
};
The Actor constructor is set up like so:
AMyVisualizer::AMyVisualizer()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you dont need it.
PrimaryActorTick.bCanEverTick = true;
// Initialize LineBatcher and attach it to the actor
LineBatcher = CreateDefaultSubobject<ULineBatchComponent>(TEXT("LineBatcher"));
}
When I have populated the relevant arrays and I am ready to draw the lines, I call the function DrawLines() from a BP_widget
int AMyVisualizer::DrawLines(int32 LineGroupNumber, bool ShowPoints, float PointSize)
{
int32 StartIdx, EndIdx;
LineBatcher->Flush();
if (this->DrawGroupIndices.IsValidIndex(LineGroupNumber) && this->DrawGroupIndices.IsValidIndex(LineGroupNumber + 1))
{
StartIdx = this->DrawGroupIndices[LineGroupNumber];
EndIdx = this->DrawGroupIndices[LineGroupNumber + 1];
this->Lines.Empty();
}
else
{
return 0;
}
if (ShowPoints)
{
DrawLayerPoints(LineGroupNumber, 0.0f, PointSize);
}
if (ShowLaserPath)
{
for (int32 i = StartIdx; i < EndIdx - 1; i++)
{
FBatchedLine Line;
Line.Thickness = 0.f;
Line.DepthPriority = SDPG_World;
Line.RemainingLifeTime = 0.0f;
Line.Start = FVector(this->Points[i], 0.0f);
Line.End = FVector(this->Points[i + 1], 0.0f);
if (LineColor[i + 1] == 0)
{
Line.Color = FLinearColor::Blue;
}
//
// so on and so forth..
// yes I know I could use a switch statement
// and i likely will in the future but thats not the focus here
//
else
{
continue;
}
this->Lines.Emplace(Line);
}
LineBatcher->DrawLines(this->Lines);
}
return EndIdx - StartIdx;
}
In the editor, and when simulating a standalone game, everything draws as expected and I have not run into performance issues yet.
Is there a different more robust way of producing identical results?
I have considered using Niagara but this approach has felt more straight forward.
Thanks for reading, hope this can help any others looking to do primitive drawing in UE, resources seem somewhat sparse. I suspect most people are more excited about Lumen, nanite, subsurface materials, etc… but the basics are cool too!