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
|
shelter through the server-authoritative placement component. Fully modular
|
||||||
wall-by-wall building stays deferred to `0.2.E Permanent Structures`.
|
wall-by-wall building stays deferred to `0.2.E Permanent Structures`.
|
||||||
- [x] Create build placement mode.
|
- [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 placement validation.
|
||||||
- [x] Add basic shelter piece.
|
- [x] Add basic shelter piece.
|
||||||
- [ ] Add wall piece if needed.
|
- [ ] 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
|
as inventory parts and then combined into a single placeable primitive
|
||||||
shelter actor. Fully modular wall-by-wall construction is deferred to
|
shelter actor. Fully modular wall-by-wall construction is deferred to
|
||||||
permanent structures so the first survival loop remains reliable.
|
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.
|
- Persistence layer: save/load contracts for player and world state.
|
||||||
|
|
||||||
Blueprints can compose and expose these systems, but core replicated behavior
|
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 "AgrarianBuildingPlacementComponent.h"
|
||||||
#include "AgrarianInventoryComponent.h"
|
#include "AgrarianInventoryComponent.h"
|
||||||
|
#include "DrawDebugHelpers.h"
|
||||||
#include "GameFramework/Pawn.h"
|
#include "GameFramework/Pawn.h"
|
||||||
#include "GameFramework/Controller.h"
|
#include "GameFramework/Controller.h"
|
||||||
#include "Engine/World.h"
|
#include "Engine/World.h"
|
||||||
|
|
||||||
UAgrarianBuildingPlacementComponent::UAgrarianBuildingPlacementComponent()
|
UAgrarianBuildingPlacementComponent::UAgrarianBuildingPlacementComponent()
|
||||||
{
|
{
|
||||||
PrimaryComponentTick.bCanEverTick = false;
|
PrimaryComponentTick.bCanEverTick = true;
|
||||||
SetIsReplicatedByDefault(true);
|
SetIsReplicatedByDefault(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,6 +56,16 @@ bool UAgrarianBuildingPlacementComponent::GetPlacementPreview(FTransform& OutTra
|
|||||||
return true;
|
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
|
bool UAgrarianBuildingPlacementComponent::CanPlaceAtTransform(TSubclassOf<AActor> BuildClass, const FTransform& PlacementTransform, FText& FailureReason) const
|
||||||
{
|
{
|
||||||
if (!BuildClass)
|
if (!BuildClass)
|
||||||
@@ -148,6 +159,38 @@ void UAgrarianBuildingPlacementComponent::ServerPlaceBuildable_Implementation(TS
|
|||||||
OnBuildPlaced.Broadcast(PlacedActor);
|
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
|
bool UAgrarianBuildingPlacementComponent::HasPlacementCost(FText& FailureReason) const
|
||||||
{
|
{
|
||||||
const UAgrarianInventoryComponent* Inventory = GetOwner() ? GetOwner()->FindComponentByClass<UAgrarianInventoryComponent>() : nullptr;
|
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(FAgrarianBuildPlacedSignature, AActor*, PlacedActor);
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAgrarianBuildPlacementFailedSignature, FText, Reason);
|
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))
|
UCLASS(ClassGroup = (Agrarian), BlueprintType, Blueprintable, meta = (BlueprintSpawnableComponent))
|
||||||
class UAgrarianBuildingPlacementComponent : public UActorComponent
|
class UAgrarianBuildingPlacementComponent : public UActorComponent
|
||||||
@@ -24,6 +25,9 @@ public:
|
|||||||
UPROPERTY(BlueprintAssignable, Category = "Agrarian|Building")
|
UPROPERTY(BlueprintAssignable, Category = "Agrarian|Building")
|
||||||
FAgrarianBuildPlacementFailedSignature OnBuildPlacementFailed;
|
FAgrarianBuildPlacementFailedSignature OnBuildPlacementFailed;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Agrarian|Building")
|
||||||
|
FAgrarianBuildPreviewUpdatedSignature OnBuildPreviewUpdated;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Building")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Building")
|
||||||
TSubclassOf<AActor> ActiveBuildClass;
|
TSubclassOf<AActor> ActiveBuildClass;
|
||||||
|
|
||||||
@@ -45,12 +49,30 @@ public:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Building", meta = (EditCondition = "bSnapToGrid", ClampMin = "1"))
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Agrarian|Building", meta = (EditCondition = "bSnapToGrid", ClampMin = "1"))
|
||||||
float GridSize = 50.0f;
|
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")
|
UFUNCTION(BlueprintCallable, Category = "Agrarian|Building")
|
||||||
void SetActiveBuildable(TSubclassOf<AActor> BuildClass, const TArray<FAgrarianItemStack>& Cost);
|
void SetActiveBuildable(TSubclassOf<AActor> BuildClass, const TArray<FAgrarianItemStack>& Cost);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Building")
|
UFUNCTION(BlueprintCallable, Category = "Agrarian|Building")
|
||||||
bool GetPlacementPreview(FTransform& OutTransform, FText& FailureReason) const;
|
bool GetPlacementPreview(FTransform& OutTransform, FText& FailureReason) const;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Agrarian|Building")
|
||||||
|
bool GetPlacementPreviewState(FTransform& OutTransform, FText& FailureReason) const;
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "Agrarian|Building")
|
UFUNCTION(BlueprintCallable, Category = "Agrarian|Building")
|
||||||
bool CanPlaceAtTransform(TSubclassOf<AActor> BuildClass, const FTransform& PlacementTransform, FText& FailureReason) const;
|
bool CanPlaceAtTransform(TSubclassOf<AActor> BuildClass, const FTransform& PlacementTransform, FText& FailureReason) const;
|
||||||
|
|
||||||
@@ -60,6 +82,8 @@ public:
|
|||||||
UFUNCTION(Server, Reliable, BlueprintCallable, Category = "Agrarian|Building")
|
UFUNCTION(Server, Reliable, BlueprintCallable, Category = "Agrarian|Building")
|
||||||
void ServerPlaceBuildable(TSubclassOf<AActor> BuildClass, FTransform PlacementTransform);
|
void ServerPlaceBuildable(TSubclassOf<AActor> BuildClass, FTransform PlacementTransform);
|
||||||
|
|
||||||
|
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool HasPlacementCost(FText& FailureReason) const;
|
bool HasPlacementCost(FText& FailureReason) const;
|
||||||
void ConsumePlacementCost();
|
void ConsumePlacementCost();
|
||||||
|
|||||||
Reference in New Issue
Block a user