Add ChildMovable interface, preload filter, IsInEditorMode, lightmap init
- Add IPS_Editor_ChildMovable interface for per-child editing of sub-elements (VR placement spots, etc.) with full gizmo, undo, and serialization support - Add "Edit Children" button in toolbar when a ChildMovable actor is selected - Add bPreload tag on SpawnableComponent for briefing/placement phase actors - Add EPS_Editor_SceneLoadFilter enum (All, PreloadOnly, ExcludePreload) to LoadScene for selective loading of preload vs gameplay actors - Add bPreload field to FPS_Editor_ActorData for JSON persistence - Add static IsInEditorMode() getter on SceneLoader (BlueprintPure) - Add Level::InitializeRenderingResources() call on sublevel load for lightmaps - Update Pawn, PlayerController, UndoManager, SceneSerializer, SceneLoader, MainWidget to support ChildMovable alongside existing SplineEditable Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
#include "PS_Editor_UndoManager.h"
|
||||
#include "PS_Editor_SplineActor.h"
|
||||
#include "PS_Editor_SplineEditable.h"
|
||||
#include "PS_Editor_ChildMovable.h"
|
||||
#include "Components/SplineComponent.h"
|
||||
#include "DrawDebugHelpers.h"
|
||||
#include "PS_Editor_ConfirmDialog.h"
|
||||
@@ -380,16 +381,21 @@ void APS_Editor_Pawn::HandleLook(const FInputActionValue& Value)
|
||||
}
|
||||
const FVector ConstrainedDelta = DisplacementAlongAxis * AxisDir;
|
||||
|
||||
// Spline point mode: move the single point instead of actors
|
||||
// Spline point / ChildMovable mode: move the single sub-element instead of actors
|
||||
if (ActiveSplinePointIndex >= 0 && DraggedSplineActor.IsValid())
|
||||
{
|
||||
if (IPS_Editor_SplineEditable* SplineActor = Cast<IPS_Editor_SplineEditable>(DraggedSplineActor.Get()))
|
||||
if (SplinePointDragInitialState.IsValidIndex(ActiveSplinePointIndex))
|
||||
{
|
||||
if (SplinePointDragInitialState.IsValidIndex(ActiveSplinePointIndex))
|
||||
const FVector NewPos = SplinePointDragInitialState[ActiveSplinePointIndex] + ConstrainedDelta;
|
||||
|
||||
if (IPS_Editor_SplineEditable* SplineActor = Cast<IPS_Editor_SplineEditable>(DraggedSplineActor.Get()))
|
||||
{
|
||||
const FVector NewPos = SplinePointDragInitialState[ActiveSplinePointIndex] + ConstrainedDelta;
|
||||
SplineActor->MovePoint(ActiveSplinePointIndex, NewPos);
|
||||
}
|
||||
else if (DraggedSplineActor->GetClass()->ImplementsInterface(UPS_Editor_ChildMovable::StaticClass()))
|
||||
{
|
||||
IPS_Editor_ChildMovable::Execute_MoveChild(DraggedSplineActor.Get(), ActiveSplinePointIndex, NewPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -475,11 +481,15 @@ void APS_Editor_Pawn::HandleLook(const FInputActionValue& Value)
|
||||
{
|
||||
if (ActiveSplinePointIndex >= 0 && DraggedSplineActor.IsValid())
|
||||
{
|
||||
// Following a single spline point
|
||||
// Following a single spline point or child movable element
|
||||
if (IPS_Editor_SplineEditable* SplineActor = Cast<IPS_Editor_SplineEditable>(DraggedSplineActor.Get()))
|
||||
{
|
||||
Gizmo->SetActorLocation(SplineActor->GetPointLocation(ActiveSplinePointIndex));
|
||||
}
|
||||
else if (DraggedSplineActor->GetClass()->ImplementsInterface(UPS_Editor_ChildMovable::StaticClass()))
|
||||
{
|
||||
Gizmo->SetActorLocation(IPS_Editor_ChildMovable::Execute_GetChildLocation(DraggedSplineActor.Get(), ActiveSplinePointIndex));
|
||||
}
|
||||
}
|
||||
else if (Selected.Num() == 1)
|
||||
{
|
||||
@@ -648,6 +658,35 @@ void APS_Editor_Pawn::HandleLeftClickStarted(const FInputActionValue& Value)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for ChildMovable child handle hit
|
||||
if (APS_Editor_PlayerController* EdPC_CM = Cast<APS_Editor_PlayerController>(GetController()))
|
||||
{
|
||||
if (EdPC_CM->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
AActor* ActiveActor = EdPC_CM->GetActivePlacementActor();
|
||||
if (ActiveActor && ActiveActor->GetClass()->ImplementsInterface(UPS_Editor_ChildMovable::StaticClass()))
|
||||
{
|
||||
const int32 ChildIdx = IPS_Editor_ChildMovable::Execute_GetChildIndexUnderCursor(ActiveActor, RayOrigin, RayDir);
|
||||
if (ChildIdx >= 0)
|
||||
{
|
||||
ActiveSplinePointIndex = ChildIdx;
|
||||
DraggedSplineActor = ActiveActor;
|
||||
IPS_Editor_ChildMovable::Execute_HighlightChild(ActiveActor, ChildIdx);
|
||||
|
||||
if (UPS_Editor_SelectionManager* SM = EdPC_CM->GetSelectionManager())
|
||||
{
|
||||
if (APS_Editor_Gizmo* Gizmo = SM->GetGizmo())
|
||||
{
|
||||
Gizmo->SetGizmoActive(true, IPS_Editor_ChildMovable::Execute_GetChildLocation(ActiveActor, ChildIdx));
|
||||
}
|
||||
SM->SetTransformMode(EPS_Editor_TransformMode::Translate);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In SplinePlacement mode: click on spline tube mesh → insert a CP
|
||||
if (APS_Editor_PlayerController* EdPC2 = Cast<APS_Editor_PlayerController>(GetController()))
|
||||
{
|
||||
@@ -804,13 +843,27 @@ void APS_Editor_Pawn::HandleLeftClickCompleted(const FInputActionValue& Value)
|
||||
// Spline point drag: record SplinePointAction
|
||||
if (ActiveSplinePointIndex >= 0 && DraggedSplineActor.IsValid())
|
||||
{
|
||||
TArray<FVector> NewPoints;
|
||||
FString Desc;
|
||||
|
||||
if (IPS_Editor_SplineEditable* SplineActor = Cast<IPS_Editor_SplineEditable>(DraggedSplineActor.Get()))
|
||||
{
|
||||
NewPoints = SplineActor->GetAllPointLocations();
|
||||
Desc = TEXT("Move spline point");
|
||||
}
|
||||
else if (DraggedSplineActor->GetClass()->ImplementsInterface(UPS_Editor_ChildMovable::StaticClass()))
|
||||
{
|
||||
NewPoints = IPS_Editor_ChildMovable::Execute_GetAllChildLocations(DraggedSplineActor.Get());
|
||||
Desc = TEXT("Move child element");
|
||||
}
|
||||
|
||||
if (NewPoints.Num() > 0)
|
||||
{
|
||||
TSharedPtr<FPS_Editor_SplinePointAction> Action = MakeShared<FPS_Editor_SplinePointAction>();
|
||||
Action->SplineActor = Cast<AActor>(SplineActor);
|
||||
Action->SplineActor = DraggedSplineActor.Get();
|
||||
Action->OldPoints = SplinePointDragInitialState;
|
||||
Action->NewPoints = SplineActor->GetAllPointLocations();
|
||||
Action->Description = TEXT("Move spline point");
|
||||
Action->NewPoints = NewPoints;
|
||||
Action->Description = Desc;
|
||||
UM->RecordAction(Action);
|
||||
}
|
||||
}
|
||||
@@ -1439,13 +1492,17 @@ void APS_Editor_Pawn::BeginGizmoDrag(EPS_Editor_GizmoAxis Axis)
|
||||
}
|
||||
}
|
||||
|
||||
// Store initial spline state if editing a point
|
||||
// Store initial sub-element state if editing a point/child
|
||||
if (ActiveSplinePointIndex >= 0 && DraggedSplineActor.IsValid())
|
||||
{
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(DraggedSplineActor.Get()))
|
||||
{
|
||||
SplinePointDragInitialState = Spline->GetAllPointLocations();
|
||||
}
|
||||
else if (DraggedSplineActor->GetClass()->ImplementsInterface(UPS_Editor_ChildMovable::StaticClass()))
|
||||
{
|
||||
SplinePointDragInitialState = IPS_Editor_ChildMovable::Execute_GetAllChildLocations(DraggedSplineActor.Get());
|
||||
}
|
||||
}
|
||||
|
||||
SetCameraMode(EPS_Editor_CameraMode::GizmoDrag);
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include "Engine/LevelStreamingDynamic.h"
|
||||
#include "Engine/PostProcessVolume.h"
|
||||
#include "PS_Editor_CameraStart.h"
|
||||
#include "PS_Editor_ChildMovable.h"
|
||||
#include "PS_Editor_SceneLoader.h"
|
||||
#include "PS_Editor_HUD.h"
|
||||
#include "PS_Editor_MainWidget.h"
|
||||
#include "Camera/PlayerCameraManager.h"
|
||||
@@ -32,6 +34,7 @@ void APS_Editor_PlayerController::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
UPS_Editor_SceneLoader::bIsEditorModeActive = true;
|
||||
SetInputMode(FInputModeGameAndUI().SetHideCursorDuringCapture(false));
|
||||
|
||||
// AI logic will be stopped per-pawn when spawned (see SpawnManager)
|
||||
@@ -190,6 +193,7 @@ void APS_Editor_PlayerController::CloseEditor()
|
||||
}
|
||||
|
||||
// Notify actors: leaving editor mode
|
||||
UPS_Editor_SceneLoader::bIsEditorModeActive = false;
|
||||
if (SpawnManager)
|
||||
{
|
||||
SpawnManager->NotifyEditorModeChanged(false);
|
||||
@@ -303,20 +307,29 @@ void APS_Editor_PlayerController::BeginPointPlacement(TSubclassOf<AActor> InActo
|
||||
|
||||
void APS_Editor_PlayerController::BeginPointPlacementAppend(AActor* ExistingActor)
|
||||
{
|
||||
if (!ExistingActor || !ExistingActor->Implements<UPS_Editor_PointPlaceable>()) return;
|
||||
if (!ExistingActor) return;
|
||||
|
||||
const bool bIsPointPlaceable = ExistingActor->Implements<UPS_Editor_PointPlaceable>();
|
||||
const bool bIsChildMovable = ExistingActor->GetClass()->ImplementsInterface(UPS_Editor_ChildMovable::StaticClass());
|
||||
if (!bIsPointPlaceable && !bIsChildMovable) return;
|
||||
|
||||
ActivePlacementActor = ExistingActor;
|
||||
bAppendingToActor = true;
|
||||
EditorMode = EPS_Editor_EditorMode::SplinePlacement;
|
||||
|
||||
// Store initial state for undo (spline-specific)
|
||||
// Store initial state and show handles
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(ExistingActor))
|
||||
{
|
||||
AppendInitialPoints = Spline->GetAllPointLocations();
|
||||
Spline->SetHandlesVisible(true);
|
||||
}
|
||||
else if (bIsChildMovable)
|
||||
{
|
||||
AppendInitialPoints = IPS_Editor_ChildMovable::Execute_GetAllChildLocations(ExistingActor);
|
||||
IPS_Editor_ChildMovable::Execute_SetChildHandlesVisible(ExistingActor, true);
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Appending points to %s"), *ExistingActor->GetName());
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Editing sub-elements of %s"), *ExistingActor->GetName());
|
||||
}
|
||||
|
||||
void APS_Editor_PlayerController::FinishPointPlacement()
|
||||
@@ -332,6 +345,11 @@ void APS_Editor_PlayerController::FinishPointPlacement()
|
||||
Spline->SetHandlesVisible(false);
|
||||
Spline->ClearHandleHighlight();
|
||||
}
|
||||
else if (PlacementActor && PlacementActor->GetClass()->ImplementsInterface(UPS_Editor_ChildMovable::StaticClass()))
|
||||
{
|
||||
IPS_Editor_ChildMovable::Execute_SetChildHandlesVisible(PlacementActor, false);
|
||||
IPS_Editor_ChildMovable::Execute_ClearChildHighlight(PlacementActor);
|
||||
}
|
||||
|
||||
if (PlacementActor && SelectionManager)
|
||||
{
|
||||
@@ -452,6 +470,9 @@ void APS_Editor_PlayerController::DisableSublevelPostProcessMaterials()
|
||||
ULevel* Level = CurrentSublevel->GetLoadedLevel();
|
||||
if (!Level) return;
|
||||
|
||||
// Force lighting/rendering resources to initialize properly for baked lightmaps
|
||||
Level->InitializeRenderingResources();
|
||||
|
||||
APS_Editor_CameraStart* CameraStart = nullptr;
|
||||
|
||||
for (AActor* Actor : Level->Actors)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "PS_Editor_UndoManager.h"
|
||||
#include "PS_Editor_SplineEditable.h"
|
||||
#include "PS_Editor_ChildMovable.h"
|
||||
#include "UObject/UnrealType.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
|
||||
@@ -9,6 +10,10 @@ void FPS_Editor_SplinePointAction::Execute()
|
||||
{
|
||||
Spline->SetAllPointLocations(NewPoints);
|
||||
}
|
||||
else if (SplineActor.IsValid() && SplineActor->GetClass()->ImplementsInterface(UPS_Editor_ChildMovable::StaticClass()))
|
||||
{
|
||||
IPS_Editor_ChildMovable::Execute_SetAllChildLocations(SplineActor.Get(), NewPoints);
|
||||
}
|
||||
}
|
||||
|
||||
void FPS_Editor_SplinePointAction::Undo()
|
||||
@@ -17,6 +22,10 @@ void FPS_Editor_SplinePointAction::Undo()
|
||||
{
|
||||
Spline->SetAllPointLocations(OldPoints);
|
||||
}
|
||||
else if (SplineActor.IsValid() && SplineActor->GetClass()->ImplementsInterface(UPS_Editor_ChildMovable::StaticClass()))
|
||||
{
|
||||
IPS_Editor_ChildMovable::Execute_SetAllChildLocations(SplineActor.Get(), OldPoints);
|
||||
}
|
||||
}
|
||||
|
||||
void FPS_Editor_PropertyAction::ApplyValue(const FString& ValueStr)
|
||||
|
||||
@@ -25,6 +25,10 @@ struct FPS_Editor_ActorData
|
||||
/** Custom property values: key = "ComponentName.PropertyName", value = text-serialized value. */
|
||||
UPROPERTY()
|
||||
TMap<FString, FString> CustomProperties;
|
||||
|
||||
/** True if this actor is tagged as preload (briefing/placement phase). */
|
||||
UPROPERTY()
|
||||
bool bPreload = false;
|
||||
};
|
||||
|
||||
/** Serialized data for a complete scene. */
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "PS_Editor_SceneData.h"
|
||||
#include "PS_Editor_EditableComponent.h"
|
||||
#include "PS_Editor_EditableInterface.h"
|
||||
#include "PS_Editor_ChildMovable.h"
|
||||
#include "PS_Editor_SpawnableComponent.h"
|
||||
#include "PS_Editor_SplineActor.h"
|
||||
#include "PS_Editor_SplineEditable.h"
|
||||
@@ -12,7 +13,7 @@
|
||||
#include "UObject/UnrealType.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
|
||||
bool UPS_Editor_SceneLoader::LoadScene(const UObject* WorldContextObject, const FString& SceneName, TArray<AActor*>& OutActors, bool bStripEditorComponents)
|
||||
bool UPS_Editor_SceneLoader::LoadScene(const UObject* WorldContextObject, const FString& SceneName, TArray<AActor*>& OutActors, bool bStripEditorComponents, EPS_Editor_SceneLoadFilter Filter)
|
||||
{
|
||||
OutActors.Empty();
|
||||
|
||||
@@ -44,7 +45,7 @@ bool UPS_Editor_SceneLoader::LoadScene(const UObject* WorldContextObject, const
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!LoadSceneFromData(World, SceneData, OutActors))
|
||||
if (!LoadSceneFromData(World, SceneData, OutActors, Filter))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -87,7 +88,7 @@ bool UPS_Editor_SceneLoader::LoadScene(const UObject* WorldContextObject, const
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UPS_Editor_SceneLoader::LoadSceneFromData(UWorld* World, const FPS_Editor_SceneData& SceneData, TArray<AActor*>& OutActors)
|
||||
bool UPS_Editor_SceneLoader::LoadSceneFromData(UWorld* World, const FPS_Editor_SceneData& SceneData, TArray<AActor*>& OutActors, EPS_Editor_SceneLoadFilter Filter)
|
||||
{
|
||||
if (!World)
|
||||
{
|
||||
@@ -99,7 +100,7 @@ bool UPS_Editor_SceneLoader::LoadSceneFromData(UWorld* World, const FPS_Editor_S
|
||||
bIsCurrentlyLoading = true;
|
||||
for (const FPS_Editor_ActorData& ActorData : SceneData.Actors)
|
||||
{
|
||||
AActor* SpawnedActor = SpawnActorFromData(World, ActorData);
|
||||
AActor* SpawnedActor = SpawnActorFromData(World, ActorData, Filter);
|
||||
if (SpawnedActor)
|
||||
{
|
||||
OutActors.Add(SpawnedActor);
|
||||
@@ -113,8 +114,12 @@ bool UPS_Editor_SceneLoader::LoadSceneFromData(UWorld* World, const FPS_Editor_S
|
||||
return SpawnedCount > 0;
|
||||
}
|
||||
|
||||
AActor* UPS_Editor_SceneLoader::SpawnActorFromData(UWorld* World, const FPS_Editor_ActorData& ActorData)
|
||||
AActor* UPS_Editor_SceneLoader::SpawnActorFromData(UWorld* World, const FPS_Editor_ActorData& ActorData, EPS_Editor_SceneLoadFilter Filter)
|
||||
{
|
||||
// Apply preload filter
|
||||
if (Filter == EPS_Editor_SceneLoadFilter::PreloadOnly && !ActorData.bPreload) return nullptr;
|
||||
if (Filter == EPS_Editor_SceneLoadFilter::ExcludePreload && ActorData.bPreload) return nullptr;
|
||||
|
||||
// Load class (supports both Blueprint and C++ classes)
|
||||
UClass* LoadedClass = LoadObject<UClass>(nullptr, *ActorData.ClassPath);
|
||||
if (!LoadedClass)
|
||||
@@ -156,6 +161,12 @@ AActor* UPS_Editor_SceneLoader::SpawnActorFromData(UWorld* World, const FPS_Edit
|
||||
Spline->ImportFromCustomProperties(ActorData.CustomProperties);
|
||||
}
|
||||
|
||||
// Import child movable data
|
||||
if (SpawnedActor->GetClass()->ImplementsInterface(UPS_Editor_ChildMovable::StaticClass()))
|
||||
{
|
||||
IPS_Editor_ChildMovable::Execute_ImportChildLocations(SpawnedActor, ActorData.CustomProperties);
|
||||
}
|
||||
|
||||
// Notify the actor that all properties are ready
|
||||
if (SpawnedActor->GetClass()->ImplementsInterface(UPS_Editor_EditableInterface::StaticClass()))
|
||||
{
|
||||
@@ -211,6 +222,7 @@ void UPS_Editor_SceneLoader::ApplyCustomProperties(AActor* Actor, const TMap<FSt
|
||||
}
|
||||
|
||||
bool UPS_Editor_SceneLoader::bIsCurrentlyLoading = false;
|
||||
bool UPS_Editor_SceneLoader::bIsEditorModeActive = false;
|
||||
|
||||
bool UPS_Editor_SceneLoader::IsLoadingFromScene(const AActor* Actor)
|
||||
{
|
||||
|
||||
@@ -5,6 +5,15 @@
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "PS_Editor_SceneLoader.generated.h"
|
||||
|
||||
/** Filter for selective scene loading. */
|
||||
UENUM(BlueprintType)
|
||||
enum class EPS_Editor_SceneLoadFilter : uint8
|
||||
{
|
||||
All, /** Load all actors. */
|
||||
PreloadOnly, /** Load only actors tagged as preload (briefing/placement). */
|
||||
ExcludePreload /** Load all actors except those tagged as preload. */
|
||||
};
|
||||
|
||||
/**
|
||||
* Standalone scene loader for PS_Editor.
|
||||
* Loads a JSON scene file and spawns all actors with their properties and spline data.
|
||||
@@ -37,13 +46,15 @@ public:
|
||||
* @return True if the scene was loaded successfully.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor", meta = (WorldContext = "WorldContextObject"))
|
||||
static bool LoadScene(const UObject* WorldContextObject, const FString& SceneName, TArray<AActor*>& OutActors, bool bStripEditorComponents = true);
|
||||
static bool LoadScene(const UObject* WorldContextObject, const FString& SceneName, TArray<AActor*>& OutActors,
|
||||
bool bStripEditorComponents = true, EPS_Editor_SceneLoadFilter Filter = EPS_Editor_SceneLoadFilter::All);
|
||||
|
||||
/**
|
||||
* Load scene from an already-parsed SceneData struct.
|
||||
* Useful if you want to parse the JSON yourself or modify data before spawning.
|
||||
*/
|
||||
static bool LoadSceneFromData(UWorld* World, const FPS_Editor_SceneData& SceneData, TArray<AActor*>& OutActors);
|
||||
static bool LoadSceneFromData(UWorld* World, const FPS_Editor_SceneData& SceneData, TArray<AActor*>& OutActors,
|
||||
EPS_Editor_SceneLoadFilter Filter = EPS_Editor_SceneLoadFilter::All);
|
||||
|
||||
/**
|
||||
* Get the directory where scenes are stored.
|
||||
@@ -80,6 +91,13 @@ public:
|
||||
/** Static flag: true while SceneLoader or SceneSerializer is spawning actors. */
|
||||
static bool bIsCurrentlyLoading;
|
||||
|
||||
/** Static flag: true while the PS_Editor runtime editor is active. */
|
||||
static bool bIsEditorModeActive;
|
||||
|
||||
/** Returns true if the PS_Editor runtime editor is currently active. Callable from any BP. */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "PS Editor")
|
||||
static bool IsInEditorMode() { return bIsEditorModeActive; }
|
||||
|
||||
/**
|
||||
* Get all saved scenes for the current level.
|
||||
*/
|
||||
@@ -87,8 +105,8 @@ public:
|
||||
static TArray<FString> GetSavedSceneNamesForCurrentLevel(const UObject* WorldContextObject);
|
||||
|
||||
private:
|
||||
/** Spawn a single actor from saved data and apply all properties. */
|
||||
static AActor* SpawnActorFromData(UWorld* World, const FPS_Editor_ActorData& ActorData);
|
||||
/** Spawn a single actor from saved data and apply all properties. Returns nullptr if filtered out. */
|
||||
static AActor* SpawnActorFromData(UWorld* World, const FPS_Editor_ActorData& ActorData, EPS_Editor_SceneLoadFilter Filter);
|
||||
|
||||
/** Apply custom editable properties to a spawned actor. */
|
||||
static void ApplyCustomProperties(AActor* Actor, const TMap<FString, FString>& CustomProperties);
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "PS_Editor_SelectionManager.h"
|
||||
#include "PS_Editor_EditableComponent.h"
|
||||
#include "PS_Editor_EditableInterface.h"
|
||||
#include "PS_Editor_SpawnableComponent.h"
|
||||
#include "PS_Editor_ChildMovable.h"
|
||||
#include "PS_Editor_PlayerController.h"
|
||||
#include "PS_Editor_SceneLoader.h"
|
||||
#include "PS_Editor_SplineActor.h"
|
||||
@@ -49,6 +51,12 @@ bool UPS_Editor_SceneSerializer::SaveScene(const FString& SceneName, UPS_Editor_
|
||||
ActorData.Rotation = Actor->GetActorRotation();
|
||||
ActorData.Scale = Actor->GetActorScale3D();
|
||||
|
||||
// Save preload tag
|
||||
if (UPS_Editor_SpawnableComponent* SpawnComp = Actor->FindComponentByClass<UPS_Editor_SpawnableComponent>())
|
||||
{
|
||||
ActorData.bPreload = SpawnComp->bPreload;
|
||||
}
|
||||
|
||||
// Export spline data
|
||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(Actor))
|
||||
{
|
||||
@@ -57,6 +65,12 @@ bool UPS_Editor_SceneSerializer::SaveScene(const FString& SceneName, UPS_Editor_
|
||||
Spline->GetNumPoints(), *ActorData.ClassPath);
|
||||
}
|
||||
|
||||
// Export child movable data
|
||||
if (Actor->GetClass()->ImplementsInterface(UPS_Editor_ChildMovable::StaticClass()))
|
||||
{
|
||||
IPS_Editor_ChildMovable::Execute_ExportChildLocations(Actor, ActorData.CustomProperties);
|
||||
}
|
||||
|
||||
// Export custom editable properties
|
||||
if (UPS_Editor_EditableComponent* EditComp = Actor->FindComponentByClass<UPS_Editor_EditableComponent>())
|
||||
{
|
||||
@@ -171,6 +185,15 @@ bool UPS_Editor_SceneSerializer::LoadScene(const FString& SceneName, UPS_Editor_
|
||||
AActor* SpawnedActor = SpawnManager->SpawnActorDirect(LoadedClass, ActorData.Location, ActorData.Rotation, ActorData.Scale);
|
||||
if (SpawnedActor)
|
||||
{
|
||||
// Restore preload tag
|
||||
if (ActorData.bPreload)
|
||||
{
|
||||
if (UPS_Editor_SpawnableComponent* SpawnComp = SpawnedActor->FindComponentByClass<UPS_Editor_SpawnableComponent>())
|
||||
{
|
||||
SpawnComp->bPreload = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Import custom properties
|
||||
if (ActorData.CustomProperties.Num() > 0)
|
||||
{
|
||||
@@ -222,6 +245,12 @@ bool UPS_Editor_SceneSerializer::LoadScene(const FString& SceneName, UPS_Editor_
|
||||
Spline->ImportFromCustomProperties(ActorData.CustomProperties);
|
||||
}
|
||||
|
||||
// Import child movable data
|
||||
if (SpawnedActor->GetClass()->ImplementsInterface(UPS_Editor_ChildMovable::StaticClass()))
|
||||
{
|
||||
IPS_Editor_ChildMovable::Execute_ImportChildLocations(SpawnedActor, ActorData.CustomProperties);
|
||||
}
|
||||
|
||||
// Notify the actor that properties were loaded and we are in editor mode
|
||||
if (SpawnedActor->GetClass()->ImplementsInterface(UPS_Editor_EditableInterface::StaticClass()))
|
||||
{
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Interface.h"
|
||||
#include "PS_Editor_ChildMovable.generated.h"
|
||||
|
||||
UINTERFACE(BlueprintType, meta = (DisplayName = "PS Editor Child Movable"))
|
||||
class PS_EDITOR_API UPS_Editor_ChildMovable : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for actors containing sub-elements (child actors, components)
|
||||
* that can be individually selected and moved in the PS_Editor runtime editor.
|
||||
*
|
||||
* Implement this in your Blueprint to enable per-child editing.
|
||||
* The plugin handles selection, gizmo, undo, and serialization.
|
||||
*
|
||||
* Example: VR placement root with 10 movable placement spots.
|
||||
*/
|
||||
class PS_EDITOR_API IPS_Editor_ChildMovable
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Number of movable sub-elements. */
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS Editor|Child Movable")
|
||||
int32 GetMovableChildCount() const;
|
||||
|
||||
/** World location of the sub-element at the given index. */
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS Editor|Child Movable")
|
||||
FVector GetChildLocation(int32 Index) const;
|
||||
|
||||
/** Move the sub-element at the given index to a new world position. */
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS Editor|Child Movable")
|
||||
void MoveChild(int32 Index, const FVector& NewWorldPosition);
|
||||
|
||||
/** Return all sub-element positions (used for undo snapshots). */
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS Editor|Child Movable")
|
||||
TArray<FVector> GetAllChildLocations() const;
|
||||
|
||||
/** Restore all sub-element positions (used for undo). */
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS Editor|Child Movable")
|
||||
void SetAllChildLocations(const TArray<FVector>& Positions);
|
||||
|
||||
/** Index of the sub-element under the cursor ray (-1 if none).
|
||||
* Implement with sphere-ray intersection or any hit detection that suits your visuals. */
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS Editor|Child Movable")
|
||||
int32 GetChildIndexUnderCursor(const FVector& RayOrigin, const FVector& RayDirection) const;
|
||||
|
||||
/** Show or hide visual handles for the sub-elements. */
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS Editor|Child Movable")
|
||||
void SetChildHandlesVisible(bool bVisible);
|
||||
|
||||
/** Highlight a specific sub-element (e.g. change handle color). */
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS Editor|Child Movable")
|
||||
void HighlightChild(int32 Index);
|
||||
|
||||
/** Clear all sub-element highlights. */
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS Editor|Child Movable")
|
||||
void ClearChildHighlight();
|
||||
|
||||
/** Export sub-element positions to the CustomProperties map for scene saving. */
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS Editor|Child Movable")
|
||||
void ExportChildLocations(TMap<FString, FString>& OutProperties) const;
|
||||
|
||||
/** Import sub-element positions from the CustomProperties map on scene load. */
|
||||
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "PS Editor|Child Movable")
|
||||
void ImportChildLocations(const TMap<FString, FString>& Properties);
|
||||
};
|
||||
@@ -47,4 +47,9 @@ public:
|
||||
/** If true, scaling is always uniform on all axes. Dragging any single axis scales all three equally. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawnable")
|
||||
bool bUniformScaleOnly = false;
|
||||
|
||||
/** If true, this actor belongs to the preload/briefing phase.
|
||||
* Use scene loading filters to load only preload actors or exclude them. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spawnable")
|
||||
bool bPreload = false;
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "PS_Editor_SpawnableComponent.h"
|
||||
#include "PS_Editor_SplineActor.h"
|
||||
#include "PS_Editor_SplineEditable.h"
|
||||
#include "PS_Editor_ChildMovable.h"
|
||||
#include "PS_Editor_Pawn.h"
|
||||
#include "PS_Editor_PointPlaceable.h"
|
||||
#include "Widgets/SBoxPanel.h"
|
||||
@@ -222,6 +223,39 @@ TSharedRef<SWidget> UPS_Editor_MainWidget::RebuildWidget()
|
||||
.ColorAndOpacity(FLinearColor(1.0f, 0.6f, 0.0f))
|
||||
]
|
||||
]
|
||||
+ SHorizontalBox::Slot().AutoWidth().Padding(8.0f, 0.0f, 0.0f, 0.0f).VAlign(VAlign_Center)
|
||||
[
|
||||
SAssignNew(ChildEditButton, SButton)
|
||||
.Visibility(EVisibility::Collapsed)
|
||||
.OnClicked_Lambda([this]() -> FReply
|
||||
{
|
||||
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetOwningPlayer()))
|
||||
{
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
EditorPC->FinishPointPlacement();
|
||||
}
|
||||
else if (UPS_Editor_SelectionManager* SelMgr = SelectionManager.Get())
|
||||
{
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SelMgr->GetSelectedActors())
|
||||
{
|
||||
if (Weak.IsValid() && Weak->GetClass()->ImplementsInterface(UPS_Editor_ChildMovable::StaticClass()))
|
||||
{
|
||||
EditorPC->BeginPointPlacementAppend(Weak.Get());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return FReply::Handled();
|
||||
})
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Edit Children")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 10))
|
||||
.ColorAndOpacity(FLinearColor(0.3f, 0.8f, 1.0f))
|
||||
]
|
||||
]
|
||||
+ SHorizontalBox::Slot().AutoWidth().Padding(8.0f, 0.0f, 0.0f, 0.0f).VAlign(VAlign_Center)
|
||||
[
|
||||
SAssignNew(SplineDeletePointButton, SButton)
|
||||
@@ -831,6 +865,7 @@ TSharedRef<SWidget> UPS_Editor_MainWidget::BuildSceneButtons()
|
||||
}
|
||||
|
||||
// Notify all spawned actors: leaving editor mode
|
||||
UPS_Editor_SceneLoader::bIsEditorModeActive = false;
|
||||
if (UPS_Editor_SpawnManager* PlaySM2 = EditorPC->GetSpawnManager())
|
||||
{
|
||||
PlaySM2->NotifyEditorModeChanged(false);
|
||||
@@ -1425,8 +1460,9 @@ void UPS_Editor_MainWidget::RefreshPropertiesFromSelection()
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if a spline is selected (for Edit button)
|
||||
// Determine if a spline or child-movable is selected (for Edit buttons)
|
||||
bool bSplineSelected = false;
|
||||
bool bChildMovableSelected = false;
|
||||
if (CurrentEditorMode == EPS_Editor_EditorMode::Normal && SM->IsAnythingSelected())
|
||||
{
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SM->GetSelectedActors())
|
||||
@@ -1436,6 +1472,11 @@ void UPS_Editor_MainWidget::RefreshPropertiesFromSelection()
|
||||
bSplineSelected = true;
|
||||
break;
|
||||
}
|
||||
if (Weak.IsValid() && Weak->GetClass()->ImplementsInterface(UPS_Editor_ChildMovable::StaticClass()))
|
||||
{
|
||||
bChildMovableSelected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1461,9 +1502,22 @@ void UPS_Editor_MainWidget::RefreshPropertiesFromSelection()
|
||||
HelpBarText->SetColorAndOpacity(FLinearColor(1.0f, 0.6f, 0.0f));
|
||||
}
|
||||
if (SplineEditButton.IsValid()) SplineEditButton->SetVisibility(EVisibility::Collapsed);
|
||||
if (ChildEditButton.IsValid()) ChildEditButton->SetVisibility(EVisibility::Collapsed);
|
||||
if (SplineDeletePointButton.IsValid()) SplineDeletePointButton->SetVisibility(bPointSelected ? EVisibility::Visible : EVisibility::Collapsed);
|
||||
if (SplineFinishButton.IsValid()) SplineFinishButton->SetVisibility(EVisibility::Visible);
|
||||
}
|
||||
else if (bChildMovableSelected)
|
||||
{
|
||||
if (HelpBarText.IsValid())
|
||||
{
|
||||
HelpBarText->SetText(FText::FromString(TEXT("Child-movable selected — click Edit Children to move sub-elements")));
|
||||
HelpBarText->SetColorAndOpacity(FLinearColor(0.3f, 0.8f, 1.0f));
|
||||
}
|
||||
if (SplineEditButton.IsValid()) SplineEditButton->SetVisibility(EVisibility::Collapsed);
|
||||
if (ChildEditButton.IsValid()) ChildEditButton->SetVisibility(EVisibility::Visible);
|
||||
if (SplineDeletePointButton.IsValid()) SplineDeletePointButton->SetVisibility(EVisibility::Collapsed);
|
||||
if (SplineFinishButton.IsValid()) SplineFinishButton->SetVisibility(EVisibility::Collapsed);
|
||||
}
|
||||
else if (bSplineSelected)
|
||||
{
|
||||
if (HelpBarText.IsValid())
|
||||
@@ -1483,6 +1537,7 @@ void UPS_Editor_MainWidget::RefreshPropertiesFromSelection()
|
||||
HelpBarText->SetColorAndOpacity(FLinearColor(0.6f, 0.6f, 0.6f));
|
||||
}
|
||||
if (SplineEditButton.IsValid()) SplineEditButton->SetVisibility(EVisibility::Collapsed);
|
||||
if (ChildEditButton.IsValid()) ChildEditButton->SetVisibility(EVisibility::Collapsed);
|
||||
if (SplineDeletePointButton.IsValid()) SplineDeletePointButton->SetVisibility(EVisibility::Collapsed);
|
||||
if (SplineFinishButton.IsValid()) SplineFinishButton->SetVisibility(EVisibility::Collapsed);
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@ private:
|
||||
TSharedPtr<SWidget> SplineFinishButton;
|
||||
TSharedPtr<SWidget> SplineEditButton;
|
||||
TSharedPtr<SWidget> SplineDeletePointButton;
|
||||
TSharedPtr<SWidget> ChildEditButton;
|
||||
|
||||
// Simulate button
|
||||
TSharedPtr<STextBlock> SimulateButtonText;
|
||||
|
||||
Reference in New Issue
Block a user