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) <noreply@anthropic.com>
This commit is contained in:
@@ -124,6 +124,102 @@ bool UPS_Editor_SceneLoader::LoadScene(const UObject* WorldContextObject, const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TArray<TWeakObjectPtr<AActor>> 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<UActorComponent*> ToRemove;
|
||||||
|
if (UActorComponent* Spawnable = SpawnedActor->FindComponentByClass<UPS_Editor_SpawnableComponent>())
|
||||||
|
{
|
||||||
|
ToRemove.Add(Spawnable);
|
||||||
|
}
|
||||||
|
if (UActorComponent* Editable = SpawnedActor->FindComponentByClass<UPS_Editor_EditableComponent>())
|
||||||
|
{
|
||||||
|
ToRemove.Add(Editable);
|
||||||
|
}
|
||||||
|
for (UActorComponent* Comp : ToRemove)
|
||||||
|
{
|
||||||
|
Comp->DestroyComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(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<AActor>& 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<AActor*>& OutActors, EPS_Editor_SceneLoadFilter Filter)
|
bool UPS_Editor_SceneLoader::LoadSceneFromData(UWorld* World, const FPS_Editor_SceneData& SceneData, TArray<AActor*>& OutActors, EPS_Editor_SceneLoadFilter Filter)
|
||||||
{
|
{
|
||||||
if (!World)
|
if (!World)
|
||||||
|
|||||||
@@ -56,6 +56,33 @@ public:
|
|||||||
static bool LoadSceneFromData(UWorld* World, const FPS_Editor_SceneData& SceneData, TArray<AActor*>& OutActors,
|
static bool LoadSceneFromData(UWorld* World, const FPS_Editor_SceneData& SceneData, TArray<AActor*>& OutActors,
|
||||||
EPS_Editor_SceneLoadFilter Filter = EPS_Editor_SceneLoadFilter::All);
|
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.
|
* Get the directory where scenes are stored.
|
||||||
*/
|
*/
|
||||||
@@ -114,4 +141,8 @@ private:
|
|||||||
|
|
||||||
/** Apply custom editable properties to a spawned actor. */
|
/** Apply custom editable properties to a spawned actor. */
|
||||||
static void ApplyCustomProperties(AActor* Actor, const TMap<FString, FString>& CustomProperties);
|
static void ApplyCustomProperties(AActor* Actor, const TMap<FString, FString>& CustomProperties);
|
||||||
|
|
||||||
|
/** Actors spawned via LoadSceneAdditive, tracked so UnloadSceneAdditive can find them.
|
||||||
|
* TWeakObjectPtr handles GC / manual destruction gracefully (skipped at unload time). */
|
||||||
|
static TArray<TWeakObjectPtr<AActor>> AdditiveSpawnedActors;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user