Coffee Shop Game

Project Overview

This is a side project that a few friends and I started while studying. It is a coffee shop simulation game with a mysterious twist. The player takes on the role of a coffee shop owner, serving guests and upgrading the shop with new furniture and equipment. As the business grows and more guests visit, the player gradually uncovers the story behind a murder that once took place in the shop.

My Contributions
  • Developed a server-authoritative 3D placement system with a custom editor for configuring object size, mesh, offsets, and occupied grid space
  • Separated server-side game logic and validation from client-side visual updates and feedback
  • Developed a modular road system supporting connections between main roads through configurable sub-roads
Role
System & Tools Programmer
Duration
Ongoing
Team Size
5
Genre
Simulation, Management
Server-authoritative placement system with client-side previews of both the object and the grid tiles it will occupy. Supports online multiplayer, including placing and moving existing objects, with validation and game-state updates handled by the server.
void UBuildingSubsystem::SetupGrid(FVector2D InCellSize, FIntVector2 InGridSize)
{
	CellSize = InCellSize;
	GridSize = InGridSize;
	Tiles.Init(true, GridSize.X * GridSize.Y);

	OnSettingChangedDelegate.Broadcast(CellSize, GridSize);
}

void UBuildingSubsystem::SetTileOnPosition(FIntVector2 TilePosition, bool NewState)
{
	if (!IsPositionValid(TilePosition)) return;

	int Index = TilePosition.Y * GridSize.X + TilePosition.X;

	Tiles[Index] = NewState;

	OnGridUpdatedDelegate.Broadcast(Tiles);
}

void UBuildingSubsystem::SetTileOnLocation(FVector Location, bool NewState)
{
	int X = (Location.X + (CellSize.X / 2)) / CellSize.X;
	int Y = (Location.Y + (CellSize.Y / 2)) / CellSize.Y;

	if (!IsPositionValid(FIntVector2(X, Y)))
		return;

	int Index = Y * GridSize.X + X;
	Tiles[Index] = NewState;

	OnGridUpdatedDelegate.Broadcast(Tiles);
}

bool UBuildingSubsystem::GetTile(FIntVector2 TilePosition)
{
	if (!IsPositionValid(TilePosition)) return false;

	int Index = TilePosition.Y * GridSize.X + TilePosition.X;

	return Tiles[Index];
}

bool UBuildingSubsystem::GetTileAtLocation(FVector Location)
{
	int X = (Location.X + (CellSize.X / 2)) / CellSize.X;
	int Y = (Location.Y + (CellSize.Y / 2)) / CellSize.Y;

	if (!IsPositionValid(FIntVector2(X, Y)))
		return false;

	int Index = Y * GridSize.X + X;

	return Tiles[Index];
}

FVector UBuildingSubsystem::GetClosestLocation(FVector Location)
{
	int X = (Location.X + (CellSize.X / 2)) / CellSize.X;
	int Y = (Location.Y + (CellSize.Y / 2)) / CellSize.Y;

	if (!IsPositionValid(FIntVector2(X, Y)))
		return FVector(-100);

	return FVector((X * CellSize.X), (Y * CellSize.Y), 0);
}

bool UBuildingSubsystem::GetClosestLocation(FVector Location, FVector& OutLocation)
{
	int X = (Location.X + (CellSize.X / 2)) / CellSize.X;
	int Y = (Location.Y + (CellSize.Y / 2)) / CellSize.Y;

	if (!IsPositionValid(FIntVector2(X, Y)))
		return false;

	OutLocation = FVector((X * CellSize.X), (Y * CellSize.Y), 0);
	return true;
}

TArray<FVector2D> UBuildingSubsystem::GetTileLocations()
{
	TArray<FVector2D> ReturnList;

	for (int Y = 0; Y < GridSize.Y; Y++)
	{
		for (int X = 0; X < GridSize.X; X++)
		{
			ReturnList.Add(FVector2D(CellSize.X * X, CellSize.Y * Y));
		}
	}

	return ReturnList;
}

void UBuildingSubsystem::SetTilesWithDataAsset(FVector Location, UFurnitureData* DataAsset, bool IsAvailable)
{
	FIntVector2 Origin((Location.X + (CellSize.X / 2)) / CellSize.X, (Location.Y + (CellSize.Y / 2)) / CellSize.Y);
	TArray<int> Indices;

	for (auto& Offset : DataAsset->Blocks)
	{
		FIntVector2 Position = Origin + Offset;
		if (!IsPositionValid(Position))
			return;

		Indices.Add(Position.Y * GridSize.X + Position.X);
	}

	for (auto& Index : Indices)
	{
		Tiles[Index] = IsAvailable;
	}

	OnGridUpdatedDelegate.Broadcast(Tiles);
}

