Add level-based scene organization and filtering
SceneData now includes LevelName field (auto-detected from current map on save). Version bumped to 3. Backward compatible (empty LevelName for old files). SceneLoader: - GetSavedSceneNames(LevelFilter) filters by associated level - GetSceneLevelName() reads level association without full parse - GetSavedSceneNamesForCurrentLevel() convenience for game code Editor UI: - Scene list grouped: "This level" (highlighted) + "Other levels" (dimmed) - Shows current level name above the list PROSERVE integration pattern: // List scenarios for current level auto Scenes = UPS_Editor_SceneLoader::GetSavedSceneNamesForCurrentLevel(this); // Load a specific scenario UPS_Editor_SceneLoader::LoadScene(this, "Scenario_A", OutActors); Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,11 +34,15 @@ struct FPS_Editor_SceneData
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY()
|
||||
int32 Version = 2;
|
||||
int32 Version = 3;
|
||||
|
||||
UPROPERTY()
|
||||
FString SceneName;
|
||||
|
||||
/** The base level this scene was built on (e.g. "Warehouse", "Office_Floor2"). */
|
||||
UPROPERTY()
|
||||
FString LevelName;
|
||||
|
||||
UPROPERTY()
|
||||
FString Timestamp;
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ FString UPS_Editor_SceneLoader::GetSceneFilePath(const FString& SceneName)
|
||||
return FPaths::Combine(GetScenesDirectory(), SceneName + TEXT(".json"));
|
||||
}
|
||||
|
||||
TArray<FString> UPS_Editor_SceneLoader::GetSavedSceneNames()
|
||||
TArray<FString> UPS_Editor_SceneLoader::GetSavedSceneNames(const FString& LevelFilter)
|
||||
{
|
||||
TArray<FString> SceneNames;
|
||||
const FString Directory = GetScenesDirectory();
|
||||
@@ -179,9 +179,54 @@ TArray<FString> UPS_Editor_SceneLoader::GetSavedSceneNames()
|
||||
|
||||
for (const FString& File : Files)
|
||||
{
|
||||
SceneNames.Add(FPaths::GetBaseFilename(File));
|
||||
const FString Name = FPaths::GetBaseFilename(File);
|
||||
|
||||
if (LevelFilter.IsEmpty())
|
||||
{
|
||||
SceneNames.Add(Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Quick check: read the LevelName field from JSON without full parse
|
||||
const FString LevelName = GetSceneLevelName(Name);
|
||||
if (LevelName == LevelFilter)
|
||||
{
|
||||
SceneNames.Add(Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SceneNames.Sort();
|
||||
return SceneNames;
|
||||
}
|
||||
|
||||
FString UPS_Editor_SceneLoader::GetSceneLevelName(const FString& SceneName)
|
||||
{
|
||||
const FString FilePath = GetSceneFilePath(SceneName);
|
||||
FString JsonString;
|
||||
if (!FFileHelper::LoadFileToString(JsonString, *FilePath))
|
||||
{
|
||||
return TEXT("");
|
||||
}
|
||||
|
||||
// Parse just the header (lightweight)
|
||||
FPS_Editor_SceneData SceneData;
|
||||
if (FJsonObjectConverter::JsonObjectStringToUStruct(JsonString, &SceneData, 0, 0))
|
||||
{
|
||||
return SceneData.LevelName;
|
||||
}
|
||||
return TEXT("");
|
||||
}
|
||||
|
||||
TArray<FString> UPS_Editor_SceneLoader::GetSavedSceneNamesForCurrentLevel(const UObject* WorldContextObject)
|
||||
{
|
||||
if (!WorldContextObject) return {};
|
||||
|
||||
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
|
||||
if (!World) return {};
|
||||
|
||||
FString LevelName = World->GetMapName();
|
||||
LevelName.RemoveFromStart(World->StreamingLevelsPrefix);
|
||||
|
||||
return GetSavedSceneNames(LevelName);
|
||||
}
|
||||
|
||||
@@ -57,10 +57,23 @@ public:
|
||||
static FString GetSceneFilePath(const FString& SceneName);
|
||||
|
||||
/**
|
||||
* Get all saved scene names.
|
||||
* Get all saved scene names. Optionally filter by level name.
|
||||
* @param LevelFilter If not empty, only return scenes associated with this level.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor")
|
||||
static TArray<FString> GetSavedSceneNames();
|
||||
static TArray<FString> GetSavedSceneNames(const FString& LevelFilter = TEXT(""));
|
||||
|
||||
/**
|
||||
* Get the level name associated with a saved scene (reads the JSON header without spawning).
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor")
|
||||
static FString GetSceneLevelName(const FString& SceneName);
|
||||
|
||||
/**
|
||||
* Get all saved scenes for the current level.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor", meta = (WorldContext = "WorldContextObject"))
|
||||
static TArray<FString> GetSavedSceneNamesForCurrentLevel(const UObject* WorldContextObject);
|
||||
|
||||
private:
|
||||
/** Spawn a single actor from saved data and apply all properties. */
|
||||
|
||||
@@ -22,6 +22,16 @@ bool UPS_Editor_SceneSerializer::SaveScene(const FString& SceneName, UPS_Editor_
|
||||
SceneData.SceneName = SceneName;
|
||||
SceneData.Timestamp = FDateTime::Now().ToString();
|
||||
|
||||
// Auto-detect the current level name
|
||||
if (APlayerController* PC = Cast<APlayerController>(GetOuter()))
|
||||
{
|
||||
if (UWorld* World = PC->GetWorld())
|
||||
{
|
||||
SceneData.LevelName = World->GetMapName();
|
||||
SceneData.LevelName.RemoveFromStart(World->StreamingLevelsPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
const TArray<TWeakObjectPtr<AActor>>& SpawnedActors = SpawnManager->GetSpawnedActors();
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SpawnedActors)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "PS_Editor_SelectionManager.h"
|
||||
#include "PS_Editor_SpawnManager.h"
|
||||
#include "PS_Editor_SceneSerializer.h"
|
||||
#include "PS_Editor_SceneLoader.h"
|
||||
#include "PS_Editor_UndoManager.h"
|
||||
#include "PS_Editor_PlayerController.h"
|
||||
#include "PS_Editor_EditableComponent.h"
|
||||
@@ -569,8 +570,35 @@ void UPS_Editor_MainWidget::PopulateSceneList()
|
||||
UPS_Editor_SceneSerializer* Ser = SceneSerializer.Get();
|
||||
if (!Ser) return;
|
||||
|
||||
TArray<FString> Scenes = Ser->GetSavedSceneNames();
|
||||
if (Scenes.Num() == 0)
|
||||
// Get current level name for filtering
|
||||
FString CurrentLevel;
|
||||
if (APlayerController* PC = GetOwningPlayer())
|
||||
{
|
||||
if (UWorld* World = PC->GetWorld())
|
||||
{
|
||||
CurrentLevel = World->GetMapName();
|
||||
CurrentLevel.RemoveFromStart(World->StreamingLevelsPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
// Show current level
|
||||
if (!CurrentLevel.IsEmpty())
|
||||
{
|
||||
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()
|
||||
@@ -583,41 +611,98 @@ void UPS_Editor_MainWidget::PopulateSceneList()
|
||||
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)
|
||||
// Show scenes for current level first
|
||||
if (LevelScenes.Num() > 0)
|
||||
{
|
||||
FString Name = SceneName;
|
||||
SceneListContainer->AddSlot()
|
||||
.AutoHeight()
|
||||
.Padding(0.0f, 1.0f)
|
||||
.Padding(0.0f, 2.0f, 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))
|
||||
]
|
||||
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
|
||||
{
|
||||
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))
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user