TObjectIterator<UClass> skipping unloaded assets

Hey all,

I’m trying to iterate over all subclasses of a base class to auto-register these classes. I’m using a TObjectIterator<UClass> for this in an actor’s BeginPlay:



	for (TObjectIterator<UClass> Itr; Itr; ++Itr)
	{
		if (Itr->IsChildOf(UMyBaseClass::StaticClass()) && !Itr->HasAnyClassFlags(CLASS_Abstract))
		{
			// Process the subclass here
		}
	}



The parent class is a blueprintable UObject. The TObjectIterator correctly finds blueprint classes as long as I have opened them before in the editor, but as long as they haven’t been opened they won’t be detected. Does anyone know a way around this? An alternative method for auto-registering blueprints or a way to force loading a class?

You need to use the asset registry. Load the asset registry module (FAssetRegistryModule), from that you can get an IAssetRegistry interface which gives access to all registered classes, whether loaded or not.

Keep in mind that you’ll need to ensure that all these subclasses are being cooked for this to work in a packaged build.

Thanks, that was what I needed! Here is my working code:



	FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(FName("AssetRegistry"));
	IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
	
	// Need to do this if running in the editor with -game to make sure that the assets in the following path are available
	TArray<FString> PathsToScan;
	PathsToScan.Add(TEXT("/Game/Story/"));
	AssetRegistry.ScanPathsSynchronous(PathsToScan);
	
	// Get asset in path
	TArray<FAssetData> ScriptAssetList;
	AssetRegistry.GetAssetsByPath(FName("/Game/Story/"), ScriptAssetList, /*bRecursive=*/true);

	// Ensure all assets are loaded
	for (const FAssetData& Asset : ScriptAssetList)
	{
		// Gets the loaded asset, loads it if necessary
		Asset.GetAsset();
		UE_LOG(AlmLog, Warning, TEXT("Asset found: %s"), *Asset.AssetName.ToString());
	}


Reference used: How to get a list of assets from code? - Asset Creation - Unreal Engine Forums

After this is executed, all blueprints in the Content/Story folder and subfolders are loaded. Then their classes are found by TObjectIterator<UClass>. There’s probably a more efficient way so that the TObjectIterator<UClass> isn’t necessary, but I’m happy with this for the time being.

Yeah ideally you want to avoid using TObjectIterator, it can return a bunch of stuff you weren’t expecting. For example, in the case of UClass, it will likely find skeleton classes which are kind of intermediate objects in the blueprint compilation process.

You’re right and it irked me right after deciding to leave it for now, so I decided to clean up that TObjectIterator sooner rather than later. This is my code for finding all blueprints that inherit from the C++ type UScriptedWorldEvent in the folder Content/Story:



	// Load asset registry module
	FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(FName("AssetRegistry"));
	IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
	
	// Scan specific path
	TArray<FString> PathsToScan;
	PathsToScan.Add(TEXT("/Game/Story/"));
	AssetRegistry.ScanPathsSynchronous(PathsToScan);
	
	// Get all assets in the path, does not load them
	TArray<FAssetData> ScriptAssetList;
	AssetRegistry.GetAssetsByPath(FName("/Game/Story/"), ScriptAssetList, /*bRecursive=*/true);

	// Ensure all assets are loaded and store their class
	TArray<TSubclassOf<UScriptedWorldEvent>> EventClasses;
	for (const FAssetData& Asset : ScriptAssetList)
	{
		// Skip non blueprint assets
		const UBlueprint* BlueprintObj = Cast<UBlueprint>(Asset.GetAsset());
		if (!BlueprintObj)
			continue;

		// Check whether blueprint class has parent class we're looking for
		UClass* BlueprintClass = BlueprintObj->GeneratedClass;
		if (!BlueprintClass || !BlueprintClass->IsChildOf(UScriptedWorldEvent::StaticClass()))
			continue;

		// Store class
		EventClasses.Add(BlueprintClass);
	}


Note thet FAssetData::GetClass would return the generic ‘UBlueprint’ UClass, but casting the asset UObject to UBlueprint and getting the GeneratedClass retrieves the actual blueprint class. So with this there is no longer a need for TObjectIterator.

1 Like

Dear [MENTION=23394]Zhi Kang Shao[/MENTION]

Thank you for sharing your research Zhi!

:heart:

Rama

PS: I like your new forum name :slight_smile:

You’re welcome, hope it helps people who want to achieve the same. :slight_smile:

Necroing just in case people have weird bugs when packaging with this…
Eg. in a packaged build your assets do not appear when used, or return nullptrs all over the place:

If you only reference certain assets in this manner, the packaging tool will not be aware that those assets are, in fact, being used, and will not include them in a packaged build.

The solution to this problem is to include the folder(s) you are accessing in this manner is to edit DefaultGame.ini (or edit this file through the editor in ProjectSettings/Packaging):

[/Script/UnrealEd.ProjectPackagingSettings]
+DirectoriesToAlwaysCook=(Path=“/Game/YourFolderToInclude”)

This will make sure that that folder is always packaged, and anything you grab from there will be valid.

Again, excuse the necro, I thought people would probably want to know about this if they were having issues.

If you do end up using a TObjectIterator, you can skip if the UClass object is transient. This seems to exclude funky cases like SKEL_, TRASH_ and REINST_ classes.

I.e.

if (Class->HasAnyFlags(RF_Transient))
{
    continue;
}
1 Like