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:
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
Reference in New Issue
Block a user