bool UBuildingSubsystem::CanPlaceFurniture(FVector Location, UFurnitureData* DataAsset)
{
	FIntVector2 Origin((Location.X + (CellSize.X / 2)) / CellSize.X, (Location.Y + (CellSize.Y / 2)) / CellSize.Y);

	for (auto& Offset : DataAsset->Blocks)
	{
		FIntVector2 Position = Origin + Offset;
		if (!IsPositionValid(Position) || !GetTile(Position))
			return false;
	}

	return true;
}

FVector2D UBuildingSubsystem::GetCellSize()
{
	return CellSize;
}

FIntVector2 UBuildingSubsystem::GetGridSize()
{
	return GridSize;
}

bool UBuildingSubsystem::IsPositionValid(FIntVector2 TilePosition)
{
	if (TilePosition.X < 0 || TilePosition.X >= GridSize.X ||
		TilePosition.Y < 0 || TilePosition.Y >= GridSize.Y)
		return false;

	return true;
}
void UBuildingComponent::BeginPlay()
{
	Super::BeginPlay();

	// ...
	Player = Cast<APawn>(GetOwner());
	if (Player) Controller = Player->GetController();

	TraceSubsystem = GetWorld()->GetSubsystem<ULookTraceSubsystem>();

	BuildingSubsystem = GetWorld()->GetSubsystem<UBuildingSubsystem>();

	if (!Player->IsLocallyControlled())
		return;

	PreviewActor = GetWorld()->SpawnActor<APreviewActor>(PreviewActorClass);

	GridVisualizer = Cast<AGridVisualizer>(UGameplayStatics::GetActorOfClass(GetWorld(), AGridVisualizer::StaticClass()));
}


// Called every frame
void UBuildingComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	if (!Furniture || !Player->IsLocallyControlled())
		return;

	FVector TraceHitLocation = TraceSubsystem->GetLocationFromLineTrace(Controller);

	if (!BuildingSubsystem)
	{
		PlaceLocation = TraceHitLocation;
		bOnGrid = false;
	}

	if (BuildingSubsystem->GetClosestLocation(TraceHitLocation, PlaceLocation))
	{
		PlaceLocation.Z = TraceHitLocation.Z;
		bOnGrid = true;
	}
	else
	{
		PlaceLocation = TraceHitLocation;
		bOnGrid = false;
	}

	PreviewActor->SetActorLocation(PlaceLocation + Furniture->GetData()->OffsetFromOrigin);
	bool CanPlace = BuildingSubsystem->CanPlaceFurniture(PlaceLocation, Furniture->GetData());
	PreviewActor->SetCanPlacePreview(CanPlace);
	GridVisualizer->UpdatePlacableStatus(CanPlace);

	if (bOnGrid)
		GridVisualizer->UpdatePreviewMeshLocation(PlaceLocation);
}

void UBuildingComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME_CONDITION(UBuildingComponent, Furniture, COND_OwnerOnly);
}

void UBuildingComponent::SetBuildingPreviewActor(AFurniture* InPreviewActor)
{
	BuildingSubsystem->SetTilesWithDataAsset(InPreviewActor->GetActorLocation() - InPreviewActor->GetData()->OffsetFromOrigin, InPreviewActor->GetData(), true);
	Furniture = InPreviewActor;

	if (GetOwner()->HasAuthority() && Player->IsLocallyControlled())
		SetPreviewMesh();
}

void UBuildingComponent::SetPreviewMesh()
{
	if (!Player->IsLocallyControlled())
		return;

	if (!Furniture)
	{
		PreviewActor->SetActorHiddenInGame(true);
		GridVisualizer->EnablePreview(false);
	}
	else
	{
		PreviewActor->SetActorHiddenInGame(false);
		PreviewActor->SetMesh(Furniture->GetMeshComponent()->GetStaticMesh());
		PreviewActor->SetActorScale3D(Furniture->GetData()->Scale);
		GridVisualizer->UpdatePreviewMesh(Furniture->GetData());
		GridVisualizer->EnablePreview(true);
	}
}

bool UBuildingComponent::ResetBuildingPreview(FVector LookAtLocation, FVector& OutPlaceLocation)
{
	bOnGrid = false;
	if (BuildingSubsystem->GetClosestLocation(LookAtLocation, PlaceLocation))
	{
		PlaceLocation.Z = LookAtLocation.Z;
		bOnGrid = true;
	}

	if (PlaceLocation == FVector::ZeroVector || !bOnGrid)
		return false;

	if (!BuildingSubsystem->CanPlaceFurniture(PlaceLocation, Furniture->GetData()))
		return false;

	BuildingSubsystem->SetTilesWithDataAsset(PlaceLocation, Furniture->GetData(), false);
	FVector Offset = Furniture->GetData()->OffsetFromOrigin;
	Furniture = nullptr;
	if (GetOwner()->HasAuthority() && Player->IsLocallyControlled())
		SetPreviewMesh();

	OutPlaceLocation = PlaceLocation + Offset;
	return true;
}

AFurniture* UBuildingComponent::GetBuildingPreviewActor()
{
	return Furniture;
}
APreviewActor::APreviewActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	MeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComp"));
}

