How to Browse All Blueprint Assets of a Defined Type in Unreal Engine

Context

I admire the simplicity of GameplayCue: create a blueprint, set the correct tag, and it’s ready. Replicating this for in-game events seemed logical, allowing our sound designer to connect events to Wwise effortlessly. Here’s a breakdown:

  1. The gameplay programmer defines a tag for the event, like “EventCue.OnHealthLow”.
  2. The sound designer creates an EventCue blueprint that matches this tag.
  3. At runtime, triggering this code instantiates all EventCue blueprints that match the tag.

While it sounds straightforward, implementing this in Unreal is a bit complex. Let’s go step by step.

The Solutions That Don’t Work

The FGraphNodeClassHelper class from the AIGraph module is easy to use but is editor-exclusive. If you want to use it anyway:

// I'm looking for all the UL0GameEventCue classes
FGraphNodeClassHelper Helper(UL0GameEventCue::StaticClass());
TArray<FGraphNodeClassData> AvailableClasses;
Helper.GatherClasses(UL0GameEventCue::StaticClass(), AvailableClasses);

for (FGraphNodeClassData& NodeClassData : AvailableClasses)
{
 // [...]
}

Or, you can use the Class Iterator, which works in both the Editor and Packaged versions but retrieve all the already loaded classes:

for (TObjectIterator<UClass> ClassIt; ClassIt; ++ClassIt)
{
    UClass* Class = *ClassIt;
}

The Correct Solution

Steps:

  1. Whip out the FAssetRegistryModule
  2. Get all the class derived from the desired one
  3. Get all the blueprints
  4. Operate an “union” between both list

Whip Out the FAssetRegistryModule

The FAssetRegistryModule allows you to query information about assets in the project. Although it works synchronously, making all queries on the game thread is not ideal. Improvements are suggested at the end.

// Get the pointer
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();

// For a scan to make sure AssetRegistry is ready
TArray<FString> ContentPaths;
// This will scan the full folder but obviously it's preferable to work on a dedicated one such as "/Game/Blueprints":
ContentPaths.Add(TEXT("/Game"));
AssetRegistry.ScanPathsSynchronous(ContentPaths);

Get All Classes Derived from the Desired One

To get blueprints that inherit from UL0GameEventCue, list all derived classes:

TSet<FTopLevelAssetPath> DerivedClassNames;
TArray<FTopLevelAssetPath> ClassNames{UL0GameEventCue::StaticClass()->GetClassPathName()};
TSet<FTopLevelAssetPath> ExcludedClassNames;
TSet<FTopLevelAssetPath> DerivedClassNames;
AssetRegistry.GetDerivedClassNames(ClassNames, ExcludedClassNames, DerivedClassNames);

Get All Blueprints

Unreal doesn’t have a direct way to search for all blueprints inheriting from a specific class. Instead, retrieve all blueprint classes:

TArray<FAssetData> BlueprintAssets;
FARFilter Filter;
Filter.ClassPaths.Add(UBlueprint::StaticClass()->GetClassPathName());
Filter.bRecursiveClasses = true;
// Filter.ClassPaths.Add(TEXT("/Game/Blueprints")); // If you want to limit the search to a specific folder
Filter.bRecursivePaths = true;
AssetRegistry.GetAssets(Filter, BlueprintAssets);

Operate a “Union” Between Both Lists

Now, use the union of the two lists: DerivedClassNames and BlueprintAssets:

for (FAssetData const& BlueprintAsset : BlueprintAssets)
{
	const FAssetTagValueRef GeneratedClassTag = BlueprintAsset.TagsAndValues.FindTag(FName("GeneratedClass"));
	if (GeneratedClassTag.IsSet())
	{
		FTopLevelAssetPath ClassPath(GeneratedClassTag.GetValue());
		if (DerivedClassNames.Contains(ClassPath))
		{
			// Now we can retrieve the class 
			FStringBuilderBase Builder;
			Builder << BlueprintAsset.PackageName << '.' << BlueprintAsset.AssetName << "_C";
			UClass* BlueprintGeneratedClass = LoadObject<UClass>(nullptr, Builder.ToString());
			if (TSubclassOf<UL0GameEventCue> CueClass = BlueprintGeneratedClass)
			{
				// [...]
			}
		}
	}
}

Remember that if you hold the UClass, it might be nullified by the garbage collector.

Going Further

All this work is done on the game thread, which might freeze your game with many assets. To alleviate this, consider using coroutines or other async methods.