Compare commits
7 Commits
5affbc7d0d
...
fb95d3b821
| Author | SHA1 | Date | |
|---|---|---|---|
| fb95d3b821 | |||
| 16fdcb55da | |||
| 1f11b79222 | |||
| 6c39b5888f | |||
| 24e50ab919 | |||
| 21e39ad3fa | |||
| c04bc2149b |
31
Unreal/Plugins/PS_BehaviorEditor/PS_BehaviorEditor.uplugin
Normal file
31
Unreal/Plugins/PS_BehaviorEditor/PS_BehaviorEditor.uplugin
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"FileVersion": 3,
|
||||
"Version": 1,
|
||||
"VersionName": "1.0",
|
||||
"FriendlyName": "PS Behavior Editor",
|
||||
"Description": "Bridge plugin: AI Behavior splines editable via PS_Editor runtime editor.",
|
||||
"Category": "PS Tools",
|
||||
"CreatedBy": "Asterion",
|
||||
"EnabledByDefault": false,
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "PS_BehaviorEditor",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default"
|
||||
}
|
||||
],
|
||||
"Plugins": [
|
||||
{
|
||||
"Name": "PS_Editor",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "PS_AI_Behavior",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "ProceduralMeshComponent",
|
||||
"Enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class PS_BehaviorEditor : ModuleRules
|
||||
{
|
||||
public PS_BehaviorEditor(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(new string[]
|
||||
{
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"ProceduralMeshComponent",
|
||||
"PS_Editor",
|
||||
"PS_AI_Behavior"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "PS_BehaviorEditor.h"
|
||||
|
||||
IMPLEMENT_MODULE(FPS_BehaviorEditorModule, PS_BehaviorEditor)
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
class FPS_BehaviorEditorModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
virtual void StartupModule() override {}
|
||||
virtual void ShutdownModule() override {}
|
||||
};
|
||||
@@ -0,0 +1,531 @@
|
||||
#include "PS_BehaviorEditor_AISpline.h"
|
||||
#include "PS_Editor_SpawnableComponent.h"
|
||||
#include "PS_Editor_EditableComponent.h"
|
||||
#include "Components/SplineComponent.h"
|
||||
#include "ProceduralMeshComponent.h"
|
||||
#include "Components/StaticMeshComponent.h"
|
||||
#include "Engine/StaticMesh.h"
|
||||
#include "Materials/MaterialInstanceDynamic.h"
|
||||
#include "PS_AI_Behavior_SplineNetwork.h"
|
||||
|
||||
APS_BehaviorEditor_AISpline::APS_BehaviorEditor_AISpline()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = false;
|
||||
|
||||
// SplineComp is already created by APS_AI_Behavior_SplinePath as root.
|
||||
// Clear default editor-placed points so we start empty for runtime placement.
|
||||
if (SplineComp)
|
||||
{
|
||||
SplineComp->ClearSplinePoints(false);
|
||||
SplineComp->SetClosedLoop(false);
|
||||
}
|
||||
|
||||
// Line mesh
|
||||
SplineMeshComp = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("SplineMesh"));
|
||||
SplineMeshComp->SetupAttachment(RootComponent);
|
||||
SplineMeshComp->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
||||
SplineMeshComp->SetCollisionResponseToAllChannels(ECR_Ignore);
|
||||
SplineMeshComp->SetCollisionResponseToChannel(ECC_Camera, ECR_Block);
|
||||
SplineMeshComp->bUseComplexAsSimpleCollision = true;
|
||||
SplineMeshComp->SetCastShadow(false);
|
||||
SplineMeshComp->SetRenderCustomDepth(true);
|
||||
SplineMeshComp->SetCustomDepthStencilValue(2);
|
||||
|
||||
// Handle parent
|
||||
HandleRoot = CreateDefaultSubobject<USceneComponent>(TEXT("HandleRoot"));
|
||||
HandleRoot->SetupAttachment(RootComponent);
|
||||
|
||||
// Spawnable: makes this actor appear in the editor catalogue
|
||||
SpawnableComp = CreateDefaultSubobject<UPS_Editor_SpawnableComponent>(TEXT("PS_Editor_Spawnable"));
|
||||
SpawnableComp->DisplayName = TEXT("AI Spline");
|
||||
SpawnableComp->Category = TEXT("AI");
|
||||
|
||||
// Editable: exposes AI + visual properties in the runtime property panel
|
||||
EditableComp = CreateDefaultSubobject<UPS_Editor_EditableComponent>(TEXT("PS_Editor_Editable"));
|
||||
EditableComp->EditableProperties.Add({FName(TEXT("SplineCategory"))});
|
||||
EditableComp->EditableProperties.Add({FName(TEXT("bBidirectional"))});
|
||||
EditableComp->EditableProperties.Add({FName(TEXT("SplineWalkSpeed"))});
|
||||
EditableComp->EditableProperties.Add({FName(TEXT("Priority"))});
|
||||
EditableComp->EditableProperties.Add({FName(TEXT("SplineColor"))});
|
||||
EditableComp->EditableProperties.Add({FName(TEXT("SplineSelectedColor"))});
|
||||
}
|
||||
|
||||
void APS_BehaviorEditor_AISpline::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// Gizmo material (no depth test) for handles and selected state
|
||||
UMaterialInterface* GizmoMat = LoadObject<UMaterialInterface>(
|
||||
nullptr, TEXT("/PS_Editor/M_PS_Editor_Gizmo.M_PS_Editor_Gizmo"));
|
||||
|
||||
// Opaque depth-tested material for spline line
|
||||
UMaterialInterface* SplineLineMat = LoadObject<UMaterialInterface>(
|
||||
nullptr, TEXT("/PS_Editor/M_PS_Editor_SplineLine.M_PS_Editor_SplineLine"));
|
||||
if (!SplineLineMat)
|
||||
{
|
||||
SplineLineMat = LoadObject<UMaterialInterface>(nullptr, TEXT("/PS_Editor/M_PS_Editor_SplineLine"));
|
||||
}
|
||||
|
||||
if (SplineLineMat)
|
||||
{
|
||||
SplineMat = UMaterialInstanceDynamic::Create(SplineLineMat, this);
|
||||
SplineMat->SetVectorParameterValue(TEXT("Color"), SplineColor);
|
||||
}
|
||||
|
||||
if (GizmoMat)
|
||||
{
|
||||
SplineMatSelected = UMaterialInstanceDynamic::Create(GizmoMat, this);
|
||||
SplineMatSelected->SetVectorParameterValue(TEXT("Color"), SplineSelectedColor);
|
||||
|
||||
HandleMat = UMaterialInstanceDynamic::Create(GizmoMat, this);
|
||||
HandleMat->SetVectorParameterValue(TEXT("Color"), FLinearColor(1.0f, 1.0f, 1.0f));
|
||||
|
||||
HandleHighlightMat = UMaterialInstanceDynamic::Create(GizmoMat, this);
|
||||
HandleHighlightMat->SetVectorParameterValue(TEXT("Color"), FLinearColor(1.0f, 1.0f, 0.0f));
|
||||
|
||||
CenterCubeMat = UMaterialInstanceDynamic::Create(GizmoMat, this);
|
||||
CenterCubeMat->SetVectorParameterValue(TEXT("Color"), SplineColor);
|
||||
|
||||
SplineMatOccluded = UMaterialInstanceDynamic::Create(GizmoMat, this);
|
||||
SplineMatOccluded->SetVectorParameterValue(TEXT("Color"), FLinearColor(0.3f, 0.3f, 0.3f));
|
||||
}
|
||||
|
||||
// Center cube (selection handle)
|
||||
CenterCube = NewObject<UStaticMeshComponent>(this);
|
||||
CenterCube->SetupAttachment(GetRootComponent());
|
||||
CenterCube->RegisterComponent();
|
||||
UStaticMesh* CubeMesh = LoadObject<UStaticMesh>(nullptr, TEXT("/Engine/BasicShapes/Cube.Cube"));
|
||||
if (CubeMesh)
|
||||
{
|
||||
CenterCube->SetStaticMesh(CubeMesh);
|
||||
}
|
||||
CenterCube->SetWorldScale3D(FVector(CenterCubeScale));
|
||||
CenterCube->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
||||
CenterCube->SetCollisionResponseToAllChannels(ECR_Ignore);
|
||||
CenterCube->SetCollisionResponseToChannel(ECC_Camera, ECR_Block);
|
||||
CenterCube->SetCastShadow(false);
|
||||
if (CenterCubeMat)
|
||||
{
|
||||
CenterCube->SetMaterial(0, CenterCubeMat);
|
||||
}
|
||||
|
||||
// Rebuild SplineNetwork next tick (all splines from scene load will be ready)
|
||||
GetWorld()->GetTimerManager().SetTimerForNextTick([WeakWorld = TWeakObjectPtr<UWorld>(GetWorld())]()
|
||||
{
|
||||
if (UWorld* World = WeakWorld.Get())
|
||||
{
|
||||
if (UPS_AI_Behavior_SplineNetwork* Network = World->GetSubsystem<UPS_AI_Behavior_SplineNetwork>())
|
||||
{
|
||||
Network->RebuildNetwork();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ---- Point manipulation ----
|
||||
|
||||
void APS_BehaviorEditor_AISpline::ScheduleNetworkRebuild()
|
||||
{
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
World->GetTimerManager().ClearTimer(NetworkRebuildTimer);
|
||||
World->GetTimerManager().SetTimer(NetworkRebuildTimer, [WeakWorld = TWeakObjectPtr<UWorld>(World)]()
|
||||
{
|
||||
if (UWorld* W = WeakWorld.Get())
|
||||
{
|
||||
if (UPS_AI_Behavior_SplineNetwork* Network = W->GetSubsystem<UPS_AI_Behavior_SplineNetwork>())
|
||||
{
|
||||
Network->RebuildNetwork();
|
||||
}
|
||||
}
|
||||
}, 0.5f, false);
|
||||
}
|
||||
}
|
||||
|
||||
void APS_BehaviorEditor_AISpline::AddPoint(const FVector& WorldPosition)
|
||||
{
|
||||
SplineComp->AddSplinePoint(WorldPosition, ESplineCoordinateSpace::World, true);
|
||||
PointHandles.Add(CreatePointHandle(WorldPosition));
|
||||
UpdateVisualization();
|
||||
ScheduleNetworkRebuild();
|
||||
}
|
||||
|
||||
void APS_BehaviorEditor_AISpline::MovePoint(int32 Index, const FVector& NewWorldPosition)
|
||||
{
|
||||
if (Index < 0 || Index >= GetNumPoints()) return;
|
||||
SplineComp->SetLocationAtSplinePoint(Index, NewWorldPosition, ESplineCoordinateSpace::World, true);
|
||||
if (PointHandles.IsValidIndex(Index) && PointHandles[Index])
|
||||
{
|
||||
PointHandles[Index]->SetWorldLocation(NewWorldPosition);
|
||||
}
|
||||
UpdateVisualization();
|
||||
}
|
||||
|
||||
void APS_BehaviorEditor_AISpline::RemovePoint(int32 Index)
|
||||
{
|
||||
if (Index < 0 || Index >= GetNumPoints()) return;
|
||||
SplineComp->RemoveSplinePoint(Index, true);
|
||||
if (PointHandles.IsValidIndex(Index))
|
||||
{
|
||||
if (PointHandles[Index]) PointHandles[Index]->DestroyComponent();
|
||||
PointHandles.RemoveAt(Index);
|
||||
}
|
||||
UpdateVisualization();
|
||||
ScheduleNetworkRebuild();
|
||||
}
|
||||
|
||||
int32 APS_BehaviorEditor_AISpline::GetNumPoints() const
|
||||
{
|
||||
return SplineComp ? SplineComp->GetNumberOfSplinePoints() : 0;
|
||||
}
|
||||
|
||||
FVector APS_BehaviorEditor_AISpline::GetPointLocation(int32 Index) const
|
||||
{
|
||||
if (!SplineComp || Index < 0 || Index >= GetNumPoints()) return FVector::ZeroVector;
|
||||
return SplineComp->GetLocationAtSplinePoint(Index, ESplineCoordinateSpace::World);
|
||||
}
|
||||
|
||||
TArray<FVector> APS_BehaviorEditor_AISpline::GetAllPointLocations() const
|
||||
{
|
||||
TArray<FVector> Result;
|
||||
const int32 Num = GetNumPoints();
|
||||
Result.Reserve(Num);
|
||||
for (int32 i = 0; i < Num; i++)
|
||||
{
|
||||
Result.Add(GetPointLocation(i));
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
void APS_BehaviorEditor_AISpline::SetAllPointLocations(const TArray<FVector>& Points)
|
||||
{
|
||||
if (!SplineComp) return;
|
||||
SplineComp->ClearSplinePoints(false);
|
||||
for (const FVector& WorldPos : Points)
|
||||
{
|
||||
SplineComp->AddSplinePoint(WorldPos, ESplineCoordinateSpace::World, false);
|
||||
}
|
||||
SplineComp->UpdateSpline();
|
||||
RebuildHandles();
|
||||
UpdateVisualization();
|
||||
ScheduleNetworkRebuild();
|
||||
}
|
||||
|
||||
void APS_BehaviorEditor_AISpline::InsertPoint(int32 Index, const FVector& WorldPosition)
|
||||
{
|
||||
if (!SplineComp) return;
|
||||
Index = FMath::Clamp(Index, 0, GetNumPoints());
|
||||
TArray<FVector> Points = GetAllPointLocations();
|
||||
Points.Insert(WorldPosition, Index);
|
||||
SetAllPointLocations(Points); // Already calls ScheduleNetworkRebuild
|
||||
}
|
||||
|
||||
int32 APS_BehaviorEditor_AISpline::FindInsertIndex(const FVector& WorldLocation) const
|
||||
{
|
||||
if (!SplineComp || GetNumPoints() < 2) return GetNumPoints();
|
||||
const float InputKey = SplineComp->FindInputKeyClosestToWorldLocation(WorldLocation);
|
||||
return FMath::Clamp(FMath::FloorToInt(InputKey) + 1, 1, GetNumPoints());
|
||||
}
|
||||
|
||||
FVector APS_BehaviorEditor_AISpline::GetCentroid() const
|
||||
{
|
||||
const int32 Num = GetNumPoints();
|
||||
if (Num == 0) return GetActorLocation();
|
||||
FVector Centroid = FVector::ZeroVector;
|
||||
for (int32 i = 0; i < Num; i++)
|
||||
{
|
||||
Centroid += GetPointLocation(i);
|
||||
}
|
||||
return Centroid / Num;
|
||||
}
|
||||
|
||||
// ---- Visualization ----
|
||||
|
||||
void APS_BehaviorEditor_AISpline::UpdateVisualization()
|
||||
{
|
||||
if (!SplineMeshComp || !SplineComp || GetNumPoints() < 2)
|
||||
{
|
||||
if (SplineMeshComp) SplineMeshComp->ClearAllMeshSections();
|
||||
return;
|
||||
}
|
||||
|
||||
const float SplineLength = SplineComp->GetSplineLength();
|
||||
const int32 NumSamples = FMath::Max(2, FMath::CeilToInt(SplineLength / SampleInterval));
|
||||
const int32 Segs = TubeSegments;
|
||||
|
||||
TArray<FVector> Vertices;
|
||||
TArray<int32> Triangles;
|
||||
TArray<FVector> Normals;
|
||||
TArray<FVector2D> UVs;
|
||||
|
||||
Vertices.Reserve(NumSamples * Segs);
|
||||
Normals.Reserve(NumSamples * Segs);
|
||||
UVs.Reserve(NumSamples * Segs);
|
||||
|
||||
for (int32 i = 0; i < NumSamples; i++)
|
||||
{
|
||||
const float Distance = (static_cast<float>(i) / (NumSamples - 1)) * SplineLength;
|
||||
const FVector Location = SplineComp->GetLocationAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::Local);
|
||||
const FVector SplineRight = SplineComp->GetRightVectorAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::Local);
|
||||
const FVector SplineUp = SplineComp->GetUpVectorAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::Local);
|
||||
|
||||
const float U = static_cast<float>(i) / (NumSamples - 1);
|
||||
|
||||
for (int32 j = 0; j < Segs; j++)
|
||||
{
|
||||
const float Angle = (static_cast<float>(j) / Segs) * 2.0f * PI;
|
||||
const FVector Normal = FMath::Cos(Angle) * SplineRight + FMath::Sin(Angle) * SplineUp;
|
||||
Vertices.Add(Location + Normal * TubeRadius);
|
||||
Normals.Add(Normal);
|
||||
UVs.Add(FVector2D(U, static_cast<float>(j) / Segs));
|
||||
}
|
||||
}
|
||||
|
||||
Triangles.Reserve((NumSamples - 1) * Segs * 6);
|
||||
for (int32 i = 0; i < NumSamples - 1; i++)
|
||||
{
|
||||
for (int32 j = 0; j < Segs; j++)
|
||||
{
|
||||
const int32 NextJ = (j + 1) % Segs;
|
||||
const int32 A = i * Segs + j;
|
||||
const int32 B = i * Segs + NextJ;
|
||||
const int32 C = (i + 1) * Segs + j;
|
||||
const int32 D = (i + 1) * Segs + NextJ;
|
||||
Triangles.Add(A); Triangles.Add(C); Triangles.Add(B);
|
||||
Triangles.Add(B); Triangles.Add(C); Triangles.Add(D);
|
||||
}
|
||||
}
|
||||
|
||||
SplineMeshComp->ClearAllMeshSections();
|
||||
SplineMeshComp->CreateMeshSection(0, Vertices, Triangles, Normals, UVs, TArray<FColor>(), TArray<FProcMeshTangent>(), true);
|
||||
SplineMeshComp->UpdateBounds();
|
||||
SplineMeshComp->MarkRenderStateDirty();
|
||||
|
||||
const bool bCurrentlySelected = (PointHandles.Num() > 0 && PointHandles[0] && PointHandles[0]->IsVisible());
|
||||
UMaterialInstanceDynamic* LineMat = (bCurrentlySelected && SplineMatSelected) ? SplineMatSelected : SplineMat;
|
||||
if (LineMat)
|
||||
{
|
||||
SplineMeshComp->SetMaterial(0, LineMat);
|
||||
}
|
||||
|
||||
UpdateCenterCube();
|
||||
}
|
||||
|
||||
void APS_BehaviorEditor_AISpline::UpdateCenterCube()
|
||||
{
|
||||
if (!CenterCube) return;
|
||||
if (GetNumPoints() == 0)
|
||||
{
|
||||
CenterCube->SetVisibility(false);
|
||||
return;
|
||||
}
|
||||
FVector Centroid = FVector::ZeroVector;
|
||||
for (int32 i = 0; i < GetNumPoints(); i++)
|
||||
{
|
||||
Centroid += GetPointLocation(i);
|
||||
}
|
||||
Centroid /= GetNumPoints();
|
||||
CenterCube->SetWorldLocation(Centroid);
|
||||
CenterCube->SetVisibility(true);
|
||||
}
|
||||
|
||||
void APS_BehaviorEditor_AISpline::UpdateSplineMaterials()
|
||||
{
|
||||
if (SplineMat) SplineMat->SetVectorParameterValue(TEXT("Color"), SplineColor);
|
||||
if (SplineMatSelected) SplineMatSelected->SetVectorParameterValue(TEXT("Color"), SplineSelectedColor);
|
||||
if (CenterCubeMat) CenterCubeMat->SetVectorParameterValue(TEXT("Color"), SplineColor);
|
||||
}
|
||||
|
||||
// ---- Handle interaction ----
|
||||
|
||||
UStaticMeshComponent* APS_BehaviorEditor_AISpline::CreatePointHandle(const FVector& WorldPosition)
|
||||
{
|
||||
UStaticMeshComponent* Handle = NewObject<UStaticMeshComponent>(this);
|
||||
Handle->SetupAttachment(HandleRoot);
|
||||
Handle->RegisterComponent();
|
||||
UStaticMesh* SphereMesh = LoadObject<UStaticMesh>(nullptr, TEXT("/Engine/BasicShapes/Sphere.Sphere"));
|
||||
if (SphereMesh) Handle->SetStaticMesh(SphereMesh);
|
||||
Handle->SetWorldLocation(WorldPosition);
|
||||
Handle->SetWorldScale3D(FVector(HandleScale));
|
||||
Handle->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
||||
Handle->SetCollisionResponseToAllChannels(ECR_Ignore);
|
||||
Handle->SetCollisionResponseToChannel(ECC_Camera, ECR_Block);
|
||||
Handle->SetCastShadow(false);
|
||||
Handle->SetRenderInDepthPass(false);
|
||||
if (HandleMat) Handle->SetMaterial(0, HandleMat);
|
||||
return Handle;
|
||||
}
|
||||
|
||||
void APS_BehaviorEditor_AISpline::RebuildHandles()
|
||||
{
|
||||
for (UStaticMeshComponent* Handle : PointHandles)
|
||||
{
|
||||
if (Handle) Handle->DestroyComponent();
|
||||
}
|
||||
PointHandles.Empty();
|
||||
for (int32 i = 0; i < GetNumPoints(); i++)
|
||||
{
|
||||
PointHandles.Add(CreatePointHandle(GetPointLocation(i)));
|
||||
}
|
||||
}
|
||||
|
||||
int32 APS_BehaviorEditor_AISpline::GetHandleIndexUnderCursor(const FVector& RayOrigin, const FVector& RayDirection, float SphereRadius) const
|
||||
{
|
||||
int32 BestIndex = -1;
|
||||
float BestDist = MAX_FLT;
|
||||
for (int32 i = 0; i < PointHandles.Num(); i++)
|
||||
{
|
||||
if (!PointHandles[i] || !PointHandles[i]->IsVisible()) continue;
|
||||
const FVector HandlePos = PointHandles[i]->GetComponentLocation();
|
||||
const FVector ToHandle = HandlePos - RayOrigin;
|
||||
const float Proj = FVector::DotProduct(ToHandle, RayDirection);
|
||||
if (Proj < 0.0f) continue;
|
||||
const FVector ClosestPoint = RayOrigin + RayDirection * Proj;
|
||||
const float Dist = FVector::Dist(ClosestPoint, HandlePos);
|
||||
if (Dist < SphereRadius && Proj < BestDist)
|
||||
{
|
||||
BestDist = Proj;
|
||||
BestIndex = i;
|
||||
}
|
||||
}
|
||||
return BestIndex;
|
||||
}
|
||||
|
||||
void APS_BehaviorEditor_AISpline::SetHandlesVisible(bool bVisible)
|
||||
{
|
||||
for (UStaticMeshComponent* Handle : PointHandles)
|
||||
{
|
||||
if (Handle)
|
||||
{
|
||||
Handle->SetVisibility(bVisible);
|
||||
Handle->SetCollisionEnabled(bVisible ? ECollisionEnabled::QueryOnly : ECollisionEnabled::NoCollision);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void APS_BehaviorEditor_AISpline::SetSelected(bool bSelected)
|
||||
{
|
||||
if (SplineMeshComp)
|
||||
{
|
||||
UMaterialInstanceDynamic* Mat = bSelected ? SplineMatSelected : SplineMat;
|
||||
if (Mat) SplineMeshComp->SetMaterial(0, Mat);
|
||||
SplineMeshComp->SetCustomDepthStencilValue(bSelected ? 1 : 2);
|
||||
}
|
||||
if (CenterCubeMat)
|
||||
{
|
||||
CenterCubeMat->SetVectorParameterValue(TEXT("Color"), bSelected ? SplineSelectedColor : SplineColor);
|
||||
}
|
||||
}
|
||||
|
||||
bool APS_BehaviorEditor_AISpline::IsCenterCube(const UPrimitiveComponent* Comp) const
|
||||
{
|
||||
return Comp && Comp == CenterCube;
|
||||
}
|
||||
|
||||
bool APS_BehaviorEditor_AISpline::IsSplineMesh(const UPrimitiveComponent* Comp) const
|
||||
{
|
||||
return Comp && Comp == SplineMeshComp;
|
||||
}
|
||||
|
||||
void APS_BehaviorEditor_AISpline::HighlightHandle(int32 Index)
|
||||
{
|
||||
for (int32 i = 0; i < PointHandles.Num(); i++)
|
||||
{
|
||||
if (PointHandles[i])
|
||||
{
|
||||
PointHandles[i]->SetMaterial(0, (i == Index) ? HandleHighlightMat : HandleMat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void APS_BehaviorEditor_AISpline::ClearHandleHighlight()
|
||||
{
|
||||
for (UStaticMeshComponent* Handle : PointHandles)
|
||||
{
|
||||
if (Handle && HandleMat) Handle->SetMaterial(0, HandleMat);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Visibility ----
|
||||
|
||||
void APS_BehaviorEditor_AISpline::SetVisualizationVisible(bool bVisible)
|
||||
{
|
||||
if (SplineMeshComp) SplineMeshComp->SetVisibility(bVisible);
|
||||
if (CenterCube) CenterCube->SetVisibility(bVisible);
|
||||
if (HandleRoot) HandleRoot->SetVisibility(bVisible, true);
|
||||
for (UStaticMeshComponent* Handle : PointHandles)
|
||||
{
|
||||
if (Handle)
|
||||
{
|
||||
Handle->SetVisibility(bVisible);
|
||||
Handle->SetCollisionEnabled(bVisible ? ECollisionEnabled::QueryOnly : ECollisionEnabled::NoCollision);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Serialization ----
|
||||
|
||||
void APS_BehaviorEditor_AISpline::ExportToCustomProperties(TMap<FString, FString>& OutProperties) const
|
||||
{
|
||||
// Spline points
|
||||
const int32 Num = GetNumPoints();
|
||||
OutProperties.Add(TEXT("SplinePointCount"), FString::FromInt(Num));
|
||||
for (int32 i = 0; i < Num; i++)
|
||||
{
|
||||
const FVector Loc = GetPointLocation(i);
|
||||
OutProperties.Add(
|
||||
FString::Printf(TEXT("SplinePoint_%d"), i),
|
||||
FString::Printf(TEXT("X=%.4f Y=%.4f Z=%.4f"), Loc.X, Loc.Y, Loc.Z));
|
||||
}
|
||||
|
||||
// Visual properties
|
||||
OutProperties.Add(TEXT("SplineColor"), SplineColor.ToString());
|
||||
OutProperties.Add(TEXT("SplineSelectedColor"), SplineSelectedColor.ToString());
|
||||
|
||||
// AI properties
|
||||
OutProperties.Add(TEXT("AISplineCategory"), FString::FromInt(static_cast<int32>(SplineCategory)));
|
||||
OutProperties.Add(TEXT("AIBidirectional"), bBidirectional ? TEXT("1") : TEXT("0"));
|
||||
OutProperties.Add(TEXT("AIWalkSpeed"), FString::SanitizeFloat(SplineWalkSpeed));
|
||||
OutProperties.Add(TEXT("AIPriority"), FString::FromInt(Priority));
|
||||
}
|
||||
|
||||
void APS_BehaviorEditor_AISpline::ImportFromCustomProperties(const TMap<FString, FString>& Properties)
|
||||
{
|
||||
// Spline points
|
||||
const FString* CountStr = Properties.Find(TEXT("SplinePointCount"));
|
||||
if (CountStr)
|
||||
{
|
||||
const int32 Num = FCString::Atoi(**CountStr);
|
||||
if (Num >= 2)
|
||||
{
|
||||
TArray<FVector> Points;
|
||||
for (int32 i = 0; i < Num; i++)
|
||||
{
|
||||
const FString Key = FString::Printf(TEXT("SplinePoint_%d"), i);
|
||||
const FString* ValStr = Properties.Find(Key);
|
||||
if (!ValStr) continue;
|
||||
FVector Loc;
|
||||
if (Loc.InitFromString(*ValStr)) Points.Add(Loc);
|
||||
}
|
||||
if (Points.Num() >= 2) SetAllPointLocations(Points);
|
||||
}
|
||||
}
|
||||
|
||||
// Visual properties
|
||||
if (const FString* ColorStr = Properties.Find(TEXT("SplineColor")))
|
||||
SplineColor.InitFromString(*ColorStr);
|
||||
if (const FString* SelColorStr = Properties.Find(TEXT("SplineSelectedColor")))
|
||||
SplineSelectedColor.InitFromString(*SelColorStr);
|
||||
|
||||
// AI properties
|
||||
if (const FString* CatStr = Properties.Find(TEXT("AISplineCategory")))
|
||||
SplineCategory = static_cast<EPS_AI_Behavior_NPCType>(FCString::Atoi(**CatStr));
|
||||
if (const FString* BiStr = Properties.Find(TEXT("AIBidirectional")))
|
||||
bBidirectional = FCString::Atoi(**BiStr) != 0;
|
||||
if (const FString* SpeedStr = Properties.Find(TEXT("AIWalkSpeed")))
|
||||
SplineWalkSpeed = FCString::Atof(**SpeedStr);
|
||||
if (const FString* PriStr = Properties.Find(TEXT("AIPriority")))
|
||||
Priority = FCString::Atoi(**PriStr);
|
||||
|
||||
UpdateSplineMaterials();
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "PS_AI_Behavior_SplinePath.h"
|
||||
#include "PS_Editor_PointPlaceable.h"
|
||||
#include "PS_Editor_SplineEditable.h"
|
||||
#include "PS_BehaviorEditor_AISpline.generated.h"
|
||||
|
||||
class UProceduralMeshComponent;
|
||||
class UPS_Editor_SpawnableComponent;
|
||||
class UPS_Editor_EditableComponent;
|
||||
|
||||
/**
|
||||
* AI Spline placeable from the PS_Editor runtime editor.
|
||||
*
|
||||
* Inherits from APS_AI_Behavior_SplinePath so it is automatically
|
||||
* detected by the SplineNetwork subsystem and usable by BT tasks.
|
||||
*
|
||||
* Implements IPS_Editor_PointPlaceable for click-to-place flow.
|
||||
* Includes tube visualization + handles from PS_Editor's spline system.
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable, meta = (DisplayName = "AI Spline (Editor)"))
|
||||
class PS_BEHAVIOREDITOR_API APS_BehaviorEditor_AISpline : public APS_AI_Behavior_SplinePath, public IPS_Editor_PointPlaceable, public IPS_Editor_SplineEditable
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
APS_BehaviorEditor_AISpline();
|
||||
|
||||
// ---- IPS_Editor_PointPlaceable interface ----
|
||||
virtual void AddPlacementPoint(const FVector& WorldPosition) override { AddPoint(WorldPosition); }
|
||||
virtual int32 GetMinimumPoints() const override { return 2; }
|
||||
virtual int32 GetCurrentPointCount() const override { return GetNumPoints(); }
|
||||
|
||||
// ---- Visual properties (editable at runtime) ----
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spline|Visual")
|
||||
FLinearColor SplineColor = FLinearColor(0.2f, 0.6f, 1.0f); // Blue for AI splines
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spline|Visual")
|
||||
FLinearColor SplineSelectedColor = FLinearColor(0.4f, 0.8f, 1.0f);
|
||||
|
||||
// ---- Point manipulation (IPS_Editor_SplineEditable) ----
|
||||
|
||||
virtual void AddPoint(const FVector& WorldPosition) override;
|
||||
virtual void MovePoint(int32 Index, const FVector& NewWorldPosition) override;
|
||||
virtual void RemovePoint(int32 Index) override;
|
||||
virtual int32 GetNumPoints() const override;
|
||||
virtual FVector GetPointLocation(int32 Index) const override;
|
||||
virtual TArray<FVector> GetAllPointLocations() const override;
|
||||
virtual void SetAllPointLocations(const TArray<FVector>& Points) override;
|
||||
virtual void InsertPoint(int32 Index, const FVector& WorldPosition) override;
|
||||
virtual int32 FindInsertIndex(const FVector& WorldLocation) const override;
|
||||
virtual FVector GetCentroid() const override;
|
||||
|
||||
// ---- Visualization (IPS_Editor_SplineEditable) ----
|
||||
|
||||
virtual void UpdateVisualization() override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor|Spline")
|
||||
virtual void UpdateSplineMaterials() override;
|
||||
|
||||
// ---- Handle interaction (IPS_Editor_SplineEditable) ----
|
||||
|
||||
virtual int32 GetHandleIndexUnderCursor(const FVector& RayOrigin, const FVector& RayDirection, float SphereRadius = 15.0f) const override;
|
||||
virtual void SetHandlesVisible(bool bVisible) override;
|
||||
virtual void SetSelected(bool bSelected) override;
|
||||
virtual bool IsCenterCube(const UPrimitiveComponent* Comp) const override;
|
||||
virtual bool IsSplineMesh(const UPrimitiveComponent* Comp) const override;
|
||||
virtual void HighlightHandle(int32 Index) override;
|
||||
virtual void ClearHandleHighlight() override;
|
||||
|
||||
// ---- Visibility (IPS_Editor_SplineEditable) ----
|
||||
virtual void SetVisualizationVisible(bool bVisible) override;
|
||||
|
||||
// ---- Serialization (IPS_Editor_SplineEditable) ----
|
||||
|
||||
virtual void ExportToCustomProperties(TMap<FString, FString>& OutProperties) const override;
|
||||
virtual void ImportFromCustomProperties(const TMap<FString, FString>& Properties) override;
|
||||
|
||||
// ---- Access (IPS_Editor_SplineEditable) ----
|
||||
virtual USplineComponent* GetSplineComponent() const override { return SplineComp; }
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
private:
|
||||
UPROPERTY(VisibleAnywhere)
|
||||
TObjectPtr<UProceduralMeshComponent> SplineMeshComp;
|
||||
|
||||
UPROPERTY(VisibleAnywhere)
|
||||
TObjectPtr<USceneComponent> HandleRoot;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<TObjectPtr<UStaticMeshComponent>> PointHandles;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UMaterialInstanceDynamic> SplineMat;
|
||||
UPROPERTY()
|
||||
TObjectPtr<UMaterialInstanceDynamic> SplineMatSelected;
|
||||
UPROPERTY()
|
||||
TObjectPtr<UMaterialInstanceDynamic> HandleMat;
|
||||
UPROPERTY()
|
||||
TObjectPtr<UMaterialInstanceDynamic> HandleHighlightMat;
|
||||
UPROPERTY()
|
||||
TObjectPtr<UMaterialInstanceDynamic> SplineMatOccluded;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UStaticMeshComponent> CenterCube;
|
||||
UPROPERTY()
|
||||
TObjectPtr<UMaterialInstanceDynamic> CenterCubeMat;
|
||||
|
||||
/** Schedule a debounced SplineNetwork rebuild. */
|
||||
void ScheduleNetworkRebuild();
|
||||
FTimerHandle NetworkRebuildTimer;
|
||||
|
||||
UPROPERTY(VisibleAnywhere)
|
||||
TObjectPtr<UPS_Editor_SpawnableComponent> SpawnableComp;
|
||||
UPROPERTY(VisibleAnywhere)
|
||||
TObjectPtr<UPS_Editor_EditableComponent> EditableComp;
|
||||
|
||||
UStaticMeshComponent* CreatePointHandle(const FVector& WorldPosition);
|
||||
void RebuildHandles();
|
||||
void UpdateCenterCube();
|
||||
|
||||
float TubeRadius = 2.0f;
|
||||
int32 TubeSegments = 8;
|
||||
float SampleInterval = 10.0f;
|
||||
float HandleScale = 0.12f;
|
||||
float CenterCubeScale = 0.10f;
|
||||
};
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "PS_Editor_Gizmo.h"
|
||||
#include "PS_Editor_UndoManager.h"
|
||||
#include "PS_Editor_SplineActor.h"
|
||||
#include "PS_Editor_SplineEditable.h"
|
||||
#include "Components/SplineComponent.h"
|
||||
#include "DrawDebugHelpers.h"
|
||||
#include "PS_Editor_ConfirmDialog.h"
|
||||
@@ -52,6 +53,9 @@ void APS_Editor_Pawn::BeginPlay()
|
||||
OutlinePostProcess->Settings.WeightedBlendables.Array.Add(FWeightedBlendable(1.0f, OutlineMat));
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Selection outline material loaded"));
|
||||
}
|
||||
|
||||
// TODO: Spline occluded PP material — disabled for now, depth comparison unreliable
|
||||
// Consider translucent second-pass approach instead of post-process
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("PS_Editor: Outline material /PS_Editor/M_PS_Editor_SelectionOutline not found. "
|
||||
@@ -379,7 +383,7 @@ void APS_Editor_Pawn::HandleLook(const FInputActionValue& Value)
|
||||
// Spline point mode: move the single point instead of actors
|
||||
if (ActiveSplinePointIndex >= 0 && DraggedSplineActor.IsValid())
|
||||
{
|
||||
if (APS_Editor_SplineActor* SplineActor = Cast<APS_Editor_SplineActor>(DraggedSplineActor.Get()))
|
||||
if (IPS_Editor_SplineEditable* SplineActor = Cast<IPS_Editor_SplineEditable>(DraggedSplineActor.Get()))
|
||||
{
|
||||
if (SplinePointDragInitialState.IsValidIndex(ActiveSplinePointIndex))
|
||||
{
|
||||
@@ -472,7 +476,7 @@ void APS_Editor_Pawn::HandleLook(const FInputActionValue& Value)
|
||||
if (ActiveSplinePointIndex >= 0 && DraggedSplineActor.IsValid())
|
||||
{
|
||||
// Following a single spline point
|
||||
if (APS_Editor_SplineActor* SplineActor = Cast<APS_Editor_SplineActor>(DraggedSplineActor.Get()))
|
||||
if (IPS_Editor_SplineEditable* SplineActor = Cast<IPS_Editor_SplineEditable>(DraggedSplineActor.Get()))
|
||||
{
|
||||
Gizmo->SetActorLocation(SplineActor->GetPointLocation(ActiveSplinePointIndex));
|
||||
}
|
||||
@@ -480,7 +484,7 @@ void APS_Editor_Pawn::HandleLook(const FInputActionValue& Value)
|
||||
else if (Selected.Num() == 1)
|
||||
{
|
||||
// Single actor: use centroid for splines, location for others
|
||||
if (APS_Editor_SplineActor* SplineActor = Cast<APS_Editor_SplineActor>(Selected[0].Get()))
|
||||
if (IPS_Editor_SplineEditable* SplineActor = Cast<IPS_Editor_SplineEditable>(Selected[0].Get()))
|
||||
{
|
||||
Gizmo->SetActorLocation(SplineActor->GetCentroid());
|
||||
}
|
||||
@@ -507,7 +511,7 @@ void APS_Editor_Pawn::HandleLook(const FInputActionValue& Value)
|
||||
else if (CameraMode == EPS_Editor_CameraMode::SplinePointDrag)
|
||||
{
|
||||
// Drag spline point along horizontal plane
|
||||
APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(DraggedSplineActor.Get());
|
||||
IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(DraggedSplineActor.Get());
|
||||
if (!Spline || ActiveSplinePointIndex < 0) return;
|
||||
|
||||
FVector RayOrigin, RayDir;
|
||||
@@ -585,23 +589,23 @@ void APS_Editor_Pawn::HandleLeftClickStarted(const FInputActionValue& Value)
|
||||
if (Mode == EPS_Editor_EditorMode::SplinePlacement || Mode == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
// Collect candidate splines: selected actors + active placement actor
|
||||
TArray<APS_Editor_SplineActor*> CandidateSplines;
|
||||
TArray<IPS_Editor_SplineEditable*> CandidateSplines;
|
||||
if (UPS_Editor_SelectionManager* SM = EdPC->GetSelectionManager())
|
||||
{
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SM->GetSelectedActors())
|
||||
{
|
||||
if (APS_Editor_SplineActor* S = Cast<APS_Editor_SplineActor>(Weak.Get()))
|
||||
if (IPS_Editor_SplineEditable* S = Cast<IPS_Editor_SplineEditable>(Weak.Get()))
|
||||
{
|
||||
CandidateSplines.AddUnique(S);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (APS_Editor_SplineActor* ActiveSpline = Cast<APS_Editor_SplineActor>(EdPC->GetActivePlacementActor()))
|
||||
if (IPS_Editor_SplineEditable* ActiveSpline = Cast<IPS_Editor_SplineEditable>(EdPC->GetActivePlacementActor()))
|
||||
{
|
||||
CandidateSplines.AddUnique(ActiveSpline);
|
||||
}
|
||||
|
||||
for (APS_Editor_SplineActor* Spline : CandidateSplines)
|
||||
for (IPS_Editor_SplineEditable* Spline : CandidateSplines)
|
||||
{
|
||||
// Center cube check (skip in SplinePlacement — no center cube interaction during creation)
|
||||
if (Mode == EPS_Editor_EditorMode::SplinePlacement)
|
||||
@@ -626,7 +630,7 @@ void APS_Editor_Pawn::HandleLeftClickStarted(const FInputActionValue& Value)
|
||||
if (HandleIdx >= 0)
|
||||
{
|
||||
ActiveSplinePointIndex = HandleIdx;
|
||||
DraggedSplineActor = Spline;
|
||||
DraggedSplineActor = Cast<AActor>(Spline);
|
||||
Spline->HighlightHandle(HandleIdx);
|
||||
|
||||
if (UPS_Editor_SelectionManager* SM = EdPC->GetSelectionManager())
|
||||
@@ -650,16 +654,16 @@ void APS_Editor_Pawn::HandleLeftClickStarted(const FInputActionValue& Value)
|
||||
if (EdPC2->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
// Collect candidate splines: selected + active placement actor
|
||||
TArray<APS_Editor_SplineActor*> TubeCandidates;
|
||||
TArray<IPS_Editor_SplineEditable*> TubeCandidates;
|
||||
if (UPS_Editor_SelectionManager* SM2 = EdPC2->GetSelectionManager())
|
||||
{
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SM2->GetSelectedActors())
|
||||
{
|
||||
if (APS_Editor_SplineActor* S = Cast<APS_Editor_SplineActor>(Weak.Get()))
|
||||
if (IPS_Editor_SplineEditable* S = Cast<IPS_Editor_SplineEditable>(Weak.Get()))
|
||||
TubeCandidates.AddUnique(S);
|
||||
}
|
||||
}
|
||||
if (APS_Editor_SplineActor* ActiveSpline = Cast<APS_Editor_SplineActor>(EdPC2->GetActivePlacementActor()))
|
||||
if (IPS_Editor_SplineEditable* ActiveSpline = Cast<IPS_Editor_SplineEditable>(EdPC2->GetActivePlacementActor()))
|
||||
{
|
||||
TubeCandidates.AddUnique(ActiveSpline);
|
||||
}
|
||||
@@ -673,7 +677,7 @@ void APS_Editor_Pawn::HandleLeftClickStarted(const FInputActionValue& Value)
|
||||
|
||||
if (GetWorld()->LineTraceSingleByChannel(LineHit, RayOrigin, TraceEnd, ECC_Camera, LineParams))
|
||||
{
|
||||
for (APS_Editor_SplineActor* Spline : TubeCandidates)
|
||||
for (IPS_Editor_SplineEditable* Spline : TubeCandidates)
|
||||
{
|
||||
|
||||
if (Spline->IsSplineMesh(LineHit.GetComponent()))
|
||||
@@ -687,7 +691,7 @@ void APS_Editor_Pawn::HandleLeftClickStarted(const FInputActionValue& Value)
|
||||
if (UPS_Editor_UndoManager* UM = EdPC2->GetUndoManager())
|
||||
{
|
||||
TSharedPtr<FPS_Editor_SplinePointAction> Action = MakeShared<FPS_Editor_SplinePointAction>();
|
||||
Action->SplineActor = Spline;
|
||||
Action->SplineActor = Cast<AActor>(Spline);
|
||||
Action->OldPoints = OldPoints;
|
||||
Action->NewPoints = Spline->GetAllPointLocations();
|
||||
Action->Description = TEXT("Insert spline point");
|
||||
@@ -695,7 +699,7 @@ void APS_Editor_Pawn::HandleLeftClickStarted(const FInputActionValue& Value)
|
||||
}
|
||||
|
||||
ActiveSplinePointIndex = InsertIdx;
|
||||
DraggedSplineActor = Spline;
|
||||
DraggedSplineActor = Cast<AActor>(Spline);
|
||||
Spline->HighlightHandle(InsertIdx);
|
||||
|
||||
if (UPS_Editor_SelectionManager* InsertSM = EdPC2->GetSelectionManager())
|
||||
@@ -721,13 +725,13 @@ void APS_Editor_Pawn::HandleLeftClickStarted(const FInputActionValue& Value)
|
||||
{
|
||||
if (EdPC3->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(EdPC3->GetActivePlacementActor()))
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(EdPC3->GetActivePlacementActor()))
|
||||
{
|
||||
// Raycast to get world click position
|
||||
FHitResult AddHit;
|
||||
FCollisionQueryParams AddParams;
|
||||
AddParams.AddIgnoredActor(this);
|
||||
AddParams.AddIgnoredActor(Spline);
|
||||
AddParams.AddIgnoredActor(Cast<AActor>(Spline));
|
||||
const FVector AddEnd = RayOrigin + RayDir * 100000.0f;
|
||||
|
||||
FVector ClickPos;
|
||||
@@ -746,7 +750,7 @@ void APS_Editor_Pawn::HandleLeftClickStarted(const FInputActionValue& Value)
|
||||
if (UPS_Editor_UndoManager* UM = EdPC3->GetUndoManager())
|
||||
{
|
||||
TSharedPtr<FPS_Editor_SplinePointAction> Action = MakeShared<FPS_Editor_SplinePointAction>();
|
||||
Action->SplineActor = Spline;
|
||||
Action->SplineActor = Cast<AActor>(Spline);
|
||||
Action->OldPoints = OldPoints;
|
||||
Action->NewPoints = Spline->GetAllPointLocations();
|
||||
Action->Description = TEXT("Add spline point");
|
||||
@@ -756,7 +760,7 @@ void APS_Editor_Pawn::HandleLeftClickStarted(const FInputActionValue& Value)
|
||||
// Select the new point
|
||||
const int32 NewIdx = Spline->GetNumPoints() - 1;
|
||||
ActiveSplinePointIndex = NewIdx;
|
||||
DraggedSplineActor = Spline;
|
||||
DraggedSplineActor = Cast<AActor>(Spline);
|
||||
Spline->HighlightHandle(NewIdx);
|
||||
|
||||
if (UPS_Editor_SelectionManager* SM3 = EdPC3->GetSelectionManager())
|
||||
@@ -800,10 +804,10 @@ void APS_Editor_Pawn::HandleLeftClickCompleted(const FInputActionValue& Value)
|
||||
// Spline point drag: record SplinePointAction
|
||||
if (ActiveSplinePointIndex >= 0 && DraggedSplineActor.IsValid())
|
||||
{
|
||||
if (APS_Editor_SplineActor* SplineActor = Cast<APS_Editor_SplineActor>(DraggedSplineActor.Get()))
|
||||
if (IPS_Editor_SplineEditable* SplineActor = Cast<IPS_Editor_SplineEditable>(DraggedSplineActor.Get()))
|
||||
{
|
||||
TSharedPtr<FPS_Editor_SplinePointAction> Action = MakeShared<FPS_Editor_SplinePointAction>();
|
||||
Action->SplineActor = SplineActor;
|
||||
Action->SplineActor = Cast<AActor>(SplineActor);
|
||||
Action->OldPoints = SplinePointDragInitialState;
|
||||
Action->NewPoints = SplineActor->GetAllPointLocations();
|
||||
Action->Description = TEXT("Move spline point");
|
||||
@@ -852,7 +856,7 @@ void APS_Editor_Pawn::HandleLeftClickCompleted(const FInputActionValue& Value)
|
||||
if (ActiveSplinePointIndex >= 0 && DraggedSplineActor.IsValid())
|
||||
{
|
||||
// Keep gizmo at the active spline point
|
||||
if (APS_Editor_SplineActor* SplineActor = Cast<APS_Editor_SplineActor>(DraggedSplineActor.Get()))
|
||||
if (IPS_Editor_SplineEditable* SplineActor = Cast<IPS_Editor_SplineEditable>(DraggedSplineActor.Get()))
|
||||
{
|
||||
if (APS_Editor_Gizmo* G = SM2->GetGizmo())
|
||||
{
|
||||
@@ -872,14 +876,14 @@ void APS_Editor_Pawn::HandleLeftClickCompleted(const FInputActionValue& Value)
|
||||
else if (CameraMode == EPS_Editor_CameraMode::SplinePointDrag)
|
||||
{
|
||||
// Record undo for spline point move
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(DraggedSplineActor.Get()))
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(DraggedSplineActor.Get()))
|
||||
{
|
||||
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetController()))
|
||||
{
|
||||
if (UPS_Editor_UndoManager* UM = EditorPC->GetUndoManager())
|
||||
{
|
||||
TSharedPtr<FPS_Editor_SplinePointAction> Action = MakeShared<FPS_Editor_SplinePointAction>();
|
||||
Action->SplineActor = Spline;
|
||||
Action->SplineActor = Cast<AActor>(Spline);
|
||||
Action->OldPoints = SplinePointDragInitialState;
|
||||
Action->NewPoints = Spline->GetAllPointLocations();
|
||||
Action->Description = TEXT("Move spline point");
|
||||
@@ -973,7 +977,7 @@ void APS_Editor_Pawn::HandleDelete(const FInputActionValue& Value)
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement
|
||||
&& ActiveSplinePointIndex >= 0 && DraggedSplineActor.IsValid())
|
||||
{
|
||||
APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(DraggedSplineActor.Get());
|
||||
IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(DraggedSplineActor.Get());
|
||||
if (Spline && Spline->GetNumPoints() > 2)
|
||||
{
|
||||
TArray<FVector> OldPoints = Spline->GetAllPointLocations();
|
||||
@@ -982,7 +986,7 @@ void APS_Editor_Pawn::HandleDelete(const FInputActionValue& Value)
|
||||
if (UPS_Editor_UndoManager* UM = EditorPC->GetUndoManager())
|
||||
{
|
||||
TSharedPtr<FPS_Editor_SplinePointAction> Action = MakeShared<FPS_Editor_SplinePointAction>();
|
||||
Action->SplineActor = Spline;
|
||||
Action->SplineActor = Cast<AActor>(Spline);
|
||||
Action->OldPoints = OldPoints;
|
||||
Action->NewPoints = Spline->GetAllPointLocations();
|
||||
Action->Description = TEXT("Delete spline point");
|
||||
@@ -1089,7 +1093,7 @@ void APS_Editor_Pawn::HandleSnapToGround(const FInputActionValue& Value)
|
||||
// If a spline point is selected (in edit or placement mode), snap that point to ground
|
||||
if (ActiveSplinePointIndex >= 0 && DraggedSplineActor.IsValid())
|
||||
{
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(DraggedSplineActor.Get()))
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(DraggedSplineActor.Get()))
|
||||
{
|
||||
const FVector PointLoc = Spline->GetPointLocation(ActiveSplinePointIndex);
|
||||
const FVector TraceStart = PointLoc + FVector(0.0f, 0.0f, 1.0f); // Just above current position
|
||||
@@ -1097,7 +1101,7 @@ void APS_Editor_Pawn::HandleSnapToGround(const FInputActionValue& Value)
|
||||
|
||||
FHitResult Hit;
|
||||
FCollisionQueryParams Params;
|
||||
Params.AddIgnoredActor(Spline);
|
||||
Params.AddIgnoredActor(Cast<AActor>(Spline));
|
||||
Params.AddIgnoredActor(this);
|
||||
if (UPS_Editor_SelectionManager* SnapSM = EditorPC->GetSelectionManager())
|
||||
{
|
||||
@@ -1132,7 +1136,7 @@ void APS_Editor_Pawn::HandleSnapToGround(const FInputActionValue& Value)
|
||||
if (UPS_Editor_UndoManager* UM = EditorPC->GetUndoManager())
|
||||
{
|
||||
TSharedPtr<FPS_Editor_SplinePointAction> Action = MakeShared<FPS_Editor_SplinePointAction>();
|
||||
Action->SplineActor = Spline;
|
||||
Action->SplineActor = Cast<AActor>(Spline);
|
||||
Action->OldPoints = OldPoints;
|
||||
Action->NewPoints = Spline->GetAllPointLocations();
|
||||
Action->Description = TEXT("Snap spline point to ground");
|
||||
@@ -1438,7 +1442,7 @@ void APS_Editor_Pawn::BeginGizmoDrag(EPS_Editor_GizmoAxis Axis)
|
||||
// Store initial spline state if editing a point
|
||||
if (ActiveSplinePointIndex >= 0 && DraggedSplineActor.IsValid())
|
||||
{
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(DraggedSplineActor.Get()))
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(DraggedSplineActor.Get()))
|
||||
{
|
||||
SplinePointDragInitialState = Spline->GetAllPointLocations();
|
||||
}
|
||||
|
||||
@@ -7,12 +7,18 @@
|
||||
#include "PS_Editor_Gizmo.h"
|
||||
#include "PS_Editor_Pawn.h"
|
||||
#include "PS_Editor_SplineActor.h"
|
||||
#include "PS_Editor_SplineEditable.h"
|
||||
#include "PS_Editor_PointPlaceable.h"
|
||||
#include "Engine/LevelStreamingDynamic.h"
|
||||
#include "Engine/PostProcessVolume.h"
|
||||
#include "PS_Editor_CameraStart.h"
|
||||
#include "PS_Editor_HUD.h"
|
||||
#include "PS_Editor_MainWidget.h"
|
||||
#include "Camera/PlayerCameraManager.h"
|
||||
#include "PS_Editor_GameInstanceSubsystem.h"
|
||||
#include "GameFramework/GameModeBase.h"
|
||||
#include "AIController.h"
|
||||
#include "BrainComponent.h"
|
||||
#include "GameFramework/GameStateBase.h"
|
||||
#include "GameFramework/PlayerState.h"
|
||||
|
||||
@@ -28,6 +34,9 @@ void APS_Editor_PlayerController::BeginPlay()
|
||||
|
||||
SetInputMode(FInputModeGameAndUI().SetHideCursorDuringCapture(false));
|
||||
|
||||
// AI logic will be stopped per-pawn when spawned (see SpawnManager)
|
||||
// Global StopLogic not available in UE 5.5 — handled per BrainComponent instead
|
||||
|
||||
// Create selection manager
|
||||
SelectionManager = NewObject<UPS_Editor_SelectionManager>(this);
|
||||
SelectionManager->Initialize(this);
|
||||
@@ -136,6 +145,14 @@ void APS_Editor_PlayerController::Tick(float DeltaTime)
|
||||
{
|
||||
Super::Tick(DeltaTime);
|
||||
|
||||
// Deferred fade-to-black: CameraManager wasn't ready at BeginPlay
|
||||
if (bPendingFadeToBlack && PlayerCameraManager)
|
||||
{
|
||||
PlayerCameraManager->StartCameraFade(0.0f, 1.0f, 0.0f, FLinearColor::Black, true, true);
|
||||
bPendingFadeToBlack = false;
|
||||
ShowLoadingScreen(true);
|
||||
}
|
||||
|
||||
if (SpawnManager)
|
||||
{
|
||||
SpawnManager->TickEditorMode(DeltaTime);
|
||||
@@ -153,9 +170,9 @@ void APS_Editor_PlayerController::OnPossess(APawn* InPawn)
|
||||
}
|
||||
}
|
||||
|
||||
APS_Editor_SplineActor* APS_Editor_PlayerController::GetActiveSplineActor() const
|
||||
IPS_Editor_SplineEditable* APS_Editor_PlayerController::GetActiveSplineActor() const
|
||||
{
|
||||
return Cast<APS_Editor_SplineActor>(ActivePlacementActor);
|
||||
return Cast<IPS_Editor_SplineEditable>(ActivePlacementActor);
|
||||
}
|
||||
|
||||
void APS_Editor_PlayerController::CloseEditor()
|
||||
@@ -189,6 +206,87 @@ void APS_Editor_PlayerController::CloseEditor()
|
||||
OnEditorClosed.Broadcast();
|
||||
}
|
||||
|
||||
void APS_Editor_PlayerController::StartSimulation()
|
||||
{
|
||||
if (bSimulating) return;
|
||||
bSimulating = true;
|
||||
|
||||
// Save all spawned actor transforms
|
||||
SimulationSavedTransforms.Empty();
|
||||
if (SpawnManager)
|
||||
{
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SpawnManager->GetSpawnedActors())
|
||||
{
|
||||
if (AActor* Actor = Weak.Get())
|
||||
{
|
||||
SimulationSavedTransforms.Add(Actor, Actor->GetActorTransform());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear selection
|
||||
if (SelectionManager)
|
||||
{
|
||||
SelectionManager->ClearSelection();
|
||||
}
|
||||
|
||||
// Enable AI logic on all spawned pawns
|
||||
if (SpawnManager)
|
||||
{
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SpawnManager->GetSpawnedActors())
|
||||
{
|
||||
if (APawn* P = Cast<APawn>(Weak.Get()))
|
||||
{
|
||||
if (AAIController* AIC = Cast<AAIController>(P->GetController()))
|
||||
{
|
||||
if (UBrainComponent* Brain = AIC->GetBrainComponent())
|
||||
{
|
||||
Brain->RestartLogic();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Simulation started (%d actors saved)"), SimulationSavedTransforms.Num());
|
||||
}
|
||||
|
||||
void APS_Editor_PlayerController::StopSimulation()
|
||||
{
|
||||
if (!bSimulating) return;
|
||||
bSimulating = false;
|
||||
|
||||
// Disable AI logic on all spawned pawns
|
||||
if (SpawnManager)
|
||||
{
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SpawnManager->GetSpawnedActors())
|
||||
{
|
||||
if (APawn* P = Cast<APawn>(Weak.Get()))
|
||||
{
|
||||
if (AAIController* AIC = Cast<AAIController>(P->GetController()))
|
||||
{
|
||||
if (UBrainComponent* Brain = AIC->GetBrainComponent())
|
||||
{
|
||||
Brain->StopLogic(TEXT("PS_Editor"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore all saved transforms
|
||||
for (auto& Pair : SimulationSavedTransforms)
|
||||
{
|
||||
if (AActor* Actor = Pair.Key.Get())
|
||||
{
|
||||
Actor->SetActorTransform(Pair.Value);
|
||||
}
|
||||
}
|
||||
SimulationSavedTransforms.Empty();
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Simulation stopped, transforms restored"));
|
||||
}
|
||||
|
||||
void APS_Editor_PlayerController::BeginPointPlacement(TSubclassOf<AActor> InActorClass)
|
||||
{
|
||||
if (SelectionManager)
|
||||
@@ -212,7 +310,7 @@ void APS_Editor_PlayerController::BeginPointPlacementAppend(AActor* ExistingActo
|
||||
EditorMode = EPS_Editor_EditorMode::SplinePlacement;
|
||||
|
||||
// Store initial state for undo (spline-specific)
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(ExistingActor))
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(ExistingActor))
|
||||
{
|
||||
AppendInitialPoints = Spline->GetAllPointLocations();
|
||||
Spline->SetHandlesVisible(true);
|
||||
@@ -229,7 +327,7 @@ void APS_Editor_PlayerController::FinishPointPlacement()
|
||||
AppendInitialPoints.Empty();
|
||||
EditorMode = EPS_Editor_EditorMode::Normal;
|
||||
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(PlacementActor))
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(PlacementActor))
|
||||
{
|
||||
Spline->SetHandlesVisible(false);
|
||||
Spline->ClearHandleHighlight();
|
||||
@@ -253,6 +351,17 @@ void APS_Editor_PlayerController::CancelPointPlacement()
|
||||
FinishPointPlacement();
|
||||
}
|
||||
|
||||
void APS_Editor_PlayerController::ShowLoadingScreen(bool bShow)
|
||||
{
|
||||
if (APS_Editor_HUD* EditorHUD = Cast<APS_Editor_HUD>(GetHUD()))
|
||||
{
|
||||
if (UPS_Editor_MainWidget* Widget = EditorHUD->GetMainWidget())
|
||||
{
|
||||
Widget->ShowLoadingScreen(bShow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void APS_Editor_PlayerController::LoadBaseLevelAsSublevel(const FString& LevelName, const FString& MapPath)
|
||||
{
|
||||
// Unload previous sublevel first
|
||||
@@ -294,6 +403,19 @@ void APS_Editor_PlayerController::LoadBaseLevelAsSublevel(const FString& LevelNa
|
||||
return;
|
||||
}
|
||||
|
||||
// Fade to black + show loading overlay
|
||||
bWaitingForSublevelFadeIn = true;
|
||||
if (PlayerCameraManager)
|
||||
{
|
||||
PlayerCameraManager->StartCameraFade(0.0f, 1.0f, 0.0f, FLinearColor::Black, true, true);
|
||||
bPendingFadeToBlack = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
bPendingFadeToBlack = true;
|
||||
}
|
||||
ShowLoadingScreen(true);
|
||||
|
||||
// Use MapPath if provided, otherwise fall back to LevelName (legacy behavior)
|
||||
const FString& LevelToLoad = MapPath.IsEmpty() ? LevelName : MapPath;
|
||||
|
||||
@@ -362,6 +484,21 @@ void APS_Editor_PlayerController::DisableSublevelPostProcessMaterials()
|
||||
*CameraStart->GetActorLocation().ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// Delay 2s then fade in from black over 2 seconds
|
||||
if (bWaitingForSublevelFadeIn)
|
||||
{
|
||||
bWaitingForSublevelFadeIn = false;
|
||||
FTimerHandle FadeTimer;
|
||||
GetWorldTimerManager().SetTimer(FadeTimer, [this]()
|
||||
{
|
||||
ShowLoadingScreen(false);
|
||||
if (PlayerCameraManager)
|
||||
{
|
||||
PlayerCameraManager->StartCameraFade(1.0f, 0.0f, 2.0f, FLinearColor::Black, false, false);
|
||||
}
|
||||
}, 2.0f, false);
|
||||
}
|
||||
}
|
||||
|
||||
void APS_Editor_PlayerController::HandleEditorClick(FVector2D ScreenPos, bool bAdditive)
|
||||
@@ -389,6 +526,22 @@ void APS_Editor_PlayerController::HandleEditorClick(FVector2D ScreenPos, bool bA
|
||||
ClickWorldPos = WorldOrigin + WorldDirection * 1000.0f;
|
||||
}
|
||||
|
||||
// Snap spline CP to ground: trace downward from click point
|
||||
{
|
||||
FHitResult GroundHit;
|
||||
FCollisionQueryParams GroundParams;
|
||||
GroundParams.AddIgnoredActor(GetPawn());
|
||||
if (ActivePlacementActor) GroundParams.AddIgnoredActor(ActivePlacementActor);
|
||||
|
||||
const FVector GroundStart = ClickWorldPos + FVector(0.0f, 0.0f, 50.0f);
|
||||
const FVector GroundEnd = ClickWorldPos - FVector(0.0f, 0.0f, 10000.0f);
|
||||
|
||||
if (GetWorld()->LineTraceSingleByChannel(GroundHit, GroundStart, GroundEnd, ECC_Visibility, GroundParams))
|
||||
{
|
||||
ClickWorldPos = GroundHit.ImpactPoint + FVector(0.0f, 0.0f, 5.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ActivePlacementActor)
|
||||
{
|
||||
// First click: spawn the actor, track immediately, record undo
|
||||
@@ -420,7 +573,7 @@ void APS_Editor_PlayerController::HandleEditorClick(FVector2D ScreenPos, bool bA
|
||||
}
|
||||
|
||||
// Check if we clicked on the existing spline tube → insert instead of append
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(ActivePlacementActor))
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(ActivePlacementActor))
|
||||
{
|
||||
if (Spline->GetNumPoints() >= 2)
|
||||
{
|
||||
@@ -444,7 +597,7 @@ void APS_Editor_PlayerController::HandleEditorClick(FVector2D ScreenPos, bool bA
|
||||
}
|
||||
|
||||
// Default: add point at end via the interface (with undo)
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(ActivePlacementActor))
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(ActivePlacementActor))
|
||||
{
|
||||
TArray<FVector> OldPoints = Spline->GetAllPointLocations();
|
||||
Spline->AddPoint(ClickWorldPos);
|
||||
@@ -452,7 +605,7 @@ void APS_Editor_PlayerController::HandleEditorClick(FVector2D ScreenPos, bool bA
|
||||
if (UndoManager)
|
||||
{
|
||||
TSharedPtr<FPS_Editor_SplinePointAction> Action = MakeShared<FPS_Editor_SplinePointAction>();
|
||||
Action->SplineActor = Spline;
|
||||
Action->SplineActor = Cast<AActor>(Spline);
|
||||
Action->OldPoints = OldPoints;
|
||||
Action->NewPoints = Spline->GetAllPointLocations();
|
||||
Action->Description = TEXT("Add spline point");
|
||||
|
||||
@@ -12,6 +12,7 @@ class UPS_Editor_SpawnCatalog;
|
||||
class UPS_Editor_MasterCatalog;
|
||||
class UPS_Editor_SceneSerializer;
|
||||
class APS_Editor_SplineActor;
|
||||
class IPS_Editor_SplineEditable;
|
||||
class IPS_Editor_PointPlaceable;
|
||||
class ULevelStreamingDynamic;
|
||||
|
||||
@@ -59,6 +60,20 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor")
|
||||
void CloseEditor();
|
||||
|
||||
// ---- Simulate Mode ----
|
||||
|
||||
/** Start simulation: enable AI, store actor positions, disable saving. */
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor")
|
||||
void StartSimulation();
|
||||
|
||||
/** Stop simulation: disable AI, restore actor positions, re-enable saving. */
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor")
|
||||
void StopSimulation();
|
||||
|
||||
/** True while simulation is running. */
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor")
|
||||
bool IsSimulating() const { return bSimulating; }
|
||||
|
||||
/** Load a base level as sublevel for visual context. Unloads previous sublevel.
|
||||
* @param LevelName Display name stored in CurrentBaseLevel (used for scene filtering).
|
||||
* @param MapPath Full package path for LoadLevelInstance (e.g. "/Game/Maps/Warehouse").
|
||||
@@ -91,7 +106,7 @@ public:
|
||||
bool IsAppendingToActor() const { return bAppendingToActor; }
|
||||
|
||||
// Legacy convenience (for spline-specific code like point editing)
|
||||
APS_Editor_SplineActor* GetActiveSplineActor() const;
|
||||
IPS_Editor_SplineEditable* GetActiveSplineActor() const;
|
||||
|
||||
/** Convenience: enter point placement for an existing spline. */
|
||||
void BeginSplineEditing(AActor* Spline) { BeginPointPlacementAppend(Spline); }
|
||||
@@ -132,13 +147,28 @@ private:
|
||||
/** Snapshot of spline points before append, for undo. */
|
||||
TArray<FVector> AppendInitialPoints;
|
||||
|
||||
/** True while simulate mode is active (AI running, save disabled). */
|
||||
bool bSimulating = false;
|
||||
|
||||
/** Saved transforms for all spawned actors before simulation, to restore on stop. */
|
||||
TMap<TWeakObjectPtr<AActor>, FTransform> SimulationSavedTransforms;
|
||||
|
||||
/** Currently loaded sublevel instance. */
|
||||
UPROPERTY()
|
||||
TObjectPtr<ULevelStreamingDynamic> CurrentSublevel;
|
||||
|
||||
/** True when a sublevel is loading and needs fade-in when ready. */
|
||||
bool bWaitingForSublevelFadeIn = false;
|
||||
|
||||
/** True if we need to apply the fade-to-black once CameraManager is available. */
|
||||
bool bPendingFadeToBlack = false;
|
||||
|
||||
/** Disable custom post-process materials in the loaded sublevel to avoid stencil conflicts. */
|
||||
UFUNCTION()
|
||||
void DisableSublevelPostProcessMaterials();
|
||||
|
||||
/** Show/hide loading screen overlay on the HUD widget. */
|
||||
void ShowLoadingScreen(bool bShow);
|
||||
|
||||
void HandleEditorClick(FVector2D ScreenPos, bool bAdditive);
|
||||
};
|
||||
|
||||
@@ -30,7 +30,8 @@ public class PS_Editor : ModuleRules
|
||||
"Json",
|
||||
"JsonUtilities",
|
||||
"ProceduralMeshComponent",
|
||||
"DesktopPlatform"
|
||||
"DesktopPlatform",
|
||||
"AIModule"
|
||||
});
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[]
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "Materials/MaterialExpressionComponentMask.h"
|
||||
#include "Materials/MaterialExpressionAppendVector.h"
|
||||
#include "Materials/MaterialExpressionVectorParameter.h"
|
||||
#include "Materials/MaterialExpressionIf.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "UObject/SavePackage.h"
|
||||
#include "Misc/PackageName.h"
|
||||
@@ -220,6 +221,168 @@ static void CreateOutlineMaterialAsset()
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Selection outline material auto-generated (8-sample, dynamic resolution)"));
|
||||
}
|
||||
|
||||
static void CreateSplineOccludedMaterialAsset()
|
||||
{
|
||||
const FString MaterialPath = TEXT("/PS_Editor/M_PS_Editor_SplineOccluded");
|
||||
const FString AssetName = TEXT("M_PS_Editor_SplineOccluded");
|
||||
|
||||
if (LoadObject<UMaterial>(nullptr, *(MaterialPath + TEXT(".") + AssetName)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UPackage* Package = CreatePackage(*MaterialPath);
|
||||
UMaterial* Mat = NewObject<UMaterial>(Package, *AssetName, RF_Public | RF_Standalone);
|
||||
Mat->MaterialDomain = EMaterialDomain::MD_PostProcess;
|
||||
Mat->BlendableLocation = BL_SceneColorAfterDOF;
|
||||
|
||||
auto AddExpr = [&](UMaterialExpression* Expr) { Mat->GetExpressionCollection().AddExpression(Expr); };
|
||||
|
||||
// ---- Scene inputs ----
|
||||
UMaterialExpressionSceneTexture* SceneColor = NewObject<UMaterialExpressionSceneTexture>(Mat);
|
||||
SceneColor->SceneTextureId = PPI_PostProcessInput0;
|
||||
AddExpr(SceneColor);
|
||||
SceneColor->MaterialExpressionEditorX = -400;
|
||||
SceneColor->MaterialExpressionEditorY = 0;
|
||||
|
||||
UMaterialExpressionSceneTexture* CustomStencil = NewObject<UMaterialExpressionSceneTexture>(Mat);
|
||||
CustomStencil->SceneTextureId = PPI_CustomStencil;
|
||||
AddExpr(CustomStencil);
|
||||
CustomStencil->MaterialExpressionEditorX = -400;
|
||||
CustomStencil->MaterialExpressionEditorY = 200;
|
||||
|
||||
UMaterialExpressionSceneTexture* CustomDepthTex = NewObject<UMaterialExpressionSceneTexture>(Mat);
|
||||
CustomDepthTex->SceneTextureId = PPI_CustomDepth;
|
||||
AddExpr(CustomDepthTex);
|
||||
CustomDepthTex->MaterialExpressionEditorX = -400;
|
||||
CustomDepthTex->MaterialExpressionEditorY = 400;
|
||||
|
||||
UMaterialExpressionSceneTexture* SceneDepthTex = NewObject<UMaterialExpressionSceneTexture>(Mat);
|
||||
SceneDepthTex->SceneTextureId = PPI_SceneDepth;
|
||||
AddExpr(SceneDepthTex);
|
||||
SceneDepthTex->MaterialExpressionEditorX = -400;
|
||||
SceneDepthTex->MaterialExpressionEditorY = 600;
|
||||
|
||||
// ---- Stencil == 2 check: abs(stencil - 2) < 0.5 ----
|
||||
UMaterialExpressionConstant* StencilTarget = NewObject<UMaterialExpressionConstant>(Mat);
|
||||
StencilTarget->R = 2.0f;
|
||||
AddExpr(StencilTarget);
|
||||
StencilTarget->MaterialExpressionEditorX = -200;
|
||||
StencilTarget->MaterialExpressionEditorY = 200;
|
||||
|
||||
UMaterialExpressionSubtract* StencilDiff = NewObject<UMaterialExpressionSubtract>(Mat);
|
||||
AddExpr(StencilDiff);
|
||||
StencilDiff->A.Connect(0, CustomStencil);
|
||||
StencilDiff->B.Connect(0, StencilTarget);
|
||||
StencilDiff->MaterialExpressionEditorX = 0;
|
||||
StencilDiff->MaterialExpressionEditorY = 200;
|
||||
|
||||
UMaterialExpressionAbs* StencilAbs = NewObject<UMaterialExpressionAbs>(Mat);
|
||||
AddExpr(StencilAbs);
|
||||
StencilAbs->Input.Connect(0, StencilDiff);
|
||||
StencilAbs->MaterialExpressionEditorX = 200;
|
||||
StencilAbs->MaterialExpressionEditorY = 200;
|
||||
|
||||
// Constants for IF outputs
|
||||
UMaterialExpressionConstant* One = NewObject<UMaterialExpressionConstant>(Mat);
|
||||
One->R = 1.0f;
|
||||
AddExpr(One);
|
||||
One->MaterialExpressionEditorX = 200;
|
||||
One->MaterialExpressionEditorY = 100;
|
||||
|
||||
UMaterialExpressionConstant* Zero = NewObject<UMaterialExpressionConstant>(Mat);
|
||||
Zero->R = 0.0f;
|
||||
AddExpr(Zero);
|
||||
Zero->MaterialExpressionEditorX = 200;
|
||||
Zero->MaterialExpressionEditorY = 350;
|
||||
|
||||
UMaterialExpressionConstant* Half = NewObject<UMaterialExpressionConstant>(Mat);
|
||||
Half->R = 0.5f;
|
||||
AddExpr(Half);
|
||||
Half->MaterialExpressionEditorX = 200;
|
||||
Half->MaterialExpressionEditorY = 250;
|
||||
|
||||
// IF(Half > StencilAbs, 1, 0) → IsSpline
|
||||
UMaterialExpressionIf* IsSpline = NewObject<UMaterialExpressionIf>(Mat);
|
||||
AddExpr(IsSpline);
|
||||
IsSpline->A.Connect(0, Half);
|
||||
IsSpline->B.Connect(0, StencilAbs);
|
||||
IsSpline->AGreaterThanB.Connect(0, One);
|
||||
IsSpline->AEqualsB.Connect(0, Zero);
|
||||
IsSpline->ALessThanB.Connect(0, Zero);
|
||||
IsSpline->MaterialExpressionEditorX = 400;
|
||||
IsSpline->MaterialExpressionEditorY = 200;
|
||||
|
||||
// ---- Depth comparison: CustomDepth > SceneDepth + 1 ----
|
||||
UMaterialExpressionConstant* DepthBias = NewObject<UMaterialExpressionConstant>(Mat);
|
||||
DepthBias->R = 1.0f;
|
||||
AddExpr(DepthBias);
|
||||
DepthBias->MaterialExpressionEditorX = -200;
|
||||
DepthBias->MaterialExpressionEditorY = 600;
|
||||
|
||||
UMaterialExpressionAdd* SceneDepthBiased = NewObject<UMaterialExpressionAdd>(Mat);
|
||||
AddExpr(SceneDepthBiased);
|
||||
SceneDepthBiased->A.Connect(0, SceneDepthTex);
|
||||
SceneDepthBiased->B.Connect(0, DepthBias);
|
||||
SceneDepthBiased->MaterialExpressionEditorX = 0;
|
||||
SceneDepthBiased->MaterialExpressionEditorY = 600;
|
||||
|
||||
// IF(CustomDepth > SceneDepth+1, 1, 0) → IsOccluded
|
||||
UMaterialExpressionIf* IsOccluded = NewObject<UMaterialExpressionIf>(Mat);
|
||||
AddExpr(IsOccluded);
|
||||
IsOccluded->A.Connect(0, CustomDepthTex);
|
||||
IsOccluded->B.Connect(0, SceneDepthBiased);
|
||||
IsOccluded->AGreaterThanB.Connect(0, One);
|
||||
IsOccluded->AEqualsB.Connect(0, Zero);
|
||||
IsOccluded->ALessThanB.Connect(0, Zero);
|
||||
IsOccluded->MaterialExpressionEditorX = 400;
|
||||
IsOccluded->MaterialExpressionEditorY = 500;
|
||||
|
||||
// ---- Final mask = IsSpline * IsOccluded * 0.6 ----
|
||||
UMaterialExpressionMultiply* CombinedMask = NewObject<UMaterialExpressionMultiply>(Mat);
|
||||
AddExpr(CombinedMask);
|
||||
CombinedMask->A.Connect(0, IsSpline);
|
||||
CombinedMask->B.Connect(0, IsOccluded);
|
||||
CombinedMask->MaterialExpressionEditorX = 600;
|
||||
CombinedMask->MaterialExpressionEditorY = 350;
|
||||
|
||||
UMaterialExpressionMultiply* FinalMask = NewObject<UMaterialExpressionMultiply>(Mat);
|
||||
AddExpr(FinalMask);
|
||||
FinalMask->A.Connect(0, CombinedMask);
|
||||
FinalMask->ConstB = 0.6f;
|
||||
FinalMask->MaterialExpressionEditorX = 800;
|
||||
FinalMask->MaterialExpressionEditorY = 350;
|
||||
|
||||
// ---- Gray color ----
|
||||
UMaterialExpressionConstant4Vector* GrayColor = NewObject<UMaterialExpressionConstant4Vector>(Mat);
|
||||
GrayColor->Constant = FLinearColor(0.35f, 0.35f, 0.35f, 1.0f);
|
||||
AddExpr(GrayColor);
|
||||
GrayColor->MaterialExpressionEditorX = 600;
|
||||
GrayColor->MaterialExpressionEditorY = 600;
|
||||
|
||||
// ---- Lerp(SceneColor, GrayColor, FinalMask) ----
|
||||
UMaterialExpressionLinearInterpolate* Lerp = NewObject<UMaterialExpressionLinearInterpolate>(Mat);
|
||||
AddExpr(Lerp);
|
||||
Lerp->A.Connect(0, SceneColor);
|
||||
Lerp->B.Connect(0, GrayColor);
|
||||
Lerp->Alpha.Connect(0, FinalMask);
|
||||
Lerp->MaterialExpressionEditorX = 1000;
|
||||
Lerp->MaterialExpressionEditorY = 200;
|
||||
|
||||
Mat->GetEditorOnlyData()->EmissiveColor.Connect(0, Lerp);
|
||||
|
||||
Mat->PreEditChange(nullptr);
|
||||
Mat->PostEditChange();
|
||||
|
||||
FString PackageFilename = FPackageName::LongPackageNameToFilename(MaterialPath, FPackageName::GetAssetPackageExtension());
|
||||
FSavePackageArgs SaveArgs;
|
||||
SaveArgs.TopLevelFlags = RF_Public | RF_Standalone;
|
||||
UPackage::SavePackage(Package, Mat, *PackageFilename, SaveArgs);
|
||||
FAssetRegistryModule::AssetCreated(Mat);
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Spline occluded PP material auto-generated (stencil 2, depth compare)"));
|
||||
}
|
||||
|
||||
static void CreateSplineLineMaterialAsset()
|
||||
{
|
||||
const FString MaterialPath = TEXT("/PS_Editor/M_PS_Editor_SplineLine");
|
||||
@@ -270,6 +433,7 @@ void FPS_EditorModule::StartupModule()
|
||||
#if WITH_EDITOR
|
||||
FCoreDelegates::OnPostEngineInit.AddStatic(&CreateOutlineMaterialAsset);
|
||||
FCoreDelegates::OnPostEngineInit.AddStatic(&CreateSplineLineMaterialAsset);
|
||||
FCoreDelegates::OnPostEngineInit.AddStatic(&CreateSplineOccludedMaterialAsset);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "PS_Editor_EditableComponent.h"
|
||||
#include "PS_Editor_SpawnableComponent.h"
|
||||
#include "PS_Editor_SplineActor.h"
|
||||
#include "PS_Editor_SplineEditable.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
@@ -188,7 +189,7 @@ void UPS_Editor_SelectionManager::SelectActor(AActor* Actor)
|
||||
OnSelectionChanged.Broadcast();
|
||||
|
||||
// Mark spline as selected (handles shown only in SplineEditing mode, not here)
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(Actor))
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(Actor))
|
||||
{
|
||||
Spline->SetSelected(true);
|
||||
}
|
||||
@@ -215,7 +216,7 @@ void UPS_Editor_SelectionManager::DeselectActor(AActor* Actor)
|
||||
OnSelectionChanged.Broadcast();
|
||||
|
||||
// Unmark spline
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(Actor))
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(Actor))
|
||||
{
|
||||
Spline->SetHandlesVisible(false);
|
||||
Spline->SetSelected(false);
|
||||
@@ -258,7 +259,7 @@ void UPS_Editor_SelectionManager::ClearSelection()
|
||||
SetActorHighlight(Actor, false);
|
||||
|
||||
// Spline-specific cleanup
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(Actor))
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(Actor))
|
||||
{
|
||||
Spline->SetHandlesVisible(false);
|
||||
Spline->SetSelected(false);
|
||||
@@ -316,7 +317,7 @@ FVector UPS_Editor_SelectionManager::GetSelectionPivot() const
|
||||
if (AActor* Actor = Weak.Get())
|
||||
{
|
||||
// For SplineActors, use the centroid of all points as pivot
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(Actor))
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(Actor))
|
||||
{
|
||||
Sum += Spline->GetCentroid();
|
||||
}
|
||||
@@ -523,7 +524,7 @@ void UPS_Editor_SelectionManager::UpdateGizmo()
|
||||
// Override pivot for SplineActors: use centroid so gizmo aligns with center cube
|
||||
if (SelectedActors.Num() == 1)
|
||||
{
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(SelectedActors[0].Get()))
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(SelectedActors[0].Get()))
|
||||
{
|
||||
Pivot = Spline->GetCentroid();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "PS_Editor_UndoManager.h"
|
||||
#include "PS_Editor_SplineActor.h"
|
||||
#include "PS_Editor_SplineEditable.h"
|
||||
#include "UObject/UnrealType.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
|
||||
void FPS_Editor_SplinePointAction::Execute()
|
||||
{
|
||||
if (APS_Editor_SplineActor* Spline = SplineActor.Get())
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(SplineActor.Get()))
|
||||
{
|
||||
Spline->SetAllPointLocations(NewPoints);
|
||||
}
|
||||
@@ -13,7 +13,7 @@ void FPS_Editor_SplinePointAction::Execute()
|
||||
|
||||
void FPS_Editor_SplinePointAction::Undo()
|
||||
{
|
||||
if (APS_Editor_SplineActor* Spline = SplineActor.Get())
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(SplineActor.Get()))
|
||||
{
|
||||
Spline->SetAllPointLocations(OldPoints);
|
||||
}
|
||||
|
||||
@@ -158,17 +158,16 @@ struct FPS_Editor_SpawnAction : public FPS_Editor_Action
|
||||
}
|
||||
};
|
||||
|
||||
class APS_Editor_SplineActor;
|
||||
|
||||
/**
|
||||
* Records a spline point state change (move, add, remove).
|
||||
* Stores full state snapshots (all points before/after) for simplicity.
|
||||
* SplineActor must implement IPS_Editor_SplineEditable.
|
||||
*/
|
||||
struct FPS_Editor_SplinePointAction : public FPS_Editor_Action
|
||||
{
|
||||
FPS_Editor_SplinePointAction() : FPS_Editor_Action(EPS_Editor_ActionType::SplinePointChange) {}
|
||||
|
||||
TWeakObjectPtr<APS_Editor_SplineActor> SplineActor;
|
||||
TWeakObjectPtr<AActor> SplineActor;
|
||||
TArray<FVector> OldPoints;
|
||||
TArray<FVector> NewPoints;
|
||||
FString Description;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "PS_Editor_EditableInterface.h"
|
||||
#include "PS_Editor_SpawnableComponent.h"
|
||||
#include "PS_Editor_SplineActor.h"
|
||||
#include "PS_Editor_SplineEditable.h"
|
||||
#include "PS_Editor_PointPlaceable.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
@@ -74,6 +75,12 @@ bool UPS_Editor_SceneLoader::LoadScene(const UObject* WorldContextObject, const
|
||||
{
|
||||
Comp->DestroyComponent();
|
||||
}
|
||||
|
||||
// Hide spline visualization in gameplay (tube, handles) — data stays for AI
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(Actor))
|
||||
{
|
||||
Spline->SetVisualizationVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +151,7 @@ AActor* UPS_Editor_SceneLoader::SpawnActorFromData(UWorld* World, const FPS_Edit
|
||||
}
|
||||
|
||||
// Import spline data if applicable
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(SpawnedActor))
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(SpawnedActor))
|
||||
{
|
||||
Spline->ImportFromCustomProperties(ActorData.CustomProperties);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
#include "PS_Editor_PlayerController.h"
|
||||
#include "PS_Editor_SceneLoader.h"
|
||||
#include "PS_Editor_SplineActor.h"
|
||||
#include "AIController.h"
|
||||
#include "BrainComponent.h"
|
||||
#include "PS_Editor_SplineEditable.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "Misc/Paths.h"
|
||||
@@ -47,7 +50,7 @@ bool UPS_Editor_SceneSerializer::SaveScene(const FString& SceneName, UPS_Editor_
|
||||
ActorData.Scale = Actor->GetActorScale3D();
|
||||
|
||||
// Export spline data
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(Actor))
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(Actor))
|
||||
{
|
||||
Spline->ExportToCustomProperties(ActorData.CustomProperties);
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Exported spline with %d points, class=%s"),
|
||||
@@ -214,7 +217,7 @@ bool UPS_Editor_SceneSerializer::LoadScene(const FString& SceneName, UPS_Editor_
|
||||
}
|
||||
|
||||
// Import spline data
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(SpawnedActor))
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(SpawnedActor))
|
||||
{
|
||||
Spline->ImportFromCustomProperties(ActorData.CustomProperties);
|
||||
}
|
||||
@@ -226,6 +229,25 @@ bool UPS_Editor_SceneSerializer::LoadScene(const FString& SceneName, UPS_Editor_
|
||||
IPS_Editor_EditableInterface::Execute_OnEditorModeChanged(SpawnedActor, true);
|
||||
}
|
||||
|
||||
// Stop AI on spawned pawns (editor mode — AI only runs during Simulate)
|
||||
if (APawn* P = Cast<APawn>(SpawnedActor))
|
||||
{
|
||||
TWeakObjectPtr<APawn> WeakP = P;
|
||||
P->GetWorldTimerManager().SetTimerForNextTick([WeakP]()
|
||||
{
|
||||
if (APawn* Pawn = WeakP.Get())
|
||||
{
|
||||
if (AAIController* AIC = Cast<AAIController>(Pawn->GetController()))
|
||||
{
|
||||
if (UBrainComponent* Brain = AIC->GetBrainComponent())
|
||||
{
|
||||
Brain->StopLogic(TEXT("PS_Editor"));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
SpawnedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "PS_Editor_EditableInterface.h"
|
||||
#include "PS_Editor_UndoManager.h"
|
||||
#include "PS_Editor_PlayerController.h"
|
||||
#include "AIController.h"
|
||||
#include "BrainComponent.h"
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "Engine/World.h"
|
||||
|
||||
@@ -166,6 +168,31 @@ AActor* UPS_Editor_SpawnManager::SpawnFromCatalogAtLocation(int32 Index, FVector
|
||||
IPS_Editor_EditableInterface::Execute_OnEditorModeChanged(SpawnedActor, true);
|
||||
}
|
||||
|
||||
// Stop AI logic on spawned pawns in editor mode (deferred — AIController may not be assigned yet)
|
||||
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(PC))
|
||||
{
|
||||
if (!EditorPC->IsSimulating())
|
||||
{
|
||||
if (APawn* SpawnedPawn = Cast<APawn>(SpawnedActor))
|
||||
{
|
||||
TWeakObjectPtr<APawn> WeakPawn = SpawnedPawn;
|
||||
SpawnedPawn->GetWorldTimerManager().SetTimerForNextTick([WeakPawn]()
|
||||
{
|
||||
if (APawn* P = WeakPawn.Get())
|
||||
{
|
||||
if (AAIController* AIC = Cast<AAIController>(P->GetController()))
|
||||
{
|
||||
if (UBrainComponent* Brain = AIC->GetBrainComponent())
|
||||
{
|
||||
Brain->StopLogic(TEXT("PS_Editor"));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Spawned %s at %s"), *Entry.DisplayName, *WorldLocation.ToString());
|
||||
return SpawnedActor;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ APS_Editor_SplineActor::APS_Editor_SplineActor()
|
||||
SplineMeshComp->SetCollisionResponseToChannel(ECC_Camera, ECR_Block);
|
||||
SplineMeshComp->bUseComplexAsSimpleCollision = true;
|
||||
SplineMeshComp->SetCastShadow(false);
|
||||
SplineMeshComp->SetRenderCustomDepth(true);
|
||||
SplineMeshComp->SetCustomDepthStencilValue(2);
|
||||
|
||||
// Handle parent
|
||||
HandleRoot = CreateDefaultSubobject<USceneComponent>(TEXT("HandleRoot"));
|
||||
@@ -87,6 +89,10 @@ void APS_Editor_SplineActor::BeginPlay()
|
||||
|
||||
CenterCubeMat = UMaterialInstanceDynamic::Create(GizmoMat, this);
|
||||
CenterCubeMat->SetVectorParameterValue(TEXT("Color"), SplineColor);
|
||||
|
||||
// Occluded pass: gray, no depth test — visible through walls
|
||||
SplineMatOccluded = UMaterialInstanceDynamic::Create(GizmoMat, this);
|
||||
SplineMatOccluded->SetVectorParameterValue(TEXT("Color"), FLinearColor(0.3f, 0.3f, 0.3f));
|
||||
}
|
||||
|
||||
// Create center cube (selection handle for whole spline)
|
||||
@@ -255,14 +261,12 @@ void APS_Editor_SplineActor::UpdateVisualization()
|
||||
}
|
||||
|
||||
SplineMeshComp->ClearAllMeshSections();
|
||||
|
||||
SplineMeshComp->CreateMeshSection(0, Vertices, Triangles, Normals, UVs, TArray<FColor>(), TArray<FProcMeshTangent>(), true);
|
||||
|
||||
// Force collision and bounds update immediately
|
||||
SplineMeshComp->UpdateBounds();
|
||||
SplineMeshComp->MarkRenderStateDirty();
|
||||
|
||||
// Apply the current material (normal or selected)
|
||||
// Check if handles are visible as a proxy for "selected" state
|
||||
const bool bCurrentlySelected = (PointHandles.Num() > 0 && PointHandles[0] && PointHandles[0]->IsVisible());
|
||||
UMaterialInstanceDynamic* LineMat = (bCurrentlySelected && SplineMatSelected) ? SplineMatSelected : SplineMat;
|
||||
if (LineMat)
|
||||
@@ -419,15 +423,12 @@ void APS_Editor_SplineActor::SetSelected(bool bSelected)
|
||||
if (SplineMeshComp)
|
||||
{
|
||||
UMaterialInstanceDynamic* Mat = bSelected ? SplineMatSelected : SplineMat;
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: SetSelected(%s) - SplineMat=%s, SplineMatSelected=%s, Using=%s"),
|
||||
bSelected ? TEXT("true") : TEXT("false"),
|
||||
SplineMat ? TEXT("valid") : TEXT("NULL"),
|
||||
SplineMatSelected ? TEXT("valid") : TEXT("NULL"),
|
||||
Mat ? TEXT("valid") : TEXT("NULL"));
|
||||
if (Mat)
|
||||
{
|
||||
SplineMeshComp->SetMaterial(0, Mat);
|
||||
}
|
||||
// Restore stencil 2 for occluded PP effect when deselected (selection sets it to 1)
|
||||
SplineMeshComp->SetCustomDepthStencilValue(bSelected ? 1 : 2);
|
||||
}
|
||||
|
||||
// Center cube: selected color when selected, normal color otherwise
|
||||
@@ -458,6 +459,21 @@ void APS_Editor_SplineActor::UpdateSplineMaterials()
|
||||
}
|
||||
}
|
||||
|
||||
void APS_Editor_SplineActor::SetVisualizationVisible(bool bVisible)
|
||||
{
|
||||
if (SplineMeshComp) SplineMeshComp->SetVisibility(bVisible);
|
||||
if (CenterCube) CenterCube->SetVisibility(bVisible);
|
||||
if (HandleRoot) HandleRoot->SetVisibility(bVisible, true);
|
||||
for (UStaticMeshComponent* Handle : PointHandles)
|
||||
{
|
||||
if (Handle)
|
||||
{
|
||||
Handle->SetVisibility(bVisible);
|
||||
Handle->SetCollisionEnabled(bVisible ? ECollisionEnabled::QueryOnly : ECollisionEnabled::NoCollision);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool APS_Editor_SplineActor::IsSplineMesh(const UPrimitiveComponent* Comp) const
|
||||
{
|
||||
return Comp && Comp == SplineMeshComp;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "PS_Editor_PointPlaceable.h"
|
||||
#include "PS_Editor_SplineEditable.h"
|
||||
#include "PS_Editor_SplineActor.generated.h"
|
||||
|
||||
class USplineComponent;
|
||||
@@ -15,7 +16,7 @@ class UPS_Editor_EditableComponent;
|
||||
* Renders as a visible quad strip with clickable point handles.
|
||||
*/
|
||||
UCLASS()
|
||||
class PS_EDITOR_API APS_Editor_SplineActor : public AActor, public IPS_Editor_PointPlaceable
|
||||
class PS_EDITOR_API APS_Editor_SplineActor : public AActor, public IPS_Editor_PointPlaceable, public IPS_Editor_SplineEditable
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
@@ -46,74 +47,77 @@ public:
|
||||
// ---- Point manipulation ----
|
||||
|
||||
/** Add a point at the end of the spline (world space). */
|
||||
void AddPoint(const FVector& WorldPosition);
|
||||
virtual void AddPoint(const FVector& WorldPosition) override;
|
||||
|
||||
/** Move an existing point to a new world position. */
|
||||
void MovePoint(int32 Index, const FVector& NewWorldPosition);
|
||||
virtual void MovePoint(int32 Index, const FVector& NewWorldPosition) override;
|
||||
|
||||
/** Remove a point by index. */
|
||||
void RemovePoint(int32 Index);
|
||||
virtual void RemovePoint(int32 Index) override;
|
||||
|
||||
/** Get the number of spline points. */
|
||||
int32 GetNumPoints() const;
|
||||
virtual int32 GetNumPoints() const override;
|
||||
|
||||
/** Get the world position of a spline point. */
|
||||
FVector GetPointLocation(int32 Index) const;
|
||||
virtual FVector GetPointLocation(int32 Index) const override;
|
||||
|
||||
/** Get all point world positions (for undo snapshots). */
|
||||
TArray<FVector> GetAllPointLocations() const;
|
||||
virtual TArray<FVector> GetAllPointLocations() const override;
|
||||
|
||||
/** Set all point positions at once (for undo restore). Rebuilds handles and visualization. */
|
||||
void SetAllPointLocations(const TArray<FVector>& Points);
|
||||
virtual void SetAllPointLocations(const TArray<FVector>& Points) override;
|
||||
|
||||
// ---- Visualization ----
|
||||
|
||||
/** Regenerate the line mesh and reposition all handles. */
|
||||
void UpdateVisualization();
|
||||
virtual void UpdateVisualization() override;
|
||||
|
||||
/** Re-apply SplineColor to materials (call after changing SplineColor at runtime). */
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor|Spline")
|
||||
void UpdateSplineMaterials();
|
||||
virtual void UpdateSplineMaterials() override;
|
||||
|
||||
// ---- Handle interaction ----
|
||||
|
||||
/** Find which point handle is under the cursor ray. Returns -1 if none. */
|
||||
int32 GetHandleIndexUnderCursor(const FVector& RayOrigin, const FVector& RayDirection, float SphereRadius = 15.0f) const;
|
||||
virtual int32 GetHandleIndexUnderCursor(const FVector& RayOrigin, const FVector& RayDirection, float SphereRadius = 15.0f) const override;
|
||||
|
||||
/** Show or hide all point handles and center cube. */
|
||||
void SetHandlesVisible(bool bVisible);
|
||||
virtual void SetHandlesVisible(bool bVisible) override;
|
||||
|
||||
/** Set the spline's selected state (changes line + cube color). */
|
||||
void SetSelected(bool bSelected);
|
||||
virtual void SetSelected(bool bSelected) override;
|
||||
|
||||
/** Returns true if the given component is the center cube. */
|
||||
bool IsCenterCube(const UPrimitiveComponent* Comp) const;
|
||||
virtual bool IsCenterCube(const UPrimitiveComponent* Comp) const override;
|
||||
|
||||
/** Highlight a specific handle (yellow), others go back to normal (white). */
|
||||
void HighlightHandle(int32 Index);
|
||||
virtual void HighlightHandle(int32 Index) override;
|
||||
|
||||
/** Clear all handle highlights. */
|
||||
void ClearHandleHighlight();
|
||||
virtual void ClearHandleHighlight() override;
|
||||
|
||||
// ---- Serialization ----
|
||||
|
||||
/** Returns true if the given component is the spline line mesh. */
|
||||
bool IsSplineMesh(const UPrimitiveComponent* Comp) const;
|
||||
virtual bool IsSplineMesh(const UPrimitiveComponent* Comp) const override;
|
||||
|
||||
/** Find the best insertion index for a new point at the given world location. */
|
||||
int32 FindInsertIndex(const FVector& WorldLocation) const;
|
||||
virtual int32 FindInsertIndex(const FVector& WorldLocation) const override;
|
||||
|
||||
/** Insert a point at a specific index. */
|
||||
void InsertPoint(int32 Index, const FVector& WorldPosition);
|
||||
virtual void InsertPoint(int32 Index, const FVector& WorldPosition) override;
|
||||
|
||||
/** Get the centroid of all spline points (world space). Used as pivot for gizmo. */
|
||||
FVector GetCentroid() const;
|
||||
virtual FVector GetCentroid() const override;
|
||||
|
||||
/** Hide/show the spline visualization. */
|
||||
virtual void SetVisualizationVisible(bool bVisible) override;
|
||||
|
||||
/** Export spline data to a key-value map (for JSON serialization). */
|
||||
void ExportToCustomProperties(TMap<FString, FString>& OutProperties) const;
|
||||
virtual void ExportToCustomProperties(TMap<FString, FString>& OutProperties) const override;
|
||||
|
||||
/** Import spline data from a key-value map (for scene loading). */
|
||||
void ImportFromCustomProperties(const TMap<FString, FString>& Properties);
|
||||
virtual void ImportFromCustomProperties(const TMap<FString, FString>& Properties) override;
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
@@ -144,6 +148,9 @@ private:
|
||||
UPROPERTY()
|
||||
TObjectPtr<UMaterialInstanceDynamic> HandleHighlightMat;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UMaterialInstanceDynamic> SplineMatOccluded; // Gray, no depth test — visible through walls
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UStaticMeshComponent> CenterCube;
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Interface.h"
|
||||
#include "PS_Editor_SplineEditable.generated.h"
|
||||
|
||||
class USplineComponent;
|
||||
|
||||
UINTERFACE(BlueprintType, meta = (DisplayName = "PS Editor Spline Editable"))
|
||||
class PS_EDITOR_API UPS_Editor_SplineEditable : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for any actor that provides spline editing in PS_Editor.
|
||||
* Implemented by APS_Editor_SplineActor and APS_BehaviorEditor_AISpline.
|
||||
*
|
||||
* The editor (Pawn, SelectionManager, Serializer, etc.) casts to this interface
|
||||
* instead of a concrete class, so any spline-like actor works with the full
|
||||
* editing flow: point placement, handle drag, insert, delete, undo, serialization.
|
||||
*/
|
||||
class PS_EDITOR_API IPS_Editor_SplineEditable
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// ---- Point manipulation ----
|
||||
virtual void AddPoint(const FVector& WorldPosition) = 0;
|
||||
virtual void MovePoint(int32 Index, const FVector& NewWorldPosition) = 0;
|
||||
virtual void RemovePoint(int32 Index) = 0;
|
||||
virtual int32 GetNumPoints() const = 0;
|
||||
virtual FVector GetPointLocation(int32 Index) const = 0;
|
||||
virtual TArray<FVector> GetAllPointLocations() const = 0;
|
||||
virtual void SetAllPointLocations(const TArray<FVector>& Points) = 0;
|
||||
virtual void InsertPoint(int32 Index, const FVector& WorldPosition) = 0;
|
||||
virtual int32 FindInsertIndex(const FVector& WorldLocation) const = 0;
|
||||
virtual FVector GetCentroid() const = 0;
|
||||
|
||||
// ---- Visualization ----
|
||||
virtual void UpdateVisualization() = 0;
|
||||
virtual void UpdateSplineMaterials() = 0;
|
||||
|
||||
// ---- Handle interaction ----
|
||||
virtual int32 GetHandleIndexUnderCursor(const FVector& RayOrigin, const FVector& RayDirection, float SphereRadius = 15.0f) const = 0;
|
||||
virtual void SetHandlesVisible(bool bVisible) = 0;
|
||||
virtual void SetSelected(bool bSelected) = 0;
|
||||
virtual bool IsCenterCube(const UPrimitiveComponent* Comp) const = 0;
|
||||
virtual bool IsSplineMesh(const UPrimitiveComponent* Comp) const = 0;
|
||||
virtual void HighlightHandle(int32 Index) = 0;
|
||||
virtual void ClearHandleHighlight() = 0;
|
||||
|
||||
// ---- Serialization ----
|
||||
virtual void ExportToCustomProperties(TMap<FString, FString>& OutProperties) const = 0;
|
||||
virtual void ImportFromCustomProperties(const TMap<FString, FString>& Properties) = 0;
|
||||
|
||||
// ---- Visibility ----
|
||||
/** Hide/show the spline visualization (tube, handles, center cube). Data remains for AI. */
|
||||
virtual void SetVisualizationVisible(bool bVisible) = 0;
|
||||
|
||||
// ---- Access ----
|
||||
virtual USplineComponent* GetSplineComponent() const = 0;
|
||||
};
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "PS_Editor_EditableInterface.h"
|
||||
#include "PS_Editor_SpawnableComponent.h"
|
||||
#include "PS_Editor_SplineActor.h"
|
||||
#include "PS_Editor_SplineEditable.h"
|
||||
#include "PS_Editor_Pawn.h"
|
||||
#include "PS_Editor_PointPlaceable.h"
|
||||
#include "Widgets/SBoxPanel.h"
|
||||
@@ -45,6 +46,27 @@ void UPS_Editor_MainWidget::SetSceneSerializer(UPS_Editor_SceneSerializer* InSer
|
||||
TSharedRef<SWidget> UPS_Editor_MainWidget::RebuildWidget()
|
||||
{
|
||||
return SNew(SOverlay)
|
||||
// Loading screen (behind everything — first slot = bottom layer)
|
||||
+ SOverlay::Slot()
|
||||
.HAlign(HAlign_Fill)
|
||||
.VAlign(VAlign_Fill)
|
||||
[
|
||||
SAssignNew(LoadingOverlay, SBorder)
|
||||
.Visibility(EVisibility::Visible)
|
||||
.BorderImage(FCoreStyle::Get().GetBrush("WhiteBrush"))
|
||||
.BorderBackgroundColor(FLinearColor(0.0f, 0.0f, 0.0f, 1.0f))
|
||||
[
|
||||
SNew(SBox)
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Loading...")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 18))
|
||||
.ColorAndOpacity(FLinearColor(0.7f, 0.7f, 0.7f, 1.0f))
|
||||
]
|
||||
]
|
||||
]
|
||||
// Top-left: Title + Toolbar
|
||||
+ SOverlay::Slot()
|
||||
.HAlign(HAlign_Left)
|
||||
@@ -183,9 +205,9 @@ TSharedRef<SWidget> UPS_Editor_MainWidget::RebuildWidget()
|
||||
{
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SelMgr->GetSelectedActors())
|
||||
{
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(Weak.Get()))
|
||||
if (Cast<IPS_Editor_SplineEditable>(Weak.Get()))
|
||||
{
|
||||
EditorPC->BeginSplineEditing(Spline);
|
||||
EditorPC->BeginSplineEditing(Weak.Get());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -563,6 +585,19 @@ TSharedRef<SWidget> UPS_Editor_MainWidget::BuildSceneButtons()
|
||||
SNew(SButton)
|
||||
.OnClicked_Lambda([this]() -> FReply
|
||||
{
|
||||
// Block save during simulation
|
||||
if (APS_Editor_PlayerController* SimPC = Cast<APS_Editor_PlayerController>(GetOwningPlayer()))
|
||||
{
|
||||
if (SimPC->IsSimulating())
|
||||
{
|
||||
if (HelpBarText.IsValid())
|
||||
{
|
||||
HelpBarText->SetText(FText::FromString(TEXT("Cannot save during simulation. Stop simulation first.")));
|
||||
HelpBarText->SetColorAndOpacity(FLinearColor(1.0f, 0.4f, 0.2f));
|
||||
}
|
||||
return FReply::Handled();
|
||||
}
|
||||
}
|
||||
if (SaveNameField.IsValid() && SceneSerializer.IsValid())
|
||||
{
|
||||
FString Name = SaveNameField->GetText().ToString();
|
||||
@@ -692,26 +727,42 @@ TSharedRef<SWidget> UPS_Editor_MainWidget::BuildSceneButtons()
|
||||
|
||||
// Store scene name in GameInstance subsystem (survives OpenLevel)
|
||||
FString BaseLevel = EditorPC->CurrentBaseLevel;
|
||||
UE_LOG(LogTemp, Warning, TEXT("PS_Editor Play: BaseLevel='%s'"), *BaseLevel);
|
||||
if (BaseLevel.IsEmpty())
|
||||
FString SceneName = SaveNameField.IsValid() ? SaveNameField->GetText().ToString() : TEXT("");
|
||||
|
||||
// Resolve map path: BaseLevel from catalog, or current map if none selected
|
||||
FString MapPath;
|
||||
if (!BaseLevel.IsEmpty())
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("PS_Editor: No BaseLevel set, cannot Play."));
|
||||
return FReply::Handled();
|
||||
if (FString* Found = BaseLevelPathMap.Find(BaseLevel))
|
||||
{
|
||||
MapPath = *Found;
|
||||
}
|
||||
else
|
||||
{
|
||||
MapPath = FString::Printf(TEXT("/Game/%s"), *BaseLevel);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No BaseLevel = play on the current editor map
|
||||
MapPath = EditorPC->GetWorld()->GetMapName();
|
||||
MapPath.RemoveFromStart(EditorPC->GetWorld()->StreamingLevelsPrefix);
|
||||
BaseLevel = MapPath;
|
||||
}
|
||||
|
||||
FString SceneName = SaveNameField.IsValid() ? SaveNameField->GetText().ToString() : TEXT("");
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor Play: BaseLevel='%s', Map='%s', Scenario='%s'"), *BaseLevel, *MapPath, *SceneName);
|
||||
|
||||
if (UGameInstance* GI = EditorPC->GetGameInstance())
|
||||
{
|
||||
if (UPS_Editor_GameInstanceSubsystem* Sub = GI->GetSubsystem<UPS_Editor_GameInstanceSubsystem>())
|
||||
{
|
||||
Sub->SetPendingScenario(SceneName, BaseLevel);
|
||||
Sub->bIsPlayMode = true;
|
||||
// Remember editor map for ReturnToEditor
|
||||
if (Sub->EditorMapName.IsEmpty())
|
||||
{
|
||||
FString MapName = EditorPC->GetWorld()->GetMapName();
|
||||
MapName.RemoveFromStart(EditorPC->GetWorld()->StreamingLevelsPrefix);
|
||||
Sub->EditorMapName = MapName;
|
||||
FString EdMapName = EditorPC->GetWorld()->GetMapName();
|
||||
EdMapName.RemoveFromStart(EditorPC->GetWorld()->StreamingLevelsPrefix);
|
||||
Sub->EditorMapName = EdMapName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -729,16 +780,6 @@ TSharedRef<SWidget> UPS_Editor_MainWidget::BuildSceneButtons()
|
||||
EditorPC->bShowMouseCursor = false;
|
||||
EditorPC->SetInputMode(FInputModeGameOnly());
|
||||
|
||||
// Use full path from MasterCatalog, fallback to /Game/{name}
|
||||
FString MapPath;
|
||||
if (FString* Found = BaseLevelPathMap.Find(BaseLevel))
|
||||
{
|
||||
MapPath = *Found;
|
||||
}
|
||||
else
|
||||
{
|
||||
MapPath = FString::Printf(TEXT("/Game/%s"), *BaseLevel);
|
||||
}
|
||||
UGameplayStatics::OpenLevel(EditorPC, FName(*MapPath), true, TEXT(""));
|
||||
return FReply::Handled();
|
||||
})
|
||||
@@ -752,6 +793,33 @@ TSharedRef<SWidget> UPS_Editor_MainWidget::BuildSceneButtons()
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
.Padding(4.0f, 0.0f, 0.0f, 0.0f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.OnClicked_Lambda([this]() -> FReply
|
||||
{
|
||||
APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetOwningPlayer());
|
||||
if (!EditorPC) return FReply::Handled();
|
||||
|
||||
if (EditorPC->IsSimulating())
|
||||
{
|
||||
EditorPC->StopSimulation();
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorPC->StartSimulation();
|
||||
}
|
||||
return FReply::Handled();
|
||||
})
|
||||
[
|
||||
SAssignNew(SimulateButtonText, STextBlock)
|
||||
.Text(FText::FromString(TEXT("Simulate")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 10))
|
||||
.ColorAndOpacity(FLinearColor(1.0f, 0.6f, 0.0f))
|
||||
]
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
.AutoWidth()
|
||||
.Padding(4.0f, 0.0f, 0.0f, 0.0f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.OnClicked_Lambda([this]() -> FReply
|
||||
@@ -999,6 +1067,19 @@ void UPS_Editor_MainWidget::NativeConstruct()
|
||||
}
|
||||
|
||||
PopulateSceneList();
|
||||
|
||||
// Hide loading overlay if no sublevel is being loaded
|
||||
if (APS_Editor_PlayerController* PC = Cast<APS_Editor_PlayerController>(GetOwningPlayer()))
|
||||
{
|
||||
if (PC->CurrentBaseLevel.IsEmpty())
|
||||
{
|
||||
ShowLoadingScreen(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowLoadingScreen(false);
|
||||
}
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget::PopulateSceneList()
|
||||
@@ -1272,6 +1353,24 @@ void UPS_Editor_MainWidget::NativeTick(const FGeometry& MyGeometry, float InDelt
|
||||
}
|
||||
}
|
||||
|
||||
// Update simulate button text
|
||||
if (SimulateButtonText.IsValid())
|
||||
{
|
||||
if (APS_Editor_PlayerController* SimPC = Cast<APS_Editor_PlayerController>(GetOwningPlayer()))
|
||||
{
|
||||
if (SimPC->IsSimulating())
|
||||
{
|
||||
SimulateButtonText->SetText(FText::FromString(TEXT("Stop")));
|
||||
SimulateButtonText->SetColorAndOpacity(FLinearColor(1.0f, 0.3f, 0.2f));
|
||||
}
|
||||
else
|
||||
{
|
||||
SimulateButtonText->SetText(FText::FromString(TEXT("Simulate")));
|
||||
SimulateButtonText->SetColorAndOpacity(FLinearColor(1.0f, 0.6f, 0.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RefreshPropertiesFromSelection();
|
||||
RefreshOutliner();
|
||||
}
|
||||
@@ -1319,7 +1418,7 @@ void UPS_Editor_MainWidget::RefreshPropertiesFromSelection()
|
||||
{
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SM->GetSelectedActors())
|
||||
{
|
||||
if (Cast<APS_Editor_SplineActor>(Weak.Get()))
|
||||
if (Cast<IPS_Editor_SplineEditable>(Weak.Get()))
|
||||
{
|
||||
bSplineSelected = true;
|
||||
break;
|
||||
@@ -1564,11 +1663,7 @@ void UPS_Editor_MainWidget::RefreshOutliner()
|
||||
|
||||
// Build display name
|
||||
FString DisplayName;
|
||||
if (Cast<APS_Editor_SplineActor>(Actor))
|
||||
{
|
||||
DisplayName = TEXT("Spline");
|
||||
}
|
||||
else if (UPS_Editor_SpawnableComponent* SC = Actor->FindComponentByClass<UPS_Editor_SpawnableComponent>())
|
||||
if (UPS_Editor_SpawnableComponent* SC = Actor->FindComponentByClass<UPS_Editor_SpawnableComponent>())
|
||||
{
|
||||
if (!SC->DisplayName.IsEmpty())
|
||||
{
|
||||
@@ -2084,10 +2179,28 @@ void UPS_Editor_MainWidget::ApplyPropertyFromUI(int32 RowIndex)
|
||||
UE_LOG(LogTemp, Warning, TEXT("PS_Editor: ApplyProperty '%s' = '%s' -> Import %s"),
|
||||
*Row.Key, *NewValueText, ImportResult ? TEXT("OK") : TEXT("FAILED"));
|
||||
|
||||
// Refresh rendering
|
||||
// Refresh rendering + force network replication (triggers OnRep on clients)
|
||||
if (UActorComponent* Comp = Cast<UActorComponent>(Target))
|
||||
{
|
||||
Comp->MarkRenderStateDirty();
|
||||
if (AActor* OwnerActor = Comp->GetOwner())
|
||||
{
|
||||
OwnerActor->ForceNetUpdate();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Actor->ForceNetUpdate();
|
||||
}
|
||||
|
||||
// Auto-call OnRep on the server (ImportText bypasses replication notify)
|
||||
if (!Row.CachedProperty->RepNotifyFunc.IsNone())
|
||||
{
|
||||
if (UFunction* RepFunc = Target->FindFunction(Row.CachedProperty->RepNotifyFunc))
|
||||
{
|
||||
Target->ProcessEvent(RepFunc, nullptr);
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Called %s on %s"), *Row.CachedProperty->RepNotifyFunc.ToString(), *Target->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
// Export new value after import (normalized form for undo)
|
||||
@@ -2184,6 +2297,14 @@ void UPS_Editor_MainWidget::OnModeButtonClicked(EPS_Editor_TransformMode Mode)
|
||||
}
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget::ShowLoadingScreen(bool bShow)
|
||||
{
|
||||
if (LoadingOverlay.IsValid())
|
||||
{
|
||||
LoadingOverlay->SetVisibility(bShow ? EVisibility::Visible : EVisibility::Collapsed);
|
||||
}
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget::OnCloseClicked()
|
||||
{
|
||||
if (CloseConfirmOverlay.IsValid())
|
||||
|
||||
@@ -105,11 +105,21 @@ private:
|
||||
TSharedPtr<SWidget> SplineEditButton;
|
||||
TSharedPtr<SWidget> SplineDeletePointButton;
|
||||
|
||||
// Simulate button
|
||||
TSharedPtr<STextBlock> SimulateButtonText;
|
||||
|
||||
// Close confirmation
|
||||
TSharedPtr<SWidget> CloseConfirmOverlay;
|
||||
bool bSceneDirty = false;
|
||||
int32 LastKnownSpawnedCount = 0;
|
||||
|
||||
// Loading screen
|
||||
TSharedPtr<SWidget> LoadingOverlay;
|
||||
|
||||
public:
|
||||
/** Show/hide the loading overlay. */
|
||||
void ShowLoadingScreen(bool bShow);
|
||||
|
||||
// ---- Helpers ----
|
||||
TSharedRef<SWidget> BuildToolbar();
|
||||
TSharedRef<SWidget> BuildPropertiesPanel();
|
||||
|
||||
Reference in New Issue
Block a user