void APreviewActor::SetMesh(UStaticMesh* Mesh)
{
	MeshComp->SetStaticMesh(Mesh);
}

void APreviewActor::SetCanPlacePreview(bool CanPlace)
{
	if (CanPlace)
		MeshComp->SetVectorParameterValueOnMaterials("PreviewColor", FVector(0, 1, 0));
	else
		MeshComp->SetVectorParameterValueOnMaterials("PreviewColor", FVector(1, 0, 0));
}
AGridVisualizer::AGridVisualizer()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	
	RootComponent = CreateDefaultSubobject<USceneComponent>("Parent");

	LineMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("LineMesh"));
	LineMesh->SetupAttachment(RootComponent);

	CellMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("CellMesh"));
	CellMesh->SetupAttachment(RootComponent);

	CellPreviewMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("PreviewMesh"));
	CellPreviewMesh->SetupAttachment(RootComponent);
}

// Called when the game starts or when spawned
void AGridVisualizer::BeginPlay()
{
	Super::BeginPlay();

	LineMesh->SetMaterial(0, LineMaterial);
	CellMesh->SetMaterial(0, CellMaterial);
	CellPreviewMesh->SetMaterial(0, PreviewMaterial);

	UBuildingSubsystem* Subsystem = GetWorld()->GetSubsystem<UBuildingSubsystem>();
	if (!Subsystem)
		return;

	Subsystem->OnSettingChangedDelegate.AddDynamic(this, &AGridVisualizer::UpdateMesh);
	Subsystem->OnGridUpdatedDelegate.AddDynamic(this, &AGridVisualizer::UpdateCellMesh);
}

void AGridVisualizer::UpdateMesh(FVector2D NewCellSize, FIntVector2 NewGridSize)
{
	CellSize = NewCellSize;
	GridSize = NewGridSize;

	TArray<FVector> LineVertices;
	TArray<int> LineTriangles;

	// Generate line mesh
	// Row lines
	for (int Row = 0; Row < GridSize.Y + 1; Row++)
	{
		LineTriangles.Add(LineVertices.Num() + 0);
		LineTriangles.Add(LineVertices.Num() + 3);
		LineTriangles.Add(LineVertices.Num() + 2);
		LineTriangles.Add(LineVertices.Num() + 2);
		LineTriangles.Add(LineVertices.Num() + 1);
		LineTriangles.Add(LineVertices.Num() + 0);

		LineVertices.Add(FVector(
			-LineThickness / 2 - (CellSize.X / 2), // X
			CellSize.Y * Row - (LineThickness / 2) - (CellSize.Y / 2), // Y
			0));				 // Z
		LineVertices.Add(FVector(
			CellSize.X * GridSize.X + (LineThickness / 2) - (CellSize.X / 2),
			CellSize.Y * Row - (LineThickness / 2) - (CellSize.Y / 2),
			0));
		LineVertices.Add(FVector(
			CellSize.X * GridSize.X + (LineThickness / 2) - (CellSize.X / 2),
			CellSize.Y * Row + (LineThickness / 2) - (CellSize.Y / 2),
			0));
		LineVertices.Add(FVector(
			-LineThickness / 2 - (CellSize.X / 2),
			CellSize.Y * Row + (LineThickness / 2) - (CellSize.Y / 2),
			0));
	}

	// Column lines
	for (int Col = 0; Col < GridSize.X + 1; Col++)
	{
		LineTriangles.Add(LineVertices.Num() + 0);
		LineTriangles.Add(LineVertices.Num() + 3);
		LineTriangles.Add(LineVertices.Num() + 2);
		LineTriangles.Add(LineVertices.Num() + 2);
		LineTriangles.Add(LineVertices.Num() + 1);
		LineTriangles.Add(LineVertices.Num() + 0);

		LineVertices.Add(FVector(
			CellSize.X * Col - (LineThickness / 2) - (CellSize.X / 2),
			-LineThickness / 2 - (CellSize.Y / 2),
			0));				 
		LineVertices.Add(FVector(
			CellSize.X * Col + (LineThickness / 2) - (CellSize.X / 2),
			-LineThickness / 2 - (CellSize.Y / 2),
			0));
		LineVertices.Add(FVector(
			CellSize.X * Col + (LineThickness / 2) - (CellSize.X / 2),
			CellSize.Y * GridSize.Y + (LineThickness / 2) - (CellSize.Y / 2),
			0));
		LineVertices.Add(FVector(
			CellSize.X * Col - (LineThickness / 2) - (CellSize.X / 2),
			CellSize.Y * GridSize.Y + (LineThickness / 2) - (CellSize.Y / 2),
			0));
	}

	LineMesh->CreateMeshSection(0, LineVertices, LineTriangles, TArray<FVector>(), TArray<FVector2D>(), TArray<FColor>(), TArray<FProcMeshTangent>(), false);
}

void AGridVisualizer::EnablePreview(bool Enable)
{
	CellPreviewMesh->SetVisibility(Enable);
	CellMesh->SetVisibility(Enable);
	LineMesh->SetVisibility(Enable);
}

