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()
|
GENERATED_BODY()
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
int32 Version = 2;
|
int32 Version = 3;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString SceneName;
|
FString SceneName;
|
||||||
|
|
||||||
|
/** The base level this scene was built on (e.g. "Warehouse", "Office_Floor2"). */
|
||||||
|
UPROPERTY()
|
||||||
|
FString LevelName;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Timestamp;
|
FString Timestamp;
|
||||||
|
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ FString UPS_Editor_SceneLoader::GetSceneFilePath(const FString& SceneName)
|
|||||||
return FPaths::Combine(GetScenesDirectory(), SceneName + TEXT(".json"));
|
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;
|
TArray<FString> SceneNames;
|
||||||
const FString Directory = GetScenesDirectory();
|
const FString Directory = GetScenesDirectory();
|
||||||
@@ -179,9 +179,54 @@ TArray<FString> UPS_Editor_SceneLoader::GetSavedSceneNames()
|
|||||||
|
|
||||||
for (const FString& File : Files)
|
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();
|
SceneNames.Sort();
|
||||||
return SceneNames;
|
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);
|
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")
|
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:
|
private:
|
||||||
/** Spawn a single actor from saved data and apply all properties. */
|
/** 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.SceneName = SceneName;
|
||||||
SceneData.Timestamp = FDateTime::Now().ToString();
|
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();
|
const TArray<TWeakObjectPtr<AActor>>& SpawnedActors = SpawnManager->GetSpawnedActors();
|
||||||
for (const TWeakObjectPtr<AActor>& Weak : SpawnedActors)
|
for (const TWeakObjectPtr<AActor>& Weak : SpawnedActors)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "PS_Editor_SelectionManager.h"
|
#include "PS_Editor_SelectionManager.h"
|
||||||
#include "PS_Editor_SpawnManager.h"
|
#include "PS_Editor_SpawnManager.h"
|
||||||
#include "PS_Editor_SceneSerializer.h"
|
#include "PS_Editor_SceneSerializer.h"
|
||||||
|
#include "PS_Editor_SceneLoader.h"
|
||||||
#include "PS_Editor_UndoManager.h"
|
#include "PS_Editor_UndoManager.h"
|
||||||
#include "PS_Editor_PlayerController.h"
|
#include "PS_Editor_PlayerController.h"
|
||||||
#include "PS_Editor_EditableComponent.h"
|
#include "PS_Editor_EditableComponent.h"
|
||||||
@@ -569,8 +570,35 @@ void UPS_Editor_MainWidget::PopulateSceneList()
|
|||||||
UPS_Editor_SceneSerializer* Ser = SceneSerializer.Get();
|
UPS_Editor_SceneSerializer* Ser = SceneSerializer.Get();
|
||||||
if (!Ser) return;
|
if (!Ser) return;
|
||||||
|
|
||||||
TArray<FString> Scenes = Ser->GetSavedSceneNames();
|
// Get current level name for filtering
|
||||||
if (Scenes.Num() == 0)
|
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()
|
SceneListContainer->AddSlot()
|
||||||
.AutoHeight()
|
.AutoHeight()
|
||||||
@@ -583,16 +611,20 @@ void UPS_Editor_MainWidget::PopulateSceneList()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show scenes for current level first
|
||||||
|
if (LevelScenes.Num() > 0)
|
||||||
|
{
|
||||||
SceneListContainer->AddSlot()
|
SceneListContainer->AddSlot()
|
||||||
.AutoHeight()
|
.AutoHeight()
|
||||||
|
.Padding(0.0f, 2.0f, 0.0f, 1.0f)
|
||||||
[
|
[
|
||||||
SNew(STextBlock)
|
SNew(STextBlock)
|
||||||
.Text(FText::FromString(TEXT("Load scene:")))
|
.Text(FText::FromString(TEXT("This level:")))
|
||||||
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 9))
|
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 9))
|
||||||
.ColorAndOpacity(FLinearColor(0.6f, 0.6f, 0.6f))
|
.ColorAndOpacity(FLinearColor(0.6f, 0.6f, 0.6f))
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const FString& SceneName : Scenes)
|
for (const FString& SceneName : LevelScenes)
|
||||||
{
|
{
|
||||||
FString Name = SceneName;
|
FString Name = SceneName;
|
||||||
SceneListContainer->AddSlot()
|
SceneListContainer->AddSlot()
|
||||||
@@ -621,6 +653,59 @@ 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()
|
void UPS_Editor_MainWidget::PopulateSpawnCatalog()
|
||||||
{
|
{
|
||||||
if (!SpawnListContainer.IsValid())
|
if (!SpawnListContainer.IsValid())
|
||||||
|
|||||||
Reference in New Issue
Block a user