From 472811b858b5a27eb816a5f9bb531e90661e5664 Mon Sep 17 00:00:00 2001 From: "j.foucher" Date: Sun, 12 Apr 2026 15:32:43 +0200 Subject: [PATCH] Add PROSERVE integration: sublevel loading, Play button, GameInstance subsystem MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Architecture for single editor map workflow: - MasterCatalog.BaseLevels lists available base levels - BaseLevel selector in UI (cycle button) loads selected level as sublevel - Sublevel provides visual context only — save/load only touches PS_Editor objects - No auto-load at startup: user picks via UI or PROSERVE sets via subsystem GameInstanceSubsystem (survives OpenLevel): - PendingSceneName + PendingBaseLevel set by PROSERVE before opening editor - Editor reads them at BeginPlay: loads sublevel + scene automatically - ConsumePendingScene() for gameplay side to read and clear Play button: - Auto-saves current scene - Stores scene name in GameInstanceSubsystem - Opens the BaseLevel via OpenLevel SceneLoader: - bStripEditorComponents parameter (default true) removes Spawnable/Editable components after load for clean gameplay actors SceneData v3: LevelName field associates scenes with base levels. Scene list filtered by selected BaseLevel. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../PS_Editor_GameInstanceSubsystem.cpp | 16 ++ .../PS_Editor_GameInstanceSubsystem.h | 35 +++ .../GameMode/PS_Editor_PlayerController.cpp | 79 +++++- .../GameMode/PS_Editor_PlayerController.h | 20 +- .../Serialization/PS_Editor_SceneLoader.cpp | 40 ++- .../Serialization/PS_Editor_SceneLoader.h | 11 +- .../PS_Editor_SceneSerializer.cpp | 12 +- .../PS_Editor/Spawn/PS_Editor_SpawnCatalog.h | 8 + .../UI/Widgets/PS_Editor_MainWidget.cpp | 260 ++++++++++-------- .../UI/Widgets/PS_Editor_MainWidget.h | 2 + 10 files changed, 338 insertions(+), 145 deletions(-) create mode 100644 Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_GameInstanceSubsystem.cpp create mode 100644 Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_GameInstanceSubsystem.h diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_GameInstanceSubsystem.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_GameInstanceSubsystem.cpp new file mode 100644 index 0000000..050e5e0 --- /dev/null +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_GameInstanceSubsystem.cpp @@ -0,0 +1,16 @@ +#include "PS_Editor_GameInstanceSubsystem.h" + +void UPS_Editor_GameInstanceSubsystem::SetPendingScene(const FString& SceneName, const FString& BaseLevel) +{ + PendingSceneName = SceneName; + PendingBaseLevel = BaseLevel; + UE_LOG(LogTemp, Log, TEXT("PS_Editor: Pending scene set: '%s' (level: '%s')"), *SceneName, *BaseLevel); +} + +FString UPS_Editor_GameInstanceSubsystem::ConsumePendingScene() +{ + FString Result = PendingSceneName; + PendingSceneName.Empty(); + PendingBaseLevel.Empty(); + return Result; +} diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_GameInstanceSubsystem.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_GameInstanceSubsystem.h new file mode 100644 index 0000000..381ef91 --- /dev/null +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_GameInstanceSubsystem.h @@ -0,0 +1,35 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Subsystems/GameInstanceSubsystem.h" +#include "PS_Editor_GameInstanceSubsystem.generated.h" + +/** + * Lightweight subsystem that survives between level transitions (OpenLevel). + * Used to pass the scene name from the editor to the gameplay level. + * + * Usage from Blueprint: + * Get Game Instance → Get Subsystem (PS_Editor_GameInstanceSubsystem) → Pending Scene Name + */ +UCLASS() +class PS_EDITOR_API UPS_Editor_GameInstanceSubsystem : public UGameInstanceSubsystem +{ + GENERATED_BODY() + +public: + /** Scene name to load on the next level. Set by PS_Editor Play button, read by gameplay GameMode. */ + UPROPERTY(BlueprintReadWrite, Category = "PS Editor") + FString PendingSceneName; + + /** Base level name associated with the pending scene. */ + UPROPERTY(BlueprintReadWrite, Category = "PS Editor") + FString PendingBaseLevel; + + /** Helper: set both values at once. */ + UFUNCTION(BlueprintCallable, Category = "PS Editor") + void SetPendingScene(const FString& SceneName, const FString& BaseLevel); + + /** Helper: consume the pending scene (reads and clears). */ + UFUNCTION(BlueprintCallable, Category = "PS Editor") + FString ConsumePendingScene(); +}; 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 5b1b617..be8c92d 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 @@ -8,6 +8,8 @@ #include "PS_Editor_Pawn.h" #include "PS_Editor_SplineActor.h" #include "PS_Editor_PointPlaceable.h" +#include "Engine/LevelStreamingDynamic.h" +#include "PS_Editor_GameInstanceSubsystem.h" APS_Editor_PlayerController::APS_Editor_PlayerController() { @@ -40,8 +42,45 @@ void APS_Editor_PlayerController::BeginPlay() SpawnManager->AddCatalog(Catalog); } } - UE_LOG(LogTemp, Log, TEXT("PS_Editor: MasterCatalog loaded with %d sub-catalogs, %d total entries"), - Master->Catalogs.Num(), SpawnManager->GetResolvedEntries().Num()); + + // Auto-detect base level: strip "_Editor"/"_editor" from map name, match against BaseLevels + FString MapName = GetWorld()->GetMapName(); + MapName.RemoveFromStart(GetWorld()->StreamingLevelsPrefix); + + FString Candidate = MapName; + Candidate.RemoveFromEnd(TEXT("_Editor")); + Candidate.RemoveFromEnd(TEXT("_editor")); + + if (Master->BaseLevels.Contains(Candidate)) + { + CurrentBaseLevel = Candidate; + } + else if (Master->BaseLevels.Num() > 0) + { + CurrentBaseLevel = Master->BaseLevels[0]; + } + + // Check if PROSERVE set a pending base level via the subsystem + if (UGameInstance* GI = GetGameInstance()) + { + if (UPS_Editor_GameInstanceSubsystem* Sub = GI->GetSubsystem()) + { + if (!Sub->PendingBaseLevel.IsEmpty()) + { + LoadBaseLevelAsSublevel(Sub->PendingBaseLevel); + // Also load pending scene if set + if (!Sub->PendingSceneName.IsEmpty() && SpawnManager) + { + SceneSerializer->LoadScene(Sub->PendingSceneName, SpawnManager); + } + Sub->PendingSceneName.Empty(); + Sub->PendingBaseLevel.Empty(); + } + } + } + + UE_LOG(LogTemp, Log, TEXT("PS_Editor: MasterCatalog loaded (%d catalogs, %d entries). BaseLevel: '%s' (map: %s)"), + Master->Catalogs.Num(), SpawnManager->GetResolvedEntries().Num(), *CurrentBaseLevel, *MapName); } else { @@ -165,6 +204,42 @@ void APS_Editor_PlayerController::CancelPointPlacement() FinishPointPlacement(); } +void APS_Editor_PlayerController::LoadBaseLevelAsSublevel(const FString& LevelName) +{ + if (LevelName.IsEmpty()) return; + + // Unload previous sublevel + if (CurrentSublevel) + { + CurrentSublevel->SetShouldBeLoaded(false); + CurrentSublevel->SetShouldBeVisible(false); + CurrentSublevel->SetIsRequestingUnloadAndRemoval(true); + CurrentSublevel = nullptr; + UE_LOG(LogTemp, Log, TEXT("PS_Editor: Unloaded previous sublevel")); + } + + // Load new sublevel + bool bSuccess = false; + CurrentSublevel = ULevelStreamingDynamic::LoadLevelInstance( + GetWorld(), + LevelName, + FVector::ZeroVector, + FRotator::ZeroRotator, + bSuccess); + + if (bSuccess && CurrentSublevel) + { + CurrentSublevel->SetShouldBeLoaded(true); + CurrentSublevel->SetShouldBeVisible(true); + CurrentBaseLevel = LevelName; + UE_LOG(LogTemp, Log, TEXT("PS_Editor: Loaded sublevel '%s'"), *LevelName); + } + else + { + UE_LOG(LogTemp, Warning, TEXT("PS_Editor: Failed to load sublevel '%s'"), *LevelName); + } +} + void APS_Editor_PlayerController::HandleEditorClick(FVector2D ScreenPos, bool bAdditive) { if (EditorMode == EPS_Editor_EditorMode::SplinePlacement) 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 8beda42..357ffe1 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 @@ -13,6 +13,7 @@ class UPS_Editor_MasterCatalog; class UPS_Editor_SceneSerializer; class APS_Editor_SplineActor; class IPS_Editor_PointPlaceable; +class ULevelStreamingDynamic; /** * Player controller for PS_Editor runtime editing. @@ -42,14 +43,13 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS Editor") TSoftObjectPtr MasterCatalogAsset; - /** - * Override the level name stored in saved scenes. - * If empty, auto-detected from the current map name. - * Use this when editing on a dedicated map (e.g. "Warehouse_Editor") - * but scenes should be associated with the base level ("Warehouse"). - */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS Editor") - FString BaseLevelNameOverride; + /** Current base level for scene save/load filtering. Auto-detected at startup, switchable via UI. */ + UPROPERTY(BlueprintReadWrite, Category = "PS Editor") + FString CurrentBaseLevel; + + /** Load a base level as sublevel for visual context. Unloads previous sublevel. */ + UFUNCTION(BlueprintCallable, Category = "PS Editor") + void LoadBaseLevelAsSublevel(const FString& LevelName); // ---- Editor Mode (Normal / SplinePlacement) ---- @@ -115,5 +115,9 @@ private: /** Snapshot of spline points before append, for undo. */ TArray AppendInitialPoints; + /** Currently loaded sublevel instance. */ + UPROPERTY() + TObjectPtr CurrentSublevel; + void HandleEditorClick(FVector2D ScreenPos, bool bAdditive); }; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneLoader.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneLoader.cpp index ef458b6..7eb7f79 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneLoader.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneLoader.cpp @@ -1,14 +1,16 @@ #include "PS_Editor_SceneLoader.h" #include "PS_Editor_SceneData.h" #include "PS_Editor_EditableComponent.h" +#include "PS_Editor_SpawnableComponent.h" #include "PS_Editor_SplineActor.h" +#include "PS_Editor_PointPlaceable.h" #include "JsonObjectConverter.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" #include "UObject/UnrealType.h" #include "Components/ActorComponent.h" -bool UPS_Editor_SceneLoader::LoadScene(const UObject* WorldContextObject, const FString& SceneName, TArray& OutActors) +bool UPS_Editor_SceneLoader::LoadScene(const UObject* WorldContextObject, const FString& SceneName, TArray& OutActors, bool bStripEditorComponents) { OutActors.Empty(); @@ -40,7 +42,41 @@ bool UPS_Editor_SceneLoader::LoadScene(const UObject* WorldContextObject, const return false; } - return LoadSceneFromData(World, SceneData, OutActors); + if (!LoadSceneFromData(World, SceneData, OutActors)) + { + return false; + } + + // Strip editor-only components for clean gameplay + if (bStripEditorComponents) + { + for (AActor* Actor : OutActors) + { + if (!Actor) continue; + + TArray ToRemove; + for (UActorComponent* Comp : Actor->GetComponentsByInterface(UPS_Editor_PointPlaceable::StaticClass())) + { + // Don't remove — PointPlaceable is on the actor itself, not a component + } + + if (UActorComponent* Spawnable = Actor->FindComponentByClass()) + { + ToRemove.Add(Spawnable); + } + if (UActorComponent* Editable = Actor->FindComponentByClass()) + { + ToRemove.Add(Editable); + } + + for (UActorComponent* Comp : ToRemove) + { + Comp->DestroyComponent(); + } + } + } + + return true; } bool UPS_Editor_SceneLoader::LoadSceneFromData(UWorld* World, const FPS_Editor_SceneData& SceneData, TArray& OutActors) diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneLoader.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneLoader.h index 36d420d..a73861a 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneLoader.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneLoader.h @@ -30,13 +30,14 @@ public: /** * Load a scene from a JSON file and spawn all actors. * - * @param World The world to spawn actors into. - * @param SceneName Scene name (without .json extension). - * @param OutActors Optional: filled with all spawned actors. - * @return True if the scene was loaded successfully. + * @param World The world to spawn actors into. + * @param SceneName Scene name (without .json extension). + * @param OutActors Filled with all spawned actors. + * @param bStripEditorComponents If true, removes SpawnableComponent and EditableComponent after spawn (cleaner for gameplay). + * @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& OutActors); + static bool LoadScene(const UObject* WorldContextObject, const FString& SceneName, TArray& OutActors, bool bStripEditorComponents = true); /** * Load scene from an already-parsed SceneData struct. 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 index 81cc2d2..66d0a1c 100644 --- 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 @@ -23,18 +23,10 @@ bool UPS_Editor_SceneSerializer::SaveScene(const FString& SceneName, UPS_Editor_ SceneData.SceneName = SceneName; SceneData.Timestamp = FDateTime::Now().ToString(); - // Use BaseLevelNameOverride if set, otherwise auto-detect from map name + // Use the current base level from PlayerController if (APS_Editor_PlayerController* EditorPC = Cast(GetOuter())) { - if (!EditorPC->BaseLevelNameOverride.IsEmpty()) - { - SceneData.LevelName = EditorPC->BaseLevelNameOverride; - } - else if (UWorld* World = EditorPC->GetWorld()) - { - SceneData.LevelName = World->GetMapName(); - SceneData.LevelName.RemoveFromStart(World->StreamingLevelsPrefix); - } + SceneData.LevelName = EditorPC->CurrentBaseLevel; } const TArray>& SpawnedActors = SpawnManager->GetSpawnedActors(); diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.h index b89a627..6e39e76 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.h @@ -58,4 +58,12 @@ public: /** Sub-catalogs to load. Each one contributes its entries to the runtime spawn UI. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS Editor") TArray> Catalogs; + + /** + * List of base level names available for scene editing. + * Used to filter saved scenes by level. Auto-matched by stripping "_Editor" from the current map name. + * Example: "Warehouse", "Office", "Parking" + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS Editor") + TArray BaseLevels; }; 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 118cee8..a85ab96 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 @@ -21,6 +21,8 @@ #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/SOverlay.h" #include "UObject/UnrealType.h" +#include "Kismet/GameplayStatics.h" +#include "PS_Editor_GameInstanceSubsystem.h" void UPS_Editor_MainWidget::SetSelectionManager(UPS_Editor_SelectionManager* InSelectionManager) { @@ -504,11 +506,117 @@ TSharedRef UPS_Editor_MainWidget::BuildSceneButtons() .Font(FCoreStyle::GetDefaultFontStyle("Regular", 10)) ] ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(4.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(SButton) + .OnClicked_Lambda([this]() -> FReply + { + APS_Editor_PlayerController* EditorPC = Cast(GetOwningPlayer()); + if (!EditorPC) return FReply::Handled(); + + // Auto-save current scene before playing + if (SaveNameField.IsValid() && SceneSerializer.IsValid() && SpawnManager.IsValid()) + { + FString SceneName = SaveNameField->GetText().ToString(); + if (!SceneName.IsEmpty()) + { + SceneSerializer->SaveScene(SceneName, SpawnManager.Get()); + UE_LOG(LogTemp, Log, TEXT("PS_Editor: Auto-saved '%s' before Play"), *SceneName); + } + } + + // Store scene name in GameInstance subsystem (survives OpenLevel) + FString BaseLevel = EditorPC->CurrentBaseLevel; + if (BaseLevel.IsEmpty()) + { + UE_LOG(LogTemp, Warning, TEXT("PS_Editor: No BaseLevel set, cannot Play")); + return FReply::Handled(); + } + + FString SceneName = SaveNameField.IsValid() ? SaveNameField->GetText().ToString() : TEXT(""); + if (UGameInstance* GI = EditorPC->GetGameInstance()) + { + if (UPS_Editor_GameInstanceSubsystem* Sub = GI->GetSubsystem()) + { + Sub->SetPendingScene(SceneName, BaseLevel); + } + } + + UGameplayStatics::OpenLevel(EditorPC, FName(*BaseLevel), true, TEXT("")); + return FReply::Handled(); + }) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Play"))) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 10)) + .ColorAndOpacity(FLinearColor(0.2f, 0.9f, 0.2f)) + ] + ] + ] + // BaseLevel selector row + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 4.0f, 0.0f, 2.0f) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(0.0f, 0.0f, 4.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("BaseLevel:"))) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 9)) + .ColorAndOpacity(FLinearColor(0.6f, 0.6f, 0.6f)) + ] + + SHorizontalBox::Slot().FillWidth(1.0f).VAlign(VAlign_Center) + [ + SAssignNew(LevelFilterText, STextBlock) + .Text(FText::FromString(TEXT("..."))) + .Font(FCoreStyle::GetDefaultFontStyle("Bold", 9)) + .ColorAndOpacity(FLinearColor(0.4f, 0.8f, 0.4f)) + ] + + SHorizontalBox::Slot().AutoWidth().Padding(4.0f, 0.0f, 0.0f, 0.0f) + [ + SNew(SButton) + .OnClicked_Lambda([this]() -> FReply + { + APS_Editor_PlayerController* EditorPC = Cast(GetOwningPlayer()); + if (!EditorPC) return FReply::Handled(); + + // Get BaseLevels from MasterCatalog + TArray Levels; + if (UPS_Editor_MasterCatalog* Master = EditorPC->MasterCatalogAsset.Get()) + { + Levels = Master->BaseLevels; + } + if (Levels.Num() == 0) return FReply::Handled(); + + // Cycle to next + int32 Idx = Levels.Find(EditorPC->CurrentBaseLevel); + Idx = (Idx + 1) % Levels.Num(); + + // Load as sublevel + update filter + EditorPC->LoadBaseLevelAsSublevel(Levels[Idx]); + CurrentLevelFilter = EditorPC->CurrentBaseLevel; + + if (LevelFilterText.IsValid()) + { + LevelFilterText->SetText(FText::FromString(CurrentLevelFilter)); + } + PopulateSceneList(); + return FReply::Handled(); + }) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT(">"))) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 10)) + ] + ] ] // Saved scenes list + SVerticalBox::Slot() .AutoHeight() - .Padding(0.0f, 4.0f, 0.0f, 0.0f) + .Padding(0.0f, 2.0f, 0.0f, 0.0f) [ SAssignNew(SceneListContainer, SVerticalBox) ] @@ -559,6 +667,17 @@ void UPS_Editor_MainWidget::NativeConstruct() { Super::NativeConstruct(); PopulateSpawnCatalog(); + + // Init BaseLevel filter from PlayerController auto-detection + if (APS_Editor_PlayerController* EditorPC = Cast(GetOwningPlayer())) + { + CurrentLevelFilter = EditorPC->CurrentBaseLevel; + if (LevelFilterText.IsValid() && !CurrentLevelFilter.IsEmpty()) + { + LevelFilterText->SetText(FText::FromString(CurrentLevelFilter)); + } + } + PopulateSceneList(); } @@ -570,143 +689,48 @@ void UPS_Editor_MainWidget::PopulateSceneList() UPS_Editor_SceneSerializer* Ser = SceneSerializer.Get(); if (!Ser) return; - // Get current level name for filtering (use override if set) - FString CurrentLevel; - if (APS_Editor_PlayerController* EditorPC = Cast(GetOwningPlayer())) - { - if (!EditorPC->BaseLevelNameOverride.IsEmpty()) - { - CurrentLevel = EditorPC->BaseLevelNameOverride; - } - else if (UWorld* World = EditorPC->GetWorld()) - { - CurrentLevel = World->GetMapName(); - CurrentLevel.RemoveFromStart(World->StreamingLevelsPrefix); - } - } + // Filter scenes by selected BaseLevel + TArray Scenes = UPS_Editor_SceneLoader::GetSavedSceneNames(CurrentLevelFilter); - // Show current level - if (!CurrentLevel.IsEmpty()) + if (Scenes.Num() == 0) { SceneListContainer->AddSlot() .AutoHeight() [ SNew(STextBlock) - .Text(FText::Format(NSLOCTEXT("PS_Editor", "LevelLabel", "Level: {0}"), FText::FromString(CurrentLevel))) - .Font(FCoreStyle::GetDefaultFontStyle("Italic", 8)) - .ColorAndOpacity(FLinearColor(0.4f, 0.6f, 0.4f)) - ]; - } - - // Get scenes for current level + all scenes - TArray LevelScenes = UPS_Editor_SceneLoader::GetSavedSceneNames(CurrentLevel); - TArray AllScenes = UPS_Editor_SceneLoader::GetSavedSceneNames(); - - if (AllScenes.Num() == 0) - { - SceneListContainer->AddSlot() - .AutoHeight() - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("No saved scenes"))) + .Text(FText::FromString(CurrentLevelFilter.IsEmpty() ? TEXT("No saved scenes") : FString::Printf(TEXT("No scenes for '%s'"), *CurrentLevelFilter))) .Font(FCoreStyle::GetDefaultFontStyle("Italic", 9)) .ColorAndOpacity(FLinearColor(0.5f, 0.5f, 0.5f)) ]; return; } - // Show scenes for current level first - if (LevelScenes.Num() > 0) + for (const FString& SceneName : Scenes) { + FString Name = SceneName; SceneListContainer->AddSlot() .AutoHeight() - .Padding(0.0f, 2.0f, 0.0f, 1.0f) + .Padding(0.0f, 1.0f) [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("This level:"))) - .Font(FCoreStyle::GetDefaultFontStyle("Bold", 9)) - .ColorAndOpacity(FLinearColor(0.6f, 0.6f, 0.6f)) - ]; - - for (const FString& SceneName : LevelScenes) - { - FString Name = SceneName; - SceneListContainer->AddSlot() - .AutoHeight() - .Padding(0.0f, 1.0f) - [ - SNew(SButton) - .OnClicked_Lambda([this, Name]() -> FReply + SNew(SButton) + .OnClicked_Lambda([this, Name]() -> FReply + { + if (SceneSerializer.IsValid() && SpawnManager.IsValid()) { - if (SceneSerializer.IsValid() && SpawnManager.IsValid()) + SceneSerializer->LoadScene(Name, SpawnManager.Get()); + if (SaveNameField.IsValid()) { - SceneSerializer->LoadScene(Name, SpawnManager.Get()); - if (SaveNameField.IsValid()) - { - SaveNameField->SetText(FText::FromString(Name)); - } + SaveNameField->SetText(FText::FromString(Name)); } - return FReply::Handled(); - }) - [ - SNew(STextBlock) - .Text(FText::FromString(SceneName)) - .Font(FCoreStyle::GetDefaultFontStyle("Regular", 10)) - ] - ]; - } - } - - // Show other scenes (from different levels) in a collapsed section - TArray OtherScenes; - for (const FString& S : AllScenes) - { - if (!LevelScenes.Contains(S)) - { - OtherScenes.Add(S); - } - } - - if (OtherScenes.Num() > 0) - { - SceneListContainer->AddSlot() - .AutoHeight() - .Padding(0.0f, 6.0f, 0.0f, 1.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Other levels:"))) - .Font(FCoreStyle::GetDefaultFontStyle("Regular", 8)) - .ColorAndOpacity(FLinearColor(0.4f, 0.4f, 0.4f)) - ]; - - for (const FString& SceneName : OtherScenes) - { - FString Name = SceneName; - SceneListContainer->AddSlot() - .AutoHeight() - .Padding(0.0f, 1.0f) + } + return FReply::Handled(); + }) [ - 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", 9)) - .ColorAndOpacity(FLinearColor(0.5f, 0.5f, 0.5f)) - ] - ]; - } + SNew(STextBlock) + .Text(FText::FromString(SceneName)) + .Font(FCoreStyle::GetDefaultFontStyle("Regular", 10)) + ] + ]; } } 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 1abc061..5942fd8 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 @@ -71,6 +71,8 @@ private: // Scene save/load TSharedPtr SaveNameField; TSharedPtr SceneListContainer; + TSharedPtr LevelFilterText; + FString CurrentLevelFilter; TArray> CachedBrushes; // Transform fields