void AGridVisualizer::UpdatePreviewMesh(UFurnitureData* FurnitureData)
{
	TArray<FVector> PreviewVertices;
	TArray<int> PreviewTriangles;

	float UseSpacing = FMath::Max(LineThickness, Spacing);

	for (auto& Offset : FurnitureData->Blocks)
	{
		PreviewTriangles.Add(PreviewVertices.Num() + 0);
		PreviewTriangles.Add(PreviewVertices.Num() + 3);
		PreviewTriangles.Add(PreviewVertices.Num() + 2);
		PreviewTriangles.Add(PreviewVertices.Num() + 2);
		PreviewTriangles.Add(PreviewVertices.Num() + 1);
		PreviewTriangles.Add(PreviewVertices.Num() + 0);

		PreviewVertices.Add(FVector(
			(Offset.X * CellSize.X) - (CellSize.X / 2) + (UseSpacing / 2),
			(Offset.Y * CellSize.Y) - (CellSize.Y / 2) + (UseSpacing / 2),
			0));
		PreviewVertices.Add(FVector(
			(Offset.X * CellSize.X) + (CellSize.X / 2) - (UseSpacing / 2),
			(Offset.Y * CellSize.Y) - (CellSize.Y / 2) + (UseSpacing / 2),
			0));
		PreviewVertices.Add(FVector(
			(Offset.X * CellSize.X) + (CellSize.X / 2) - (UseSpacing / 2),
			(Offset.Y * CellSize.Y) + (CellSize.Y / 2) - (UseSpacing / 2),
			0));
		PreviewVertices.Add(FVector(
			(Offset.X * CellSize.X) - (CellSize.X / 2) + (UseSpacing / 2),
			(Offset.Y * CellSize.Y) + (CellSize.Y / 2) - (UseSpacing / 2),
			0));
	}

	CellPreviewMesh->CreateMeshSection(0, PreviewVertices, PreviewTriangles, TArray<FVector>(), TArray<FVector2D>(), TArray<FColor>(), TArray<FProcMeshTangent>(), false);
}

void AGridVisualizer::UpdatePreviewMeshLocation(FVector Location)
{
	CellPreviewMesh->SetWorldLocation(FVector(Location.X, Location.Y, GetActorLocation().Z + 0.1f));
}

void AGridVisualizer::UpdatePlacableStatus(bool CanPlace)
{
	CellPreviewMesh->SetVectorParameterValueOnMaterials("PreviewColor", CanPlace ? FVector(0, 1, 0) : FVector(1, 0, 0));
}

void AGridVisualizer::UpdateCellMesh(const TArray<bool>& Tiles)
{
	TArray<FVector> CellVertices;
	TArray<int> CellTriangles;

	float UseSpacing = FMath::Max(LineThickness, Spacing);

	// Generate cell mesh
	for (int X = 0; X < GridSize.X; X++)
	{
		for (int Y = 0; Y < GridSize.Y; Y++)
		{
			if (Tiles[Y * GridSize.X + X])
				continue;

			CellTriangles.Add(CellVertices.Num() + 0);
			CellTriangles.Add(CellVertices.Num() + 3);
			CellTriangles.Add(CellVertices.Num() + 2);
			CellTriangles.Add(CellVertices.Num() + 2);
			CellTriangles.Add(CellVertices.Num() + 1);
			CellTriangles.Add(CellVertices.Num() + 0);

			CellVertices.Add(FVector(
				(X * CellSize.X) - (CellSize.X / 2) + (UseSpacing / 2),
				(Y * CellSize.Y) - (CellSize.Y / 2) + (UseSpacing / 2),
				0));
			CellVertices.Add(FVector(
				(X * CellSize.X) + (CellSize.X / 2) - (UseSpacing / 2),
				(Y * CellSize.Y) - (CellSize.Y / 2) + (UseSpacing / 2),
				0));
			CellVertices.Add(FVector(
				(X * CellSize.X) + (CellSize.X / 2) - (UseSpacing / 2),
				(Y * CellSize.Y) + (CellSize.Y / 2) - (UseSpacing / 2),
				0));
			CellVertices.Add(FVector(
				(X * CellSize.X) - (CellSize.X / 2) + (UseSpacing / 2),
				(Y * CellSize.Y) + (CellSize.Y / 2) - (UseSpacing / 2),
				0));
		}
	}

	CellMesh->CreateMeshSection(0, CellVertices, CellTriangles, TArray<FVector>(), TArray<FVector2D>(), TArray<FColor>(), TArray<FProcMeshTangent>(), false);
}
bool AFurniture::PickUp(const FInteractionContext& Context)
{
	UHolderComponent* HolderComponent = Context.HolderComponent;
	if (!HolderComponent) return false;
	UBuildingComponent* BuildingComponent = Context.BuildingComponent;
	if (!BuildingComponent) return false;

	UHeldItem* HolderComponentHeldItem = HolderComponent->GetHeldItem();

	if (HolderComponentHeldItem) //pick up
		return false;

	BuildingComponent->SetBuildingPreviewActor(this);
	return Super::PickUp(Context);
}

