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.




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);
}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;
}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++;
}