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