bool AFurniture::Drop(const FInteractionContext& Context)
{
	UHolderComponent* HolderComponent = Context.HolderComponent;
	if (!HolderComponent) return false;
	UBuildingComponent* BuildingComponent = Context.BuildingComponent;
	if (!BuildingComponent) return false;

	UHeldItem* HolderComponentHeldItem = HolderComponent->GetHeldItem();
	FVector Location;

	if (BuildingComponent->ResetBuildingPreview(Context.LookedAtLocation, Location))
	{
		FInteractionContext NewContext(Context);
		NewContext.LookedAtLocation = Location;
		if (!Super::Drop(NewContext))
			return false;

		Multicast_ResetRotation();
		return true;
	}

	return false;
}

void AFurniture::BeginPlay()
{
	Super::BeginPlay();

	if (!HasAuthority())
		return;

	MeshComp->SetStaticMesh(Data->Mesh);
	SetActorScale3D(Data->Scale);
}

UFurnitureData* AFurniture::GetData()
{
	return Data;
}


void AFurniture::Multicast_ResetRotation_Implementation()
{
	SetActorRotation(FRotator::ZeroRotator);
}
The Placeable Editor has two main features. First, it allows developers to configure the mesh and adjust its position, size, and rotation, with a real-time preview showing how the object aligns with the placement grid. Second, developers can define which grid tiles the object occupies. All configuration changes are reflected in-game when the player previews and places the object.
AEditorGridVisualizer::AEditorGridVisualizer() : AGridVisualizer()
{
	PivotMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("PivotMesh"));
	PivotMesh->SetupAttachment(RootComponent);
}

void AEditorGridVisualizer::BeginPlay()
{
	Super::BeginPlay();

	CellSize = UGridSettings::GetGridSettings()->CellSize;

	PivotMesh->SetMaterial(0, PivotMaterial);

	TArray<FVector> CellVertices;
	TArray<int> CellTriangles;

	float UseSpacing = FMath::Max(LineThickness, Spacing);

	CellTriangles.Add(CellVertices.Num() + 0);
	CellTriangles.Add(CellVertices.Num() + 3);
	CellTriangles.Add(CellVertices.Num() + 2);
	CellTriangles.Add(CellVertices.Num() + 2);
	CellTriangles.Add(CellVertices.Num() + 1);
	CellTriangles.Add(CellVertices.Num() + 0);

	CellVertices.Add(FVector(
		-(CellSize.X / 2) + (UseSpacing / 2),
		-(CellSize.Y / 2) + (UseSpacing / 2),
		0));
	CellVertices.Add(FVector(
		+(CellSize.X / 2) - (UseSpacing / 2),
		-(CellSize.Y / 2) + (UseSpacing / 2),
		0));
	CellVertices.Add(FVector(
		+(CellSize.X / 2) - (UseSpacing / 2),
		+(CellSize.Y / 2) - (UseSpacing / 2),
		0));
	CellVertices.Add(FVector(
		-(CellSize.X / 2) + (UseSpacing / 2),
		+(CellSize.Y / 2) - (UseSpacing / 2),
		0));

	PivotMesh->CreateMeshSection(0, CellVertices, CellTriangles, TArray<FVector>(), TArray<FVector2D>(), TArray<FColor>(), TArray<FProcMeshTangent>(), false);
}

void AEditorGridVisualizer::SetupGrid(FVector InOffset)
{
	Offset = InOffset;

	SetPivotLocation(Pivot);
}

int AEditorGridVisualizer::GetTileIndexAtLocation(FVector Location, FVector2D& CellPosition)
{
	int X = (Location.X + (CellSize.X / 2)) / CellSize.X;
	int Y = (Location.Y + (CellSize.Y / 2)) / CellSize.Y;

	if (X < 0 || X >= GridSize.X ||
		Y < 0 || Y >= GridSize.Y)
		return -1;

	CellPosition = FVector2D(X, Y);

	int Index = Y * GridSize.X + X;
	return Index;
}

int AEditorGridVisualizer::GetTileIndexAtPosition(FVector Location)
{
	return 	Location.Y * GridSize.X + Location.X;;
}

void AEditorGridVisualizer::SetPivotLocation(FVector2D Position)
{
	Pivot = Position;

	FVector Location = FVector(Position.X * CellSize.X, Position.Y * CellSize.Y, 0.0f) + Offset;

	PivotMesh->SetWorldLocation(Location);
}

FVector AEditorGridVisualizer::GetPivotLocation()
{
	return PivotMesh->GetComponentLocation();
}

TArray<FIntVector2> AEditorGridVisualizer::GetBlockData(TArray<bool> Blocks)
{
	TArray<FIntVector2> ReturnList;

	for (int i = 0; i < Blocks.Num(); i++)
	{
		if (Blocks[i])
			ReturnList.Add(FIntVector2(int(i % (int)GridSize.X), int(i / GridSize.X)) - FIntVector2(Pivot.X, Pivot.Y));
	}

	return ReturnList;
}

