From f6efc00c735d986353919769cabaa0912b93b384 Mon Sep 17 00:00:00 2001 From: "j.foucher" Date: Wed, 20 May 2026 15:10:52 +0200 Subject: [PATCH] Add LoadSceneAdditive / UnloadSceneAdditive for briefing scene preload Lets a briefing scene materialize only the preload-tagged actors from a scenario .pss (player placement markers, briefing props) without clearing the current world, pushing timeline data, registering ActorIds, or starting any auto-play. The actors spawned by each LoadSceneAdditive call are tracked in a static TWeakObjectPtr array; UnloadSceneAdditive destroys every still-valid one and clears the array. Typical BP usage on scenario change: UnloadSceneAdditive(this) -> LoadSceneAdditive(this, NewSceneName) No BP-side bookkeeping required. Filter defaults to PreloadOnly which matches the intended briefing use case; the parameter stays exposed for other niche cases (ExcludePreload, All). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Serialization/PS_Editor_SceneLoader.cpp | 96 +++++++++++++++++++ .../Serialization/PS_Editor_SceneLoader.h | 31 ++++++ 2 files changed, 127 insertions(+) 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 69783b3..4c07b26 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 @@ -124,6 +124,102 @@ bool UPS_Editor_SceneLoader::LoadScene(const UObject* WorldContextObject, const return true; } +TArray> UPS_Editor_SceneLoader::AdditiveSpawnedActors; + +bool UPS_Editor_SceneLoader::LoadSceneAdditive(const UObject* WorldContextObject, const FString& SceneName, EPS_Editor_SceneLoadFilter Filter, bool bStripEditorComponents) +{ + if (!WorldContextObject || SceneName.IsEmpty()) + { + return false; + } + + UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); + if (!World) + { + return false; + } + + // Read + parse the scenario JSON exactly like LoadScene + const FString FilePath = GetSceneFilePath(SceneName); + FString JsonString; + if (!FFileHelper::LoadFileToString(JsonString, *FilePath)) + { + UE_LOG(LogTemp, Error, TEXT("PS_Editor SceneLoader (Additive): Failed to read file: %s"), *FilePath); + return false; + } + + FPS_Editor_SceneData SceneData; + if (!FJsonObjectConverter::JsonObjectStringToUStruct(JsonString, &SceneData, 0, 0)) + { + UE_LOG(LogTemp, Error, TEXT("PS_Editor SceneLoader (Additive): Failed to parse JSON from: %s"), *FilePath); + return false; + } + + // Spawn matching actors WITHOUT touching the timeline subsystem, registering ActorIds, + // or starting any auto-play. The briefing scene drives its own timeline. + int32 SpawnedCount = 0; + bIsCurrentlyLoading = true; + for (const FPS_Editor_ActorData& ActorData : SceneData.Actors) + { + AActor* SpawnedActor = SpawnActorFromData(World, ActorData, Filter); + if (SpawnedActor) + { + AdditiveSpawnedActors.Add(SpawnedActor); + + if (bStripEditorComponents) + { + TArray ToRemove; + if (UActorComponent* Spawnable = SpawnedActor->FindComponentByClass()) + { + ToRemove.Add(Spawnable); + } + if (UActorComponent* Editable = SpawnedActor->FindComponentByClass()) + { + ToRemove.Add(Editable); + } + for (UActorComponent* Comp : ToRemove) + { + Comp->DestroyComponent(); + } + + if (IPS_Editor_SplineEditable* Spline = Cast(SpawnedActor)) + { + Spline->SetVisualizationVisible(false); + } + } + + SpawnedCount++; + } + } + bIsCurrentlyLoading = false; + + UE_LOG(LogTemp, Log, TEXT("PS_Editor SceneLoader (Additive): Spawned %d actor(s) from '%s' [filter=%d]"), + SpawnedCount, *SceneName, (int32)Filter); + + return SpawnedCount > 0; +} + +int32 UPS_Editor_SceneLoader::UnloadSceneAdditive(const UObject* WorldContextObject) +{ + int32 Destroyed = 0; + for (TWeakObjectPtr& Weak : AdditiveSpawnedActors) + { + if (AActor* Actor = Weak.Get()) + { + Actor->Destroy(); + ++Destroyed; + } + // else: already gone (GC, manual destroy, level reload) — skip silently + } + AdditiveSpawnedActors.Reset(); + + if (Destroyed > 0) + { + UE_LOG(LogTemp, Log, TEXT("PS_Editor SceneLoader (Additive): Unloaded %d actor(s)"), Destroyed); + } + return Destroyed; +} + bool UPS_Editor_SceneLoader::LoadSceneFromData(UWorld* World, const FPS_Editor_SceneData& SceneData, TArray& OutActors, EPS_Editor_SceneLoadFilter Filter) { if (!World) 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 c3dc8f1..cb2e01c 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 @@ -56,6 +56,33 @@ public: static bool LoadSceneFromData(UWorld* World, const FPS_Editor_SceneData& SceneData, TArray& OutActors, EPS_Editor_SceneLoadFilter Filter = EPS_Editor_SceneLoadFilter::All); + /** + * Additive load: spawns actors from a scenario WITHOUT clearing the current world and + * WITHOUT touching the timeline subsystem. Designed for briefing scenes where you only + * want to materialize preload-tagged actors (player placement markers, briefing props) + * configured in the main scenario. + * + * Spawned actors are tracked internally — call UnloadSceneAdditive() to destroy them all + * before loading the next scenario. + * + * @param SceneName Scenario name (without extension) + * @param Filter Which actors to spawn — defaults to PreloadOnly + * @param bStripEditorComponents If true, removes SpawnableComponent / EditableComponent after spawn + * @return True if at least one actor was spawned + */ + UFUNCTION(BlueprintCallable, Category = "PS_Editor", meta = (WorldContext = "WorldContextObject")) + static bool LoadSceneAdditive(const UObject* WorldContextObject, const FString& SceneName, + EPS_Editor_SceneLoadFilter Filter = EPS_Editor_SceneLoadFilter::PreloadOnly, + bool bStripEditorComponents = true); + + /** + * Destroy every actor previously spawned via LoadSceneAdditive. No-op if nothing was loaded. + * Call this before LoadSceneAdditive when switching briefing scenarios. + * @return Number of actors destroyed. + */ + UFUNCTION(BlueprintCallable, Category = "PS_Editor", meta = (WorldContext = "WorldContextObject")) + static int32 UnloadSceneAdditive(const UObject* WorldContextObject); + /** * Get the directory where scenes are stored. */ @@ -114,4 +141,8 @@ private: /** Apply custom editable properties to a spawned actor. */ static void ApplyCustomProperties(AActor* Actor, const TMap& CustomProperties); + + /** Actors spawned via LoadSceneAdditive, tracked so UnloadSceneAdditive can find them. + * TWeakObjectPtr handles GC / manual destruction gracefully (skipped at unload time). */ + static TArray> AdditiveSpawnedActors; };