Add PROSERVE integration: sublevel loading, Play button, GameInstance subsystem
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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<UPS_Editor_GameInstanceSubsystem>())
|
||||
{
|
||||
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)
|
||||
|
||||
@@ -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<UPS_Editor_MasterCatalog> 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<FVector> AppendInitialPoints;
|
||||
|
||||
/** Currently loaded sublevel instance. */
|
||||
UPROPERTY()
|
||||
TObjectPtr<ULevelStreamingDynamic> CurrentSublevel;
|
||||
|
||||
void HandleEditorClick(FVector2D ScreenPos, bool bAdditive);
|
||||
};
|
||||
|
||||
@@ -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<AActor*>& OutActors)
|
||||
bool UPS_Editor_SceneLoader::LoadScene(const UObject* WorldContextObject, const FString& SceneName, TArray<AActor*>& 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<UActorComponent*> 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<UPS_Editor_SpawnableComponent>())
|
||||
{
|
||||
ToRemove.Add(Spawnable);
|
||||
}
|
||||
if (UActorComponent* Editable = Actor->FindComponentByClass<UPS_Editor_EditableComponent>())
|
||||
{
|
||||
ToRemove.Add(Editable);
|
||||
}
|
||||
|
||||
for (UActorComponent* Comp : ToRemove)
|
||||
{
|
||||
Comp->DestroyComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UPS_Editor_SceneLoader::LoadSceneFromData(UWorld* World, const FPS_Editor_SceneData& SceneData, TArray<AActor*>& OutActors)
|
||||
|
||||
@@ -32,11 +32,12 @@ public:
|
||||
*
|
||||
* @param World The world to spawn actors into.
|
||||
* @param SceneName Scene name (without .json extension).
|
||||
* @param OutActors Optional: filled with all spawned actors.
|
||||
* @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<AActor*>& OutActors);
|
||||
static bool LoadScene(const UObject* WorldContextObject, const FString& SceneName, TArray<AActor*>& OutActors, bool bStripEditorComponents = true);
|
||||
|
||||
/**
|
||||
* Load scene from an already-parsed SceneData struct.
|
||||
|
||||
@@ -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<APS_Editor_PlayerController>(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<TWeakObjectPtr<AActor>>& SpawnedActors = SpawnManager->GetSpawnedActors();
|
||||
|
||||
@@ -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<TSoftObjectPtr<UPS_Editor_SpawnCatalog>> 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<FString> BaseLevels;
|
||||
};
|
||||
|
||||
@@ -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<SWidget> 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<APS_Editor_PlayerController>(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<UPS_Editor_GameInstanceSubsystem>())
|
||||
{
|
||||
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<APS_Editor_PlayerController>(GetOwningPlayer());
|
||||
if (!EditorPC) return FReply::Handled();
|
||||
|
||||
// Get BaseLevels from MasterCatalog
|
||||
TArray<FString> 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<APS_Editor_PlayerController>(GetOwningPlayer()))
|
||||
{
|
||||
CurrentLevelFilter = EditorPC->CurrentBaseLevel;
|
||||
if (LevelFilterText.IsValid() && !CurrentLevelFilter.IsEmpty())
|
||||
{
|
||||
LevelFilterText->SetText(FText::FromString(CurrentLevelFilter));
|
||||
}
|
||||
}
|
||||
|
||||
PopulateSceneList();
|
||||
}
|
||||
|
||||
@@ -570,65 +689,23 @@ 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<APS_Editor_PlayerController>(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<FString> 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<FString> LevelScenes = UPS_Editor_SceneLoader::GetSavedSceneNames(CurrentLevel);
|
||||
TArray<FString> 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)
|
||||
{
|
||||
SceneListContainer->AddSlot()
|
||||
.AutoHeight()
|
||||
.Padding(0.0f, 2.0f, 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)
|
||||
for (const FString& SceneName : Scenes)
|
||||
{
|
||||
FString Name = SceneName;
|
||||
SceneListContainer->AddSlot()
|
||||
@@ -657,59 +734,6 @@ void UPS_Editor_MainWidget::PopulateSceneList()
|
||||
}
|
||||
}
|
||||
|
||||
// Show other scenes (from different levels) in a collapsed section
|
||||
TArray<FString> 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)
|
||||
[
|
||||
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))
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget::PopulateSpawnCatalog()
|
||||
{
|
||||
if (!SpawnListContainer.IsValid())
|
||||
|
||||
@@ -71,6 +71,8 @@ private:
|
||||
// Scene save/load
|
||||
TSharedPtr<SEditableTextBox> SaveNameField;
|
||||
TSharedPtr<SVerticalBox> SceneListContainer;
|
||||
TSharedPtr<STextBlock> LevelFilterText;
|
||||
FString CurrentLevelFilter;
|
||||
TArray<TSharedPtr<FSlateBrush>> CachedBrushes;
|
||||
|
||||
// Transform fields
|
||||
|
||||
Reference in New Issue
Block a user