void AEditorGridVisualizer::UpdateCellMesh(const TArray<bool>& Tiles)
{
	TArray<FVector> CellVertices;
	TArray<int> CellTriangles;

	TArray<FVector> PreviewCellVertices;
	TArray<int> PreviewCellTriangles;

	float UseSpacing = FMath::Max(LineThickness, Spacing);

	// Generate cell mesh
	for (int X = 0; X < GridSize.X; X++)
	{
		for (int Y = 0; Y < GridSize.Y; Y++)
		{
			if (Pivot == FVector2D(X, Y))
				continue;

			if (!Tiles[Y * GridSize.X + X])
			{
				CellTriangles.Add(CellVertices.Num() + 0);
				CellTriangles.Add(CellVertices.Num() + 3);
				CellTriangles.Add(CellVertices.Num() + 2);
				CellTriangles.Add(CellVertices.Num() + 2);
				CellTriangles.Add(CellVertices.Num() + 1);
				CellTriangles.Add(CellVertices.Num() + 0);

				CellVertices.Add(FVector(
					(X * CellSize.X) - (CellSize.X / 2) + (UseSpacing / 2),
					(Y * CellSize.Y) - (CellSize.Y / 2) + (UseSpacing / 2),
					0));
				CellVertices.Add(FVector(
					(X * CellSize.X) + (CellSize.X / 2) - (UseSpacing / 2),
					(Y * CellSize.Y) - (CellSize.Y / 2) + (UseSpacing / 2),
					0));
				CellVertices.Add(FVector(
					(X * CellSize.X) + (CellSize.X / 2) - (UseSpacing / 2),
					(Y * CellSize.Y) + (CellSize.Y / 2) - (UseSpacing / 2),
					0));
				CellVertices.Add(FVector(
					(X * CellSize.X) - (CellSize.X / 2) + (UseSpacing / 2),
					(Y * CellSize.Y) + (CellSize.Y / 2) - (UseSpacing / 2),
					0));
			}
			else
			{
				PreviewCellTriangles.Add(PreviewCellVertices.Num() + 0);
				PreviewCellTriangles.Add(PreviewCellVertices.Num() + 3);
				PreviewCellTriangles.Add(PreviewCellVertices.Num() + 2);
				PreviewCellTriangles.Add(PreviewCellVertices.Num() + 2);
				PreviewCellTriangles.Add(PreviewCellVertices.Num() + 1);
				PreviewCellTriangles.Add(PreviewCellVertices.Num() + 0);

				PreviewCellVertices.Add(FVector(
					(X * CellSize.X) - (CellSize.X / 2) + (UseSpacing / 2),
					(Y * CellSize.Y) - (CellSize.Y / 2) + (UseSpacing / 2),
					0));
				PreviewCellVertices.Add(FVector(
					(X * CellSize.X) + (CellSize.X / 2) - (UseSpacing / 2),
					(Y * CellSize.Y) - (CellSize.Y / 2) + (UseSpacing / 2),
					0));
				PreviewCellVertices.Add(FVector(
					(X * CellSize.X) + (CellSize.X / 2) - (UseSpacing / 2),
					(Y * CellSize.Y) + (CellSize.Y / 2) - (UseSpacing / 2),
					0));
				PreviewCellVertices.Add(FVector(
					(X * CellSize.X) - (CellSize.X / 2) + (UseSpacing / 2),
					(Y * CellSize.Y) + (CellSize.Y / 2) - (UseSpacing / 2),
					0));
			}
		}
	}

	CellMesh->CreateMeshSection(0, CellVertices, CellTriangles, TArray<FVector>(), TArray<FVector2D>(), TArray<FColor>(), TArray<FProcMeshTangent>(), true);
	CellPreviewMesh->CreateMeshSection(0, PreviewCellVertices, PreviewCellTriangles, TArray<FVector>(), TArray<FVector2D>(), TArray<FColor>(), TArray<FProcMeshTangent>(), true);
}
FVector UExtendedViewportWidget::GetCursorWorldDirection(FVector2D CursorPosition)
{
	FVector WorldPosition(0);
    FVector WorldDirection(0);

	FVector2f ViewportPosition = ViewportWidget->Viewport->GetCachedGeometry().GetAbsolutePosition();

	FSceneView::DeprojectScreenToWorld(CursorPosition - (FVector2D)ViewportPosition, FIntRect(0, 0, ViewportWidget->Viewport->GetSizeXY().X, ViewportWidget->Viewport->GetSizeXY().Y), GetViewProjectionMatrix().Inverse(), WorldPosition, WorldDirection);

    return WorldDirection;
}
A road planning system for creating main roads and connecting points between them using sub-roads. Each road maintains information about its connected roads and provides functionality for querying connections at different points along the road. The system is currently under development, with some known issues still being addressed.
class FTrackSplineComponentVisualizerCommands : public TCommands< FTrackSplineComponentVisualizerCommands>
{
public:
	FTrackSplineComponentVisualizerCommands() : TCommands< FTrackSplineComponentVisualizerCommands>
		(
			"TrackSplineComponentVisualizer",
			LOCTEXT("TrackSplineComponentVisualizer", "TrackSpline Component Visualizer"),
			NAME_None,
			FAppStyle::GetAppStyleSetName()
		)
	{
	}

