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<AActor*> 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) <noreply@anthropic.com>
This commit is contained in:
2026-04-12 11:32:09 +02:00
parent 861c15c193
commit 77f2da027e
2 changed files with 258 additions and 0 deletions

View File

@@ -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<AActor*>& 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<AActor*>& 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<UClass>(nullptr, *ActorData.ClassPath);
if (!LoadedClass)
{
LoadedClass = FindObject<UClass>(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<AActor>(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<APS_Editor_SplineActor>(SpawnedActor))
{
Spline->ImportFromCustomProperties(ActorData.CustomProperties);
}
return SpawnedActor;
}
void UPS_Editor_SceneLoader::ApplyCustomProperties(AActor* Actor, const TMap<FString, FString>& CustomProperties)
{
UPS_Editor_EditableComponent* EditComp = Actor->FindComponentByClass<UPS_Editor_EditableComponent>();
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<UObject*>(Actor) : nullptr;
if (!Target)
{
TArray<UActorComponent*> 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<FProperty>(Target->GetClass(), *PropName);
if (Prop)
{
Prop->ImportText_InContainer(**ValueStr, Target, Target, PPF_None);
if (UActorComponent* AsComp = Cast<UActorComponent>(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<FString> UPS_Editor_SceneLoader::GetSavedSceneNames()
{
TArray<FString> SceneNames;
const FString Directory = GetScenesDirectory();
TArray<FString> 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;
}

View File

@@ -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<AActor*> 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<AActor*>& 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<AActor*>& 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<FString> 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<FString, FString>& CustomProperties);
};