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_Pawn.h"
|
||||||
#include "PS_Editor_SplineActor.h"
|
#include "PS_Editor_SplineActor.h"
|
||||||
#include "PS_Editor_PointPlaceable.h"
|
#include "PS_Editor_PointPlaceable.h"
|
||||||
|
#include "Engine/LevelStreamingDynamic.h"
|
||||||
|
#include "PS_Editor_GameInstanceSubsystem.h"
|
||||||
|
|
||||||
APS_Editor_PlayerController::APS_Editor_PlayerController()
|
APS_Editor_PlayerController::APS_Editor_PlayerController()
|
||||||
{
|
{
|
||||||
@@ -40,8 +42,45 @@ void APS_Editor_PlayerController::BeginPlay()
|
|||||||
SpawnManager->AddCatalog(Catalog);
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -165,6 +204,42 @@ void APS_Editor_PlayerController::CancelPointPlacement()
|
|||||||
FinishPointPlacement();
|
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)
|
void APS_Editor_PlayerController::HandleEditorClick(FVector2D ScreenPos, bool bAdditive)
|
||||||
{
|
{
|
||||||
if (EditorMode == EPS_Editor_EditorMode::SplinePlacement)
|
if (EditorMode == EPS_Editor_EditorMode::SplinePlacement)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class UPS_Editor_MasterCatalog;
|
|||||||
class UPS_Editor_SceneSerializer;
|
class UPS_Editor_SceneSerializer;
|
||||||
class APS_Editor_SplineActor;
|
class APS_Editor_SplineActor;
|
||||||
class IPS_Editor_PointPlaceable;
|
class IPS_Editor_PointPlaceable;
|
||||||
|
class ULevelStreamingDynamic;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Player controller for PS_Editor runtime editing.
|
* Player controller for PS_Editor runtime editing.
|
||||||
@@ -42,14 +43,13 @@ public:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS Editor")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS Editor")
|
||||||
TSoftObjectPtr<UPS_Editor_MasterCatalog> MasterCatalogAsset;
|
TSoftObjectPtr<UPS_Editor_MasterCatalog> MasterCatalogAsset;
|
||||||
|
|
||||||
/**
|
/** Current base level for scene save/load filtering. Auto-detected at startup, switchable via UI. */
|
||||||
* Override the level name stored in saved scenes.
|
UPROPERTY(BlueprintReadWrite, Category = "PS Editor")
|
||||||
* If empty, auto-detected from the current map name.
|
FString CurrentBaseLevel;
|
||||||
* Use this when editing on a dedicated map (e.g. "Warehouse_Editor")
|
|
||||||
* but scenes should be associated with the base level ("Warehouse").
|
/** Load a base level as sublevel for visual context. Unloads previous sublevel. */
|
||||||
*/
|
UFUNCTION(BlueprintCallable, Category = "PS Editor")
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS Editor")
|
void LoadBaseLevelAsSublevel(const FString& LevelName);
|
||||||
FString BaseLevelNameOverride;
|
|
||||||
|
|
||||||
// ---- Editor Mode (Normal / SplinePlacement) ----
|
// ---- Editor Mode (Normal / SplinePlacement) ----
|
||||||
|
|
||||||
@@ -115,5 +115,9 @@ private:
|
|||||||
/** Snapshot of spline points before append, for undo. */
|
/** Snapshot of spline points before append, for undo. */
|
||||||
TArray<FVector> AppendInitialPoints;
|
TArray<FVector> AppendInitialPoints;
|
||||||
|
|
||||||
|
/** Currently loaded sublevel instance. */
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<ULevelStreamingDynamic> CurrentSublevel;
|
||||||
|
|
||||||
void HandleEditorClick(FVector2D ScreenPos, bool bAdditive);
|
void HandleEditorClick(FVector2D ScreenPos, bool bAdditive);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
#include "PS_Editor_SceneLoader.h"
|
#include "PS_Editor_SceneLoader.h"
|
||||||
#include "PS_Editor_SceneData.h"
|
#include "PS_Editor_SceneData.h"
|
||||||
#include "PS_Editor_EditableComponent.h"
|
#include "PS_Editor_EditableComponent.h"
|
||||||
|
#include "PS_Editor_SpawnableComponent.h"
|
||||||
#include "PS_Editor_SplineActor.h"
|
#include "PS_Editor_SplineActor.h"
|
||||||
|
#include "PS_Editor_PointPlaceable.h"
|
||||||
#include "JsonObjectConverter.h"
|
#include "JsonObjectConverter.h"
|
||||||
#include "Misc/FileHelper.h"
|
#include "Misc/FileHelper.h"
|
||||||
#include "Misc/Paths.h"
|
#include "Misc/Paths.h"
|
||||||
#include "UObject/UnrealType.h"
|
#include "UObject/UnrealType.h"
|
||||||
#include "Components/ActorComponent.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();
|
OutActors.Empty();
|
||||||
|
|
||||||
@@ -40,7 +42,41 @@ bool UPS_Editor_SceneLoader::LoadScene(const UObject* WorldContextObject, const
|
|||||||
return false;
|
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)
|
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 World The world to spawn actors into.
|
||||||
* @param SceneName Scene name (without .json extension).
|
* @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.
|
* @return True if the scene was loaded successfully.
|
||||||
*/
|
*/
|
||||||
UFUNCTION(BlueprintCallable, Category = "PS Editor", meta = (WorldContext = "WorldContextObject"))
|
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.
|
* 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.SceneName = SceneName;
|
||||||
SceneData.Timestamp = FDateTime::Now().ToString();
|
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 (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetOuter()))
|
||||||
{
|
{
|
||||||
if (!EditorPC->BaseLevelNameOverride.IsEmpty())
|
SceneData.LevelName = EditorPC->CurrentBaseLevel;
|
||||||
{
|
|
||||||
SceneData.LevelName = EditorPC->BaseLevelNameOverride;
|
|
||||||
}
|
|
||||||
else if (UWorld* World = EditorPC->GetWorld())
|
|
||||||
{
|
|
||||||
SceneData.LevelName = World->GetMapName();
|
|
||||||
SceneData.LevelName.RemoveFromStart(World->StreamingLevelsPrefix);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const TArray<TWeakObjectPtr<AActor>>& SpawnedActors = SpawnManager->GetSpawnedActors();
|
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. */
|
/** Sub-catalogs to load. Each one contributes its entries to the runtime spawn UI. */
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS Editor")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS Editor")
|
||||||
TArray<TSoftObjectPtr<UPS_Editor_SpawnCatalog>> Catalogs;
|
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/Input/SEditableTextBox.h"
|
||||||
#include "Widgets/SOverlay.h"
|
#include "Widgets/SOverlay.h"
|
||||||
#include "UObject/UnrealType.h"
|
#include "UObject/UnrealType.h"
|
||||||
|
#include "Kismet/GameplayStatics.h"
|
||||||
|
#include "PS_Editor_GameInstanceSubsystem.h"
|
||||||
|
|
||||||
void UPS_Editor_MainWidget::SetSelectionManager(UPS_Editor_SelectionManager* InSelectionManager)
|
void UPS_Editor_MainWidget::SetSelectionManager(UPS_Editor_SelectionManager* InSelectionManager)
|
||||||
{
|
{
|
||||||
@@ -504,11 +506,117 @@ TSharedRef<SWidget> UPS_Editor_MainWidget::BuildSceneButtons()
|
|||||||
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 10))
|
.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
|
// Saved scenes list
|
||||||
+ SVerticalBox::Slot()
|
+ SVerticalBox::Slot()
|
||||||
.AutoHeight()
|
.AutoHeight()
|
||||||
.Padding(0.0f, 4.0f, 0.0f, 0.0f)
|
.Padding(0.0f, 2.0f, 0.0f, 0.0f)
|
||||||
[
|
[
|
||||||
SAssignNew(SceneListContainer, SVerticalBox)
|
SAssignNew(SceneListContainer, SVerticalBox)
|
||||||
]
|
]
|
||||||
@@ -559,6 +667,17 @@ void UPS_Editor_MainWidget::NativeConstruct()
|
|||||||
{
|
{
|
||||||
Super::NativeConstruct();
|
Super::NativeConstruct();
|
||||||
PopulateSpawnCatalog();
|
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();
|
PopulateSceneList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -570,65 +689,23 @@ void UPS_Editor_MainWidget::PopulateSceneList()
|
|||||||
UPS_Editor_SceneSerializer* Ser = SceneSerializer.Get();
|
UPS_Editor_SceneSerializer* Ser = SceneSerializer.Get();
|
||||||
if (!Ser) return;
|
if (!Ser) return;
|
||||||
|
|
||||||
// Get current level name for filtering (use override if set)
|
// Filter scenes by selected BaseLevel
|
||||||
FString CurrentLevel;
|
TArray<FString> Scenes = UPS_Editor_SceneLoader::GetSavedSceneNames(CurrentLevelFilter);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show current level
|
if (Scenes.Num() == 0)
|
||||||
if (!CurrentLevel.IsEmpty())
|
|
||||||
{
|
{
|
||||||
SceneListContainer->AddSlot()
|
SceneListContainer->AddSlot()
|
||||||
.AutoHeight()
|
.AutoHeight()
|
||||||
[
|
[
|
||||||
SNew(STextBlock)
|
SNew(STextBlock)
|
||||||
.Text(FText::Format(NSLOCTEXT("PS_Editor", "LevelLabel", "Level: {0}"), FText::FromString(CurrentLevel)))
|
.Text(FText::FromString(CurrentLevelFilter.IsEmpty() ? TEXT("No saved scenes") : FString::Printf(TEXT("No scenes for '%s'"), *CurrentLevelFilter)))
|
||||||
.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")))
|
|
||||||
.Font(FCoreStyle::GetDefaultFontStyle("Italic", 9))
|
.Font(FCoreStyle::GetDefaultFontStyle("Italic", 9))
|
||||||
.ColorAndOpacity(FLinearColor(0.5f, 0.5f, 0.5f))
|
.ColorAndOpacity(FLinearColor(0.5f, 0.5f, 0.5f))
|
||||||
];
|
];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show scenes for current level first
|
for (const FString& SceneName : Scenes)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
FString Name = SceneName;
|
FString Name = SceneName;
|
||||||
SceneListContainer->AddSlot()
|
SceneListContainer->AddSlot()
|
||||||
@@ -655,59 +732,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()
|
void UPS_Editor_MainWidget::PopulateSpawnCatalog()
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ private:
|
|||||||
// Scene save/load
|
// Scene save/load
|
||||||
TSharedPtr<SEditableTextBox> SaveNameField;
|
TSharedPtr<SEditableTextBox> SaveNameField;
|
||||||
TSharedPtr<SVerticalBox> SceneListContainer;
|
TSharedPtr<SVerticalBox> SceneListContainer;
|
||||||
|
TSharedPtr<STextBlock> LevelFilterText;
|
||||||
|
FString CurrentLevelFilter;
|
||||||
TArray<TSharedPtr<FSlateBrush>> CachedBrushes;
|
TArray<TSharedPtr<FSlateBrush>> CachedBrushes;
|
||||||
|
|
||||||
// Transform fields
|
// Transform fields
|
||||||
|
|||||||
Reference in New Issue
Block a user