	virtual void RegisterCommands() override
	{
		UI_COMMAND(AddSubSpline, "Add Subspline", "Add a subspline starting from this point. Click on another point to set the destination of subspline", EUserInterfaceActionType::Button, FInputChord());
	}

public:
	TSharedPtr<FUICommandInfo> AddSubSpline;
};
void FTrackSplineComponentVisualizer::GenerateContextMenuSections(FMenuBuilder& InMenuBuilder) const
{
	FSplineComponentVisualizer::GenerateContextMenuSections(InMenuBuilder);

	InMenuBuilder.BeginSection("TrackSpline", LOCTEXT("TrackSpline", "TrackSpline"));
	{
		USplineComponent* SplineComp = GetEditedSplineComponent();
		if (SplineComp != nullptr)
		{
			int SelectedKey = SelectionState->GetLastKeyIndexSelected();
			if (SelectedKey != INDEX_NONE && SelectionState->GetSelectedSegmentIndex() == INDEX_NONE)
			{
				check(SelectedKey >= 0);
				check(SelectedKey < SplineComp->GetNumberOfSplinePoints());
				InMenuBuilder.AddMenuEntry(FTrackSplineComponentVisualizerCommands::Get().AddSubSpline);
			}
		}
	}
	InMenuBuilder.EndSection();
}
void FTrackSplineComponentVisualizer::AddSubSpline()
{
	USplineComponent* SplineComp = Cast<UTrackSplineComponent>(GetEditedSplineComponent());
	int SelectedKey = SelectionState->GetLastKeyIndexSelected();
	if (SelectedKey != INDEX_NONE)
	{
		check(SelectedKey >= 0);
		check(SelectedKey < SplineComp->GetNumberOfSplinePoints());

		StartParent = Cast<UTrackSplineComponent>(SplineComp);
		StartPointIndex = SelectedKey;
	}

	GEditor->RedrawLevelEditingViewports(true);
}
bool FTrackSplineComponentVisualizer::VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click)
{
	ResetTempModes();

	FTypedElementHandle SelectedComponentHandle = VisProxy->GetElementHandle();
	UTypedElementSelectionSet* ElementSelectionSet = (InViewportClient && InViewportClient->GetModeTools()) ? InViewportClient->GetModeTools()->GetEditorSelectionSet() : nullptr;

	if (!ElementSelectionSet->IsElementSelected(SelectedComponentHandle, FTypedElementIsSelectedOptions().SetAllowIndirect(true)))
	{
		GEditor->DeselectAllSurfaces();
		ElementSelectionSet->ClearSelection(FTypedElementSelectionOptions()
			.SetAllowHidden(true)
			.SetWarnIfLocked(true));
		ElementSelectionSet->NotifyPendingChanges();
	}

	bool bVisProxyClickHandled = false;
	UTrackSplineComponent* ClickedComponent = nullptr;

	if (VisProxy && VisProxy->Component.IsValid())
	{
		ClickedComponent = CastChecked<UTrackSplineComponent>(const_cast<UActorComponent*>(VisProxy->Component.Get()));
	}
	else
		return false;

	if (StartParent.IsValid() && StartPointIndex != INDEX_NONE)
	{
		if (FString(VisProxy->GetType()->GetName()) == TEXT("HSplineKeyProxy"))
		{
			ResetTempModes();
			if (ClickedComponent->IsA(UTrackSplineComponent::StaticClass()))
			{
				HSplineKeyProxy* KeyProxy = (HSplineKeyProxy*)VisProxy;

				int EndPointIndex = INDEX_NONE;

				if (ClickedComponent == StartParent.Get())
				{
					if (KeyProxy->KeyIndex != StartPointIndex)
					{
						EndPointIndex = KeyProxy->KeyIndex;

						bVisProxyClickHandled = true;
					}
					else
					{
						StartParent.Reset();
						StartPointIndex = INDEX_NONE;
					}
				}
				else
				{
					EndPointIndex = KeyProxy->KeyIndex;

					bVisProxyClickHandled = true;
				}

				if (bVisProxyClickHandled)
				{
					if (AActor* Owner = StartParent.Get()->GetOwner())
					{
						Owner->Modify();

						int id = Cast<APathMap>(Owner)->GetSubSplineCount();

						UTrackSplineComponent* NewSubspline = NewObject<UTrackSplineComponent>(Owner, *FString("SubSpline" + FString::FromInt(id)));

						Cast<APathMap>(Owner)->IncreaseSubSpline();

						Owner->AddInstanceComponent(NewSubspline);
						NewSubspline->RegisterComponent();
						NewSubspline->AttachToComponent(Owner->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);

						int NumOfPoints = NewSubspline->GetNumberOfSplinePoints();
						if (NumOfPoints < 2)
						{
							TArray<FSplinePoint> AdditionalPoints;
							AdditionalPoints.Init(FSplinePoint(), 2 - NumOfPoints);
							NewSubspline->AddPoints(AdditionalPoints, false);
						}

						NewSubspline->SetLocationAtSplinePoint(0, StartParent->GetLocationAtSplinePoint(StartPointIndex, ESplineCoordinateSpace::World), ESplineCoordinateSpace::World, false);
						NewSubspline->SetLocationAtSplinePoint(1, ClickedComponent->GetLocationAtSplinePoint(EndPointIndex, ESplineCoordinateSpace::World), ESplineCoordinateSpace::World, false);
						NewSubspline->UpdateSpline();

						StartParent->AddConnectedSpline(NewSubspline, StartPointIndex, 0);
						NewSubspline->AddConnectedSpline(StartParent.Get(), 0, StartPointIndex);
						ClickedComponent->AddConnectedSpline(NewSubspline, EndPointIndex, 1);
						NewSubspline->AddConnectedSpline(ClickedComponent, 1, EndPointIndex);

						StartParent.Reset();
						StartPointIndex = INDEX_NONE;
					}

					GEditor->RedrawLevelEditingViewports(true);

					return true;
				}
			}
		}
	}

	return FSplineComponentVisualizer::VisProxyHandleClick(InViewportClient, VisProxy, Click);
}
class UTrackSplineComponent;

