From 77f2da027eea00e916432ed57704a6bcb44c3972 Mon Sep 17 00:00:00 2001 From: "j.foucher" Date: Sun, 12 Apr 2026 11:32:09 +0200 Subject: [PATCH] Add standalone SceneLoader for game runtime initialization UPS_Editor_SceneLoader is a BlueprintFunctionLibrary that loads PS_Editor JSON scenes without any editor infrastructure (no PlayerController, Gizmo, SelectionManager, or UI required). Usage from any GameMode: TArray Actors; UPS_Editor_SceneLoader::LoadScene(this, "MyScene", Actors); Handles: class loading (BP + C++), transforms, custom editable properties via reflection (ImportText), and spline point data. All functions are BlueprintCallable for BP GameMode usage. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Serialization/PS_Editor_SceneLoader.cpp | 187 ++++++++++++++++++ .../Serialization/PS_Editor_SceneLoader.h | 71 +++++++ 2 files changed, 258 insertions(+) create mode 100644 Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneLoader.cpp create mode 100644 Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneLoader.h 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 new file mode 100644 index 0000000..e37e75a --- /dev/null +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneLoader.cpp @@ -0,0 +1,187 @@ +#include "PS_Editor_SceneLoader.h" +#include "PS_Editor_SceneData.h" +#include "PS_Editor_EditableComponent.h" +#include "PS_Editor_SplineActor.h" +#include "JsonObjectConverter.h" +#include "Misc/FileHelper.h" +#include "Misc/Paths.h" +#include "UObject/UnrealType.h" +#include "Components/ActorComponent.h" + +bool UPS_Editor_SceneLoader::LoadScene(const UObject* WorldContextObject, const FString& SceneName, TArray& OutActors) +{ + OutActors.Empty(); + + if (!WorldContextObject || SceneName.IsEmpty()) + { + return false; + } + + UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); + if (!World) + { + return false; + } + + // Read JSON file + const FString FilePath = GetSceneFilePath(SceneName); + FString JsonString; + if (!FFileHelper::LoadFileToString(JsonString, *FilePath)) + { + UE_LOG(LogTemp, Error, TEXT("PS_Editor SceneLoader: Failed to read file: %s"), *FilePath); + return false; + } + + // Parse JSON to struct + FPS_Editor_SceneData SceneData; + if (!FJsonObjectConverter::JsonObjectStringToUStruct(JsonString, &SceneData, 0, 0)) + { + UE_LOG(LogTemp, Error, TEXT("PS_Editor SceneLoader: Failed to parse JSON from: %s"), *FilePath); + return false; + } + + return LoadSceneFromData(World, SceneData, OutActors); +} + +bool UPS_Editor_SceneLoader::LoadSceneFromData(UWorld* World, const FPS_Editor_SceneData& SceneData, TArray& OutActors) +{ + if (!World) + { + return false; + } + + int32 SpawnedCount = 0; + + for (const FPS_Editor_ActorData& ActorData : SceneData.Actors) + { + AActor* SpawnedActor = SpawnActorFromData(World, ActorData); + if (SpawnedActor) + { + OutActors.Add(SpawnedActor); + SpawnedCount++; + } + } + + UE_LOG(LogTemp, Log, TEXT("PS_Editor SceneLoader: Loaded %d actors from scene '%s'"), + SpawnedCount, *SceneData.SceneName); + return SpawnedCount > 0; +} + +AActor* UPS_Editor_SceneLoader::SpawnActorFromData(UWorld* World, const FPS_Editor_ActorData& ActorData) +{ + // Load class (supports both Blueprint and C++ classes) + UClass* LoadedClass = LoadObject(nullptr, *ActorData.ClassPath); + if (!LoadedClass) + { + LoadedClass = FindObject(nullptr, *ActorData.ClassPath); + } + if (!LoadedClass) + { + UE_LOG(LogTemp, Warning, TEXT("PS_Editor SceneLoader: Failed to load class: %s"), *ActorData.ClassPath); + return nullptr; + } + + // Spawn actor + FActorSpawnParameters SpawnParams; + SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn; + + AActor* SpawnedActor = World->SpawnActor(LoadedClass, ActorData.Location, ActorData.Rotation, SpawnParams); + if (!SpawnedActor) + { + UE_LOG(LogTemp, Warning, TEXT("PS_Editor SceneLoader: Failed to spawn: %s"), *ActorData.ClassPath); + return nullptr; + } + + // Apply scale and ensure movable + SpawnedActor->SetActorScale3D(ActorData.Scale); + if (USceneComponent* Root = SpawnedActor->GetRootComponent()) + { + Root->SetMobility(EComponentMobility::Movable); + } + + // Apply custom editable properties + if (ActorData.CustomProperties.Num() > 0) + { + ApplyCustomProperties(SpawnedActor, ActorData.CustomProperties); + } + + // Import spline data if applicable + if (APS_Editor_SplineActor* Spline = Cast(SpawnedActor)) + { + Spline->ImportFromCustomProperties(ActorData.CustomProperties); + } + + return SpawnedActor; +} + +void UPS_Editor_SceneLoader::ApplyCustomProperties(AActor* Actor, const TMap& CustomProperties) +{ + UPS_Editor_EditableComponent* EditComp = Actor->FindComponentByClass(); + if (!EditComp) return; + + for (const FName& Path : EditComp->EditableProperties) + { + FString CompName, PropName; + UPS_Editor_EditableComponent::ParsePropertyPath(Path, CompName, PropName); + const FString Key = Path.ToString(); + + const FString* ValueStr = CustomProperties.Find(Key); + if (!ValueStr) continue; + + // Find target object (actor or component) + UObject* Target = CompName.IsEmpty() ? static_cast(Actor) : nullptr; + if (!Target) + { + TArray Components; + Actor->GetComponents(Components); + for (UActorComponent* Comp : Components) + { + if (Comp && Comp->GetName() == CompName) + { + Target = Comp; + break; + } + } + } + + if (!Target) continue; + + // Set property value via reflection + FProperty* Prop = FindFProperty(Target->GetClass(), *PropName); + if (Prop) + { + Prop->ImportText_InContainer(**ValueStr, Target, Target, PPF_None); + if (UActorComponent* AsComp = Cast(Target)) + { + AsComp->MarkRenderStateDirty(); + } + } + } +} + +FString UPS_Editor_SceneLoader::GetScenesDirectory() +{ + return FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("PS_Editor"), TEXT("Scenes")); +} + +FString UPS_Editor_SceneLoader::GetSceneFilePath(const FString& SceneName) +{ + return FPaths::Combine(GetScenesDirectory(), SceneName + TEXT(".json")); +} + +TArray UPS_Editor_SceneLoader::GetSavedSceneNames() +{ + TArray SceneNames; + const FString Directory = GetScenesDirectory(); + + TArray Files; + IFileManager::Get().FindFiles(Files, *FPaths::Combine(Directory, TEXT("*.json")), true, false); + + for (const FString& File : Files) + { + SceneNames.Add(FPaths::GetBaseFilename(File)); + } + + SceneNames.Sort(); + return SceneNames; +} 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 new file mode 100644 index 0000000..9a654bf --- /dev/null +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Serialization/PS_Editor_SceneLoader.h @@ -0,0 +1,71 @@ +#pragma once + +#include "CoreMinimal.h" +#include "PS_Editor_SceneData.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "PS_Editor_SceneLoader.generated.h" + +/** + * Standalone scene loader for PS_Editor. + * Loads a JSON scene file and spawns all actors with their properties and spline data. + * + * This is the entry point for GAME MODE usage — it does NOT require + * PS_Editor's PlayerController, SelectionManager, Gizmo, or UI. + * Just call LoadScene() from your GameMode's BeginPlay. + * + * Usage: + * void AMyGameMode::BeginPlay() + * { + * Super::BeginPlay(); + * TArray SpawnedActors; + * UPS_Editor_SceneLoader::LoadScene(GetWorld(), TEXT("MyScene"), SpawnedActors); + * } + */ +UCLASS() +class PS_EDITOR_API UPS_Editor_SceneLoader : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + /** + * Load a scene from a JSON file and spawn all actors. + * + * @param World The world to spawn actors into. + * @param SceneName Scene name (without .json extension). + * @param OutActors Optional: filled with all spawned actors. + * @return True if the scene was loaded successfully. + */ + UFUNCTION(BlueprintCallable, Category = "PS Editor", meta = (WorldContext = "WorldContextObject")) + static bool LoadScene(const UObject* WorldContextObject, const FString& SceneName, TArray& OutActors); + + /** + * Load scene from an already-parsed SceneData struct. + * Useful if you want to parse the JSON yourself or modify data before spawning. + */ + static bool LoadSceneFromData(UWorld* World, const FPS_Editor_SceneData& SceneData, TArray& OutActors); + + /** + * Get the directory where scenes are stored. + */ + UFUNCTION(BlueprintCallable, Category = "PS Editor") + static FString GetScenesDirectory(); + + /** + * Get the full file path for a scene name. + */ + UFUNCTION(BlueprintCallable, Category = "PS Editor") + static FString GetSceneFilePath(const FString& SceneName); + + /** + * Get all saved scene names. + */ + UFUNCTION(BlueprintCallable, Category = "PS Editor") + static TArray GetSavedSceneNames(); + +private: + /** Spawn a single actor from saved data and apply all properties. */ + static AActor* SpawnActorFromData(UWorld* World, const FPS_Editor_ActorData& ActorData); + + /** Apply custom editable properties to a spawned actor. */ + static void ApplyCustomProperties(AActor* Actor, const TMap& CustomProperties); +};