I have a level generator that uses UE5 level instance to create rooms. Each room can be used multiple time at once and are always loaded. What I need to do is somehow get the actors that are from that particular level instance to open or close connected gates based on the generation. Problem is, level instances always belong to the persistent level and all actors spawn in it. Is there any way to get actors that belong to instanced levels?
I figured out a method that seems to work today, after finding little help on google. It does require some C++, but it’s pretty easy.
Step 1: You’ll need to define the following function:
// in LevelUtils.h...
UFUNCTION(BlueprintCallable, BlueprintPure, Meta = (WorldContext = WorldContextObject))
static void GetAllActorsOfClassFromStreamLevel(ULevelStreaming * LevelStreaming, TArray<AActor *> & OutActors);
// in LevelUtils.cpp...
void ULevelUtils::GetAllActorsOfClassFromStreamLevel(ULevelStreaming * LevelStreaming, TSubclassOf<AActor> ActorClass, TArray<AActor *> & OutActors)
{
if (!LevelStreaming)
{
OutActors.Empty();
return;
}
OutActors = LevelStreaming->GetLoadedLevel()->Actors.FilterByPredicate([ActorClass](const auto Actor)
{
return Actor && Actor->GetClass() == ActorClass;
});
}
Step 2: Create your level instance via the Load Level Instance node. The output pin will give you a ULevelStreaming pointer. With this, you can wait for OnLevelLoaded to know when the level instance finishes loading.
Step 3: Once the level is loaded, call the provided function, above, on the ULevelStreaming object.
And there you have it! Surprisingly easy, given how little information is available online for this
See GameplayStatics.cpp lines 999 - 1106 if you need to repurpose any of the other “Get Actor(s)” functions for level instances.
How simple actually is this? I’m trying to get help on Unreal Source but they’re saying that you would need to make a fork of the engine to implement these changes to LevelUtils.h and .cpp
In my project I added a new C++ class so that the code base would be created, then in VSS I opened my project and navigated to the LevelUtils and added the code to the css file and the header file. I saved and compiled but this isn’t working for me. Did you actually get this to work? If so, would you mind explaining in a bit more detail how to get this working?
Are you making a custom .h and cpp file called LevelUtils and then naming the class ULevelUtils in the header file? If so what is it derived from, a public UObject?
I went and created a new class called LevelUtils, here is what I put in the h file:
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "LevelUtils.generated.h"
UCLASS(BlueprintType, Blueprintable)
class GAME_API ALevelUtils : public AActor
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, BlueprintPure, Meta = (WorldContext = WorldContextObject))
static void GetAllActorsOfClassFromStreamLevel(ULevelStreaming* LevelStreaming, TSubclassOf<AActor> ActorClass, TArray<AActor *> & OutActors);
};
This is what I put in the cpp file:
#include "LevelUtils.h"
void ALevelUtils::GetAllActorsOfClassFromStreamLevel(ULevelStreaming* LevelStreaming, TSubclassOf<AActor> ActorClass, TArray<AActor *> & OutActors)
{
if (!LevelStreaming)
{
OutActors.Empty();
return;
}
OutActors = LevelStreaming->GetLoadedLevel()->Actors.FilterByPredicate([ActorClass](const auto Actor)
{
return Actor && Actor->GetClass() == ActorClass;
});
}
I’m getting this error as soon as I try and access that data through blueprints
Was it possible to find out the reason for the “access violation”?
Yeah, I’m just doing somethings fundamentally wrong. I added a check to see if LevelStreaming->GetLoadedLevel() was valid and it returned straight away instead of throwing the error. Haven’t been able to revisit this issue in some time. If I get it working I’ll post my solution though.
Just to wrap this up and for anyone else that finds this from search:
The level instance will need to finish loading before it can be queried. Here’s a quick way to do this in BPs:
Additionally here is a modified reusable version of the function with more null checking to prevent possible crashes:
void ULevelUtilities::GetAllActorsOfClassFromStreamLevel(const ULevelStreaming* LevelStreaming,
TArray<AActor*>& OutActors, UClass* Class)
{
// Clear the output array
OutActors.Empty();
// Validate LevelStreaming input
if (!LevelStreaming)
{
return;
}
// Get the loaded level
ULevel* LoadedLevel = LevelStreaming->GetLoadedLevel();
if (!LoadedLevel)
{
return;
}
// Validate the class input
if (!Class)
{
return;
}
// Filter actors by the specified class
for (AActor* Actor : LoadedLevel->Actors)
{
if (Actor && Actor->IsA(Class))
{
OutActors.Add(Actor);
}
}
}