USTRUCT()
struct FTrackConnectionData
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere)
	UTrackSplineComponent* OtherSpline;

	UPROPERTY(EditAnywhere)
	int EndPointIndex;
};
void UTrackSplineComponent::AddConnectedSpline(UTrackSplineComponent* NewSpline, int StartPointIndex, int EndPointIndex)
{
	FConnectionDataContainer& Container = ConnectedSplines.FindOrAdd(StartPointIndex);
	FTrackConnectionData NewConnection;
	NewConnection.OtherSpline = NewSpline;
	NewConnection.EndPointIndex = EndPointIndex;
	Container.Connections.Add(NewConnection);
}

void UTrackSplineComponent::RemoveConnectedSpline(UTrackSplineComponent* SplineToRemove)
{
	TArray<int> RemoveList;
	for(auto& Pair : ConnectedSplines)
	{
		FConnectionDataContainer& Container = Pair.Value;
		Container.Connections.RemoveAll([SplineToRemove](const FTrackConnectionData& Connection)
		{
			return Connection.OtherSpline == SplineToRemove;
		});

		if(Container.Connections.IsEmpty())
			RemoveList.Add(Pair.Key);
	}

	for(auto& Key : RemoveList)
	{
		ConnectedSplines.Remove(Key);
	}
}

bool UTrackSplineComponent::HaveConnectionAtPoint(int PointIndex)
{
	if(ConnectedSplines.Contains(PointIndex))
	{
		return true;
	}

	return false;
}

FConnectionDataContainer* UTrackSplineComponent::GetConnectionDataAtPoint(int PointIndex)
{
	if(ConnectedSplines.Contains(PointIndex))
	{
		return &ConnectedSplines[PointIndex];
	}

	return nullptr;
}

TRACKSYSTEM_API EForwardOrder UTrackSplineComponent::GetForwardOrder()
{
	return IndexOrderOfForwardDirection;
}

void UTrackSplineComponent::OnComponentDestroyed(bool bDestroyingHierarchy)
{
	Super::OnComponentDestroyed(bDestroyingHierarchy);

	UWorld* World = GetWorld();
	if (GExitPurge || !World || World->bIsTearingDown)
	{
		return;
	}

	for(auto& Pair : ConnectedSplines)
	{
		FConnectionDataContainer& Container = Pair.Value;
		for(FTrackConnectionData& Connection : Container.Connections)
		{
			if(Connection.OtherSpline)
			{
				Connection.OtherSpline->RemoveConnectedSpline(this);
			}
		}
	}

	ConnectedSplines.Empty();
}
APathMap::APathMap()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = false;

	SceneRoot = CreateDefaultSubobject<USceneComponent>("SceneRoot");
	SetRootComponent(SceneRoot);
}

#if WITH_EDITOR
void APathMap::AddMainSpline()
{
	UTrackSplineComponent* NewSpline = NewObject<UTrackSplineComponent>(this, *FString("MainSpline" + FString::FromInt(NumOfMainSplines)));

	AddInstanceComponent(NewSpline);
	NewSpline->RegisterComponent();
	NewSpline->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
	NewSpline->SetIsMainSpline(true);

	NumOfMainSplines++;

	GEditor->RedrawLevelEditingViewports(true);
}
#endif

void APathMap::IncreaseSubSpline()
{
	NumOfSubSplines++;
}