Add building ghost preview
This commit is contained in:
@@ -610,7 +610,9 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
|
||||
shelter through the server-authoritative placement component. Fully modular
|
||||
wall-by-wall building stays deferred to `0.2.E Permanent Structures`.
|
||||
- [x] Create build placement mode.
|
||||
- [~] Add ghost preview.
|
||||
- [x] Add ghost preview. The building placement component now exposes preview
|
||||
validity/transform state and draws an assetless green/red wireframe ghost
|
||||
footprint at the snapped placement location for MVP development builds.
|
||||
- [x] Add placement validation.
|
||||
- [x] Add basic shelter piece.
|
||||
- [ ] Add wall piece if needed.
|
||||
|
||||
@@ -86,6 +86,11 @@ Early runtime systems should remain small and explicit:
|
||||
as inventory parts and then combined into a single placeable primitive
|
||||
shelter actor. Fully modular wall-by-wall construction is deferred to
|
||||
permanent structures so the first survival loop remains reliable.
|
||||
- `UAgrarianBuildingPlacementComponent` owns the MVP placement preview. It
|
||||
traces from the player view, snaps to the configured grid, validates distance
|
||||
and collision, broadcasts Blueprint-readable preview state, and draws a
|
||||
green/red wireframe ghost footprint in development builds so placement can be
|
||||
tested before final mesh/material ghost assets exist.
|
||||
- Persistence layer: save/load contracts for player and world state.
|
||||
|
||||
Blueprints can compose and expose these systems, but core replicated behavior
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
HEADER = ROOT / "Source" / "AgrarianGame" / "AgrarianBuildingPlacementComponent.h"
|
||||
SOURCE = ROOT / "Source" / "AgrarianGame" / "AgrarianBuildingPlacementComponent.cpp"
|
||||
ROADMAP = ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md"
|
||||
TECHNICAL_DESIGN = ROOT / "Docs" / "TechnicalDesignDocument.md"
|
||||
|
||||
|
||||
def compact(path: Path) -> str:
|
||||
return " ".join(path.read_text(encoding="utf-8").split())
|
||||
|
||||
|
||||
def require(path: Path, text: str) -> None:
|
||||
data = compact(path)
|
||||
if text not in data:
|
||||
raise SystemExit(f"FAIL: {path.relative_to(ROOT)} missing required text: {text}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
require(HEADER, "FAgrarianBuildPreviewUpdatedSignature")
|
||||
require(HEADER, "OnBuildPreviewUpdated")
|
||||
require(HEADER, "bShowGhostPreview")
|
||||
require(HEADER, "GetPlacementPreviewState")
|
||||
require(HEADER, "TickComponent")
|
||||
require(SOURCE, "DrawDebugBox")
|
||||
require(SOURCE, "ValidGhostPreviewColor")
|
||||
require(SOURCE, "InvalidGhostPreviewColor")
|
||||
require(SOURCE, "OnBuildPreviewUpdated.Broadcast")
|
||||
require(ROADMAP, "[x] Add ghost preview.")
|
||||
require(TECHNICAL_DESIGN, "green/red wireframe ghost footprint")
|
||||
print("PASS: building ghost preview support is present.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
#include "AgrarianBuildingPlacementComponent.h"
|
||||
#include "AgrarianInventoryComponent.h"
|
||||
#include "DrawDebugHelpers.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "GameFramework/Controller.h"
|
||||
#include "Engine/World.h"
|
||||
|
||||
UAgrarianBuildingPlacementComponent::UAgrarianBuildingPlacementComponent()
|
||||
{
|
||||
PrimaryComponentTick.bCanEverTick = false;
|
||||
PrimaryComponentTick.bCanEverTick = true;
|
||||
SetIsReplicatedByDefault(true);
|
||||
}
|
||||
|
||||
@@ -55,6 +56,16 @@ bool UAgrarianBuildingPlacementComponent::GetPlacementPreview(FTransform& OutTra
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UAgrarianBuildingPlacementComponent::GetPlacementPreviewState(FTransform& OutTransform, FText& FailureReason) const
|
||||
{
|
||||
if (!GetPlacementPreview(OutTransform, FailureReason))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return CanPlaceAtTransform(ActiveBuildClass, OutTransform, FailureReason);
|
||||
}
|
||||
|
||||
bool UAgrarianBuildingPlacementComponent::CanPlaceAtTransform(TSubclassOf<AActor> BuildClass, const FTransform& PlacementTransform, FText& FailureReason) const
|
||||
{
|
||||
if (!BuildClass)
|
||||
@@ -148,6 +159,38 @@ void UAgrarianBuildingPlacementComponent::ServerPlaceBuildable_Implementation(TS
|
||||
OnBuildPlaced.Broadcast(PlacedActor);
|
||||
}
|
||||
|
||||
void UAgrarianBuildingPlacementComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
|
||||
if (!bShowGhostPreview || !ActiveBuildClass || !GetWorld())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FTransform PreviewTransform;
|
||||
FText FailureReason;
|
||||
const bool bCanPlace = GetPlacementPreviewState(PreviewTransform, FailureReason);
|
||||
const FColor PreviewColor = bCanPlace ? ValidGhostPreviewColor : InvalidGhostPreviewColor;
|
||||
const FVector PreviewExtent(
|
||||
FMath::Max(PlacementProbeRadius, 25.0f),
|
||||
FMath::Max(PlacementProbeRadius, 25.0f),
|
||||
FMath::Max(PlacementProbeRadius * 0.5f, 25.0f));
|
||||
|
||||
DrawDebugBox(
|
||||
GetWorld(),
|
||||
PreviewTransform.GetLocation() + FVector(0.0f, 0.0f, PreviewExtent.Z),
|
||||
PreviewExtent,
|
||||
PreviewTransform.GetRotation(),
|
||||
PreviewColor,
|
||||
false,
|
||||
GhostPreviewLifetimeSeconds,
|
||||
0,
|
||||
GhostPreviewLineThickness);
|
||||
|
||||
OnBuildPreviewUpdated.Broadcast(bCanPlace, PreviewTransform, FailureReason);
|
||||
}
|
||||
|
||||
bool UAgrarianBuildingPlacementComponent::HasPlacementCost(FText& FailureReason) const
|
||||
{
|
||||
const UAgrarianInventoryComponent* Inventory = GetOwner() ? GetOwner()->FindComponentByClass<UAgrarianInventoryComponent>() : nullptr;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAgrarianBuildPlacedSignature, AActor*, PlacedActor);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAgrarianBuildPlacementFailedSignature, FText, Reason);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FAgrarianBuildPreviewUpdatedSignature, bool, bCanPlace, FTransform, PreviewTransform, FText, FailureReason);
|
||||
|
||||
UCLASS(ClassGroup = (Agrarian), BlueprintType, Blueprintable, meta = (BlueprintSpawnableComponent))
|
||||
class UAgrarianBuildingPlacementComponent : public UActorComponent
|
||||
@@ -24,6 +25,9 @@ public:
|
||||
UPROPERTY(BlueprintAssignable, Category = "Agrarian|Building")
|
||||
FAgrarianBuildPlacementFailedSignature OnBuildPlacementFailed;
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "Agrarian|Building")
|
||||
FAgrarianBuildPreviewUpdatedSignature OnBuildPreviewUpdated;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Building")
|
||||
TSubclassOf<AActor> ActiveBuildClass;
|
||||
|
||||
@@ -45,12 +49,30 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Building", meta = (EditCondition = "bSnapToGrid", ClampMin = "1"))
|
||||
float GridSize = 50.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Building|Preview")
|
||||
bool bShowGhostPreview = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Building|Preview", meta = (ClampMin = "0"))
|
||||
float GhostPreviewLifetimeSeconds = 0.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Building|Preview", meta = (ClampMin = "0"))
|
||||
float GhostPreviewLineThickness = 2.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Building|Preview")
|
||||
FColor ValidGhostPreviewColor = FColor(70, 220, 120);
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Building|Preview")
|
||||
FColor InvalidGhostPreviewColor = FColor(230, 80, 65);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Building")
|
||||
void SetActiveBuildable(TSubclassOf<AActor> BuildClass, const TArray<FAgrarianItemStack>& Cost);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Building")
|
||||
bool GetPlacementPreview(FTransform& OutTransform, FText& FailureReason) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Building")
|
||||
bool GetPlacementPreviewState(FTransform& OutTransform, FText& FailureReason) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Building")
|
||||
bool CanPlaceAtTransform(TSubclassOf<AActor> BuildClass, const FTransform& PlacementTransform, FText& FailureReason) const;
|
||||
|
||||
@@ -60,6 +82,8 @@ public:
|
||||
UFUNCTION(Server, Reliable, BlueprintCallable, Category = "Agrarian|Building")
|
||||
void ServerPlaceBuildable(TSubclassOf<AActor> BuildClass, FTransform PlacementTransform);
|
||||
|
||||
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||
|
||||
protected:
|
||||
bool HasPlacementCost(FText& FailureReason) const;
|
||||
void ConsumePlacementCost();
|
||||
|
||||
Reference in New Issue
Block a user