diff --git a/Unreal/Plugins/PS_Editor/Content/Cubeezrzea.uasset b/Unreal/Plugins/PS_Editor/Content/Cubeezrzea.uasset index 5b3fc78..6a3523d 100644 Binary files a/Unreal/Plugins/PS_Editor/Content/Cubeezrzea.uasset and b/Unreal/Plugins/PS_Editor/Content/Cubeezrzea.uasset differ diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.cpp index d3196e6..859987c 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.cpp @@ -2,6 +2,7 @@ #include "PS_Editor_SelectionManager.h" #include "PS_Editor_UndoManager.h" #include "PS_Editor_SpawnManager.h" +#include "PS_Editor_SceneSerializer.h" #include "PS_Editor_Gizmo.h" #include "PS_Editor_Pawn.h" @@ -28,6 +29,9 @@ void APS_Editor_PlayerController::BeginPlay() UPS_Editor_SpawnCatalog* LoadedCatalog = SpawnCatalogAsset.LoadSynchronous(); SpawnManager->Initialize(this, LoadedCatalog); + SceneSerializer = NewObject(this); + SceneSerializer->SetSelectionManager(SelectionManager); + // After undo/redo, deselect hidden actors and refresh gizmo UndoManager->OnUndoRedo.AddWeakLambda(this, [this]() { diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.h index 37385e7..1be1722 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.h @@ -8,6 +8,7 @@ class UPS_Editor_SelectionManager; class UPS_Editor_UndoManager; class UPS_Editor_SpawnManager; class UPS_Editor_SpawnCatalog; +class UPS_Editor_SceneSerializer; /** * Player controller for PS_Editor runtime editing. @@ -30,6 +31,9 @@ public: UFUNCTION(BlueprintCallable, Category = "PS Editor") UPS_Editor_SpawnManager* GetSpawnManager() const { return SpawnManager; } + UFUNCTION(BlueprintCallable, Category = "PS Editor") + UPS_Editor_SceneSerializer* GetSceneSerializer() const { return SceneSerializer; } + /** The spawn catalog DataAsset to load. Set this in the Blueprint or World Settings. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS Editor") TSoftObjectPtr SpawnCatalogAsset; @@ -48,5 +52,8 @@ private: UPROPERTY() TObjectPtr SpawnManager; + UPROPERTY() + TObjectPtr SceneSerializer; + void HandleEditorClick(FVector2D ScreenPos, bool bAdditive); }; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/PS_Editor.Build.cs b/Unreal/Plugins/PS_Editor/Source/PS_Editor/PS_Editor.Build.cs index 4b34e74..cb2a12f 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/PS_Editor.Build.cs +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/PS_Editor.Build.cs @@ -16,6 +16,7 @@ public class PS_Editor : ModuleRules PublicIncludePaths.Add(Path.Combine(SourceDir, "Selection")); PublicIncludePaths.Add(Path.Combine(SourceDir, "Gizmo")); PublicIncludePaths.Add(Path.Combine(SourceDir, "Spawn")); + PublicIncludePaths.Add(Path.Combine(SourceDir, "Serialization")); PublicDependencyModuleNames.AddRange(new string[] { @@ -24,7 +25,9 @@ public class PS_Editor : ModuleRules "Engine", "InputCore", "EnhancedInput", - "UMG" + "UMG", + "Json", + "JsonUtilities" }); PrivateDependencyModuleNames.AddRange(new string[] diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneData.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneData.h new file mode 100644 index 0000000..4627fc4 --- /dev/null +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneData.h @@ -0,0 +1,43 @@ +#pragma once + +#include "CoreMinimal.h" +#include "PS_Editor_SceneData.generated.h" + +/** Serialized data for a single spawned actor. */ +USTRUCT() +struct FPS_Editor_ActorData +{ + GENERATED_BODY() + + /** Class path (e.g. "/Game/BP_Cube.BP_Cube_C"). */ + UPROPERTY() + FString ClassPath; + + UPROPERTY() + FVector Location = FVector::ZeroVector; + + UPROPERTY() + FRotator Rotation = FRotator::ZeroRotator; + + UPROPERTY() + FVector Scale = FVector::OneVector; +}; + +/** Serialized data for a complete scene. */ +USTRUCT() +struct FPS_Editor_SceneData +{ + GENERATED_BODY() + + UPROPERTY() + int32 Version = 1; + + UPROPERTY() + FString SceneName; + + UPROPERTY() + FString Timestamp; + + UPROPERTY() + TArray Actors; +}; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneSerializer.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneSerializer.cpp new file mode 100644 index 0000000..3f9eeaf --- /dev/null +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneSerializer.cpp @@ -0,0 +1,165 @@ +#include "PS_Editor_SceneSerializer.h" +#include "PS_Editor_SpawnManager.h" +#include "PS_Editor_SelectionManager.h" +#include "JsonObjectConverter.h" +#include "Misc/FileHelper.h" +#include "Misc/Paths.h" +#include "HAL/PlatformFileManager.h" + +bool UPS_Editor_SceneSerializer::SaveScene(const FString& SceneName, UPS_Editor_SpawnManager* SpawnManager) +{ + if (!SpawnManager || SceneName.IsEmpty()) + { + return false; + } + + // Build scene data from currently spawned actors + FPS_Editor_SceneData SceneData; + SceneData.SceneName = SceneName; + SceneData.Timestamp = FDateTime::Now().ToString(); + + const TArray>& SpawnedActors = SpawnManager->GetSpawnedActors(); + for (const TWeakObjectPtr& Weak : SpawnedActors) + { + AActor* Actor = Weak.Get(); + if (!Actor || Actor->IsHidden()) + { + continue; // Skip destroyed or soft-deleted actors + } + + FPS_Editor_ActorData ActorData; + ActorData.ClassPath = Actor->GetClass()->GetPathName(); + ActorData.Location = Actor->GetActorLocation(); + ActorData.Rotation = Actor->GetActorRotation(); + ActorData.Scale = Actor->GetActorScale3D(); + + SceneData.Actors.Add(ActorData); + } + + // Convert to JSON + FString JsonString; + if (!FJsonObjectConverter::UStructToJsonObjectString(SceneData, JsonString, 0, 0, 0, nullptr, true)) + { + UE_LOG(LogTemp, Error, TEXT("PS_Editor: Failed to serialize scene to JSON")); + return false; + } + + // Ensure directory exists + const FString Directory = GetScenesDirectory(); + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + if (!PlatformFile.DirectoryExists(*Directory)) + { + PlatformFile.CreateDirectoryTree(*Directory); + } + + // Write file + const FString FilePath = GetSceneFilePath(SceneName); + if (!FFileHelper::SaveStringToFile(JsonString, *FilePath)) + { + UE_LOG(LogTemp, Error, TEXT("PS_Editor: Failed to write scene file: %s"), *FilePath); + return false; + } + + UE_LOG(LogTemp, Log, TEXT("PS_Editor: Scene saved: %s (%d actors) -> %s"), *SceneName, SceneData.Actors.Num(), *FilePath); + return true; +} + +bool UPS_Editor_SceneSerializer::LoadScene(const FString& SceneName, UPS_Editor_SpawnManager* SpawnManager) +{ + if (!SpawnManager || SceneName.IsEmpty()) + { + return false; + } + + // Read file + const FString FilePath = GetSceneFilePath(SceneName); + FString JsonString; + if (!FFileHelper::LoadFileToString(JsonString, *FilePath)) + { + UE_LOG(LogTemp, Error, TEXT("PS_Editor: Failed to read scene file: %s"), *FilePath); + return false; + } + + // Parse JSON + FPS_Editor_SceneData SceneData; + if (!FJsonObjectConverter::JsonObjectStringToUStruct(JsonString, &SceneData, 0, 0)) + { + UE_LOG(LogTemp, Error, TEXT("PS_Editor: Failed to parse scene JSON")); + return false; + } + + // Clear existing spawned actors + ClearScene(SpawnManager); + + // Re-spawn actors from saved data + int32 SpawnedCount = 0; + for (const FPS_Editor_ActorData& ActorData : SceneData.Actors) + { + UClass* LoadedClass = LoadObject(nullptr, *ActorData.ClassPath); + if (!LoadedClass) + { + UE_LOG(LogTemp, Warning, TEXT("PS_Editor: Failed to load class: %s"), *ActorData.ClassPath); + continue; + } + + AActor* SpawnedActor = SpawnManager->SpawnActorDirect(LoadedClass, ActorData.Location, ActorData.Rotation, ActorData.Scale); + if (SpawnedActor) + { + SpawnedCount++; + } + } + + UE_LOG(LogTemp, Log, TEXT("PS_Editor: Scene loaded: %s (%d actors)"), *SceneName, SpawnedCount); + return true; +} + +TArray UPS_Editor_SceneSerializer::GetSavedSceneNames() const +{ + TArray SceneNames; + const FString Directory = GetScenesDirectory(); + + TArray Files; + IFileManager::Get().FindFiles(Files, *FPaths::Combine(Directory, TEXT("*.json")), true, false); + + for (const FString& File : Files) + { + SceneNames.Add(FPaths::GetBaseFilename(File)); + } + + SceneNames.Sort(); + return SceneNames; +} + +bool UPS_Editor_SceneSerializer::DeleteScene(const FString& SceneName) +{ + const FString FilePath = GetSceneFilePath(SceneName); + if (IFileManager::Get().Delete(*FilePath)) + { + UE_LOG(LogTemp, Log, TEXT("PS_Editor: Scene deleted: %s"), *SceneName); + return true; + } + return false; +} + +void UPS_Editor_SceneSerializer::ClearScene(UPS_Editor_SpawnManager* SpawnManager) +{ + if (UPS_Editor_SelectionManager* SM = SelectionManagerRef.Get()) + { + SM->ClearSelection(); + } + + if (SpawnManager) + { + SpawnManager->DestroyAllSpawnedActors(); + } +} + +FString UPS_Editor_SceneSerializer::GetSceneFilePath(const FString& SceneName) const +{ + return FPaths::Combine(GetScenesDirectory(), SceneName + TEXT(".json")); +} + +FString UPS_Editor_SceneSerializer::GetScenesDirectory() const +{ + return FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("PS_Editor"), TEXT("Scenes")); +} diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneSerializer.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneSerializer.h new file mode 100644 index 0000000..34a2d38 --- /dev/null +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneSerializer.h @@ -0,0 +1,48 @@ +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "PS_Editor_SceneData.h" +#include "PS_Editor_SceneSerializer.generated.h" + +class UPS_Editor_SpawnManager; +class UPS_Editor_SelectionManager; + +/** + * Handles saving and loading PS_Editor scenes as JSON files. + * Scenes are stored in {ProjectDir}/Saved/PS_Editor/Scenes/{SceneName}.json + */ +UCLASS() +class PS_EDITOR_API UPS_Editor_SceneSerializer : public UObject +{ + GENERATED_BODY() + +public: + /** Save the current spawned actors to a JSON file. */ + bool SaveScene(const FString& SceneName, UPS_Editor_SpawnManager* SpawnManager); + + /** Load a scene from a JSON file. Destroys current spawned actors and re-spawns from file. */ + bool LoadScene(const FString& SceneName, UPS_Editor_SpawnManager* SpawnManager); + + /** Get a list of all saved scene names (without .json extension). */ + TArray GetSavedSceneNames() const; + + /** Delete a saved scene file. */ + bool DeleteScene(const FString& SceneName); + + /** Clear all spawned actors (new scene). Also clears selection and undo history. */ + void ClearScene(UPS_Editor_SpawnManager* SpawnManager); + + /** Set optional selection manager to clear on load/new. */ + void SetSelectionManager(UPS_Editor_SelectionManager* InSelectionManager) { SelectionManagerRef = InSelectionManager; } + +private: + UPROPERTY() + TWeakObjectPtr SelectionManagerRef; + + /** Get the full file path for a scene name. */ + FString GetSceneFilePath(const FString& SceneName) const; + + /** Get the directory where scenes are stored. */ + FString GetScenesDirectory() const; +}; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.cpp index 67c6673..84ccec2 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.cpp @@ -138,6 +138,9 @@ AActor* UPS_Editor_SpawnManager::SpawnFromCatalogAtLocation(int32 Index, FVector } } + // Track spawned actor + SpawnedActors.Add(SpawnedActor); + UE_LOG(LogTemp, Log, TEXT("PS_Editor: Spawned %s at %s"), *Entry.DisplayName, *WorldLocation.ToString()); return SpawnedActor; } @@ -158,6 +161,48 @@ FVector UPS_Editor_SpawnManager::ComputeSpawnLocation() const return CameraLocation + CameraRotation.Vector() * 500.0f; } +AActor* UPS_Editor_SpawnManager::SpawnActorDirect(UClass* ActorClass, FVector Location, FRotator Rotation, FVector Scale) +{ + APlayerController* PC = OwnerPC.Get(); + if (!PC || !PC->GetWorld() || !ActorClass) + { + return nullptr; + } + + FActorSpawnParameters SpawnParams; + SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn; + + AActor* SpawnedActor = PC->GetWorld()->SpawnActor(ActorClass, Location, Rotation, SpawnParams); + if (!SpawnedActor) + { + return nullptr; + } + + if (USceneComponent* Root = SpawnedActor->GetRootComponent()) + { + Root->SetMobility(EComponentMobility::Movable); + } + + SpawnedActor->SetActorScale3D(Scale); + SpawnedActors.Add(SpawnedActor); + + return SpawnedActor; +} + +void UPS_Editor_SpawnManager::DestroyAllSpawnedActors() +{ + for (const TWeakObjectPtr& Weak : SpawnedActors) + { + if (AActor* Actor = Weak.Get()) + { + Actor->Destroy(); + } + } + SpawnedActors.Empty(); + + UE_LOG(LogTemp, Log, TEXT("PS_Editor: All spawned actors destroyed")); +} + TArray UPS_Editor_SpawnManager::GetCategories() const { TArray Categories; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.h index 2dc9ed8..b45321e 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.h @@ -45,6 +45,15 @@ public: /** Spawn the actor at a specific world location. */ AActor* SpawnFromCatalogAtLocation(int32 Index, FVector WorldLocation); + /** Spawn an actor directly from a class (used by scene loading). No undo recorded. */ + AActor* SpawnActorDirect(UClass* ActorClass, FVector Location, FRotator Rotation, FVector Scale); + + /** Destroy all spawned actors (for scene clear/load). */ + void DestroyAllSpawnedActors(); + + /** Get all currently spawned actors. */ + const TArray>& GetSpawnedActors() const { return SpawnedActors; } + /** Get the resolved catalog entries for UI display. */ const TArray& GetResolvedEntries() const { return ResolvedEntries; } @@ -61,6 +70,10 @@ private: UPROPERTY() TArray ResolvedEntries; + /** All actors spawned by this manager (tracked for save/load). */ + UPROPERTY() + TArray> SpawnedActors; + /** Resolve all catalog entries (load classes, read SpawnableComponent metadata). */ void ResolveCatalog(); diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/PS_Editor_HUD.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/PS_Editor_HUD.cpp index 5bc7129..70dccf3 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/PS_Editor_HUD.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/PS_Editor_HUD.cpp @@ -18,6 +18,7 @@ void APS_Editor_HUD::BeginPlay() { MainWidget->SetSelectionManager(EditorPC->GetSelectionManager()); MainWidget->SetSpawnManager(EditorPC->GetSpawnManager()); + MainWidget->SetSceneSerializer(EditorPC->GetSceneSerializer()); } MainWidget->AddToViewport(0); diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp index 5222842..0c09984 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp @@ -1,6 +1,7 @@ #include "PS_Editor_MainWidget.h" #include "PS_Editor_SelectionManager.h" #include "PS_Editor_SpawnManager.h" +#include "PS_Editor_SceneSerializer.h" #include "PS_Editor_UndoManager.h" #include "PS_Editor_PlayerController.h" #include "Widgets/SBoxPanel.h" @@ -23,6 +24,11 @@ void UPS_Editor_MainWidget::SetSpawnManager(UPS_Editor_SpawnManager* InSpawnMana SpawnManager = InSpawnManager; } +void UPS_Editor_MainWidget::SetSceneSerializer(UPS_Editor_SceneSerializer* InSerializer) +{ + SceneSerializer = InSerializer; +} + TSharedRef UPS_Editor_MainWidget::RebuildWidget() { return SNew(SOverlay) @@ -70,6 +76,13 @@ TSharedRef UPS_Editor_MainWidget::RebuildWidget() [ BuildToolbar() ] + // Scene save/load + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 4.0f, 0.0f, 0.0f) + [ + BuildSceneButtons() + ] ] // Left panel: Spawn Catalogue + SOverlay::Slot() @@ -258,6 +271,88 @@ TSharedRef UPS_Editor_MainWidget::BuildPropertiesPanel() ]; } +TSharedRef UPS_Editor_MainWidget::BuildSceneButtons() +{ + return SNew(SBorder) + .BorderBackgroundColor(FLinearColor(0.05f, 0.05f, 0.05f, 0.85f)) + .Padding(FMargin(8.0f, 4.0f)) + [ + SNew(SVerticalBox) + // Save row: text field + save button + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(0.0f, 0.0f, 4.0f, 0.0f) + [ + SAssignNew(SaveNameField, SEditableTextBox) + .HintText(FText::FromString(TEXT("Scene name..."))) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 10)) + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton) + .OnClicked_Lambda([this]() -> FReply + { + if (SaveNameField.IsValid() && SceneSerializer.IsValid() && SpawnManager.IsValid()) + { + FString Name = SaveNameField->GetText().ToString(); + if (!Name.IsEmpty()) + { + SceneSerializer->SaveScene(Name, SpawnManager.Get()); + // Refresh scene list + PopulateSceneList(); + } + } + return FReply::Handled(); + }) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Save"))) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 10)) + ] + ] + ] + // Action buttons: New + Load list + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(0.0f, 0.0f, 4.0f, 0.0f) + [ + SNew(SButton) + .OnClicked_Lambda([this]() -> FReply + { + if (SceneSerializer.IsValid() && SpawnManager.IsValid()) + { + SceneSerializer->ClearScene(SpawnManager.Get()); + } + return FReply::Handled(); + }) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("New Scene"))) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 10)) + ] + ] + ] + // Saved scenes list + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 4.0f, 0.0f, 0.0f) + [ + SAssignNew(SceneListContainer, SVerticalBox) + ] + ]; +} + TSharedRef UPS_Editor_MainWidget::BuildSpawnCatalog() { TSharedRef CatalogList = SNew(SVerticalBox); @@ -302,6 +397,67 @@ void UPS_Editor_MainWidget::NativeConstruct() { Super::NativeConstruct(); PopulateSpawnCatalog(); + PopulateSceneList(); +} + +void UPS_Editor_MainWidget::PopulateSceneList() +{ + if (!SceneListContainer.IsValid()) return; + SceneListContainer->ClearChildren(); + + UPS_Editor_SceneSerializer* Ser = SceneSerializer.Get(); + if (!Ser) return; + + TArray Scenes = Ser->GetSavedSceneNames(); + if (Scenes.Num() == 0) + { + SceneListContainer->AddSlot() + .AutoHeight() + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("No saved scenes"))) + .Font(FCoreStyle::GetDefaultFontStyle("Italic", 9)) + .ColorAndOpacity(FLinearColor(0.5f, 0.5f, 0.5f)) + ]; + return; + } + + SceneListContainer->AddSlot() + .AutoHeight() + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Load scene:"))) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 9)) + .ColorAndOpacity(FLinearColor(0.6f, 0.6f, 0.6f)) + ]; + + for (const FString& SceneName : Scenes) + { + FString Name = SceneName; + SceneListContainer->AddSlot() + .AutoHeight() + .Padding(0.0f, 1.0f) + [ + SNew(SButton) + .OnClicked_Lambda([this, Name]() -> FReply + { + if (SceneSerializer.IsValid() && SpawnManager.IsValid()) + { + SceneSerializer->LoadScene(Name, SpawnManager.Get()); + if (SaveNameField.IsValid()) + { + SaveNameField->SetText(FText::FromString(Name)); + } + } + return FReply::Handled(); + }) + [ + SNew(STextBlock) + .Text(FText::FromString(SceneName)) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 10)) + ] + ]; + } } void UPS_Editor_MainWidget::PopulateSpawnCatalog() diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h index b737501..3a0e396 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.h @@ -7,6 +7,7 @@ class UPS_Editor_SelectionManager; class UPS_Editor_SpawnManager; +class UPS_Editor_SceneSerializer; /** * Main editor widget for PS_Editor. @@ -22,6 +23,7 @@ public: /** Set manager references. Must be called after construction. */ void SetSelectionManager(UPS_Editor_SelectionManager* InSelectionManager); void SetSpawnManager(UPS_Editor_SpawnManager* InSpawnManager); + void SetSceneSerializer(UPS_Editor_SceneSerializer* InSerializer); protected: virtual TSharedRef RebuildWidget() override; @@ -35,12 +37,19 @@ private: UPROPERTY() TWeakObjectPtr SpawnManager; + UPROPERTY() + TWeakObjectPtr SceneSerializer; + // ---- Slate widget refs for dynamic updates ---- TSharedPtr ModeText; TSharedPtr SelectionInfoText; // Spawn catalog TSharedPtr SpawnListContainer; + + // Scene save/load + TSharedPtr SaveNameField; + TSharedPtr SceneListContainer; TArray> CachedBrushes; // Transform fields @@ -52,6 +61,8 @@ private: TSharedRef BuildToolbar(); TSharedRef BuildPropertiesPanel(); TSharedRef BuildSpawnCatalog(); + TSharedRef BuildSceneButtons(); + void PopulateSceneList(); void PopulateSpawnCatalog(); void RefreshPropertiesFromSelection(); void ApplyTransformFromUI();