LineBatcher in a packaged game

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!

2 Likes