There’s a few good resources around on the Asset Manager, but most of them skim over bundles and loading, and most of them don’t mention neat patterns for reuse, so it’s easy to look at the Asset Manager and wonder what it’s actually for when thinking about your project. This page is a collection of notes along with a sample project exploring some useful usage patterns the Asset Manager facilitates.
A sample project is available here with all the code from this post in there. The Asset Manager is enabled by default if you’re porting to your own project, so no need to go hunting in the plugins list.
The upstream doc for the Asset Manager is brief, but covers the basics of the system. Initially, the Asset Manager has two Primary Asset Types configured: Map and PrimaryAssetLabel. By configuring the search directories, this allows available Maps to be listed with no additional configuration using the asset manager:
UAssetManager &Manager = UAssetManager::Get();
TArray<FSoftObjectPath> Maps;
Manager.GetPrimaryAssetPathList(FPrimaryAssetType(TEXT("Map")), Maps);
Each type to be managed is configured by adding an entry to the Primary Asset Types to Scan
array in the Asset manager settings.
Each primary asset is uniquely identified by its FPrimaryAssetId
, which is composed of two FName
: an asset type and an asset
name. The Primary Asset Type property sets the type, in this case to "ExampleStaticData"
. This doesn’t need to match the name
of the class below, though it’s generally good practice to do so.
The next option is the Asset Base Class
, which is the class all Data Assets of this type must inherit from. The Asset Manager
will use an Asset Registry query to find filter out any assets that don’t match this base class, unless Has Blueprint Classes
has been checked. In this case, in my tests the Asset Base Class was ignored, and in its place the registry filtered on
ten blueprint base classes when building the list of Primary Assets:
Directories
and Specific Assets
are searched to populate the set of candidate assets, then passed to the Registry filter shown above.
Finally, Rules
is used to control cooking rules for all assets of this type. This can be overridden by both the global override
options below, or by the asset itself as we’ll see with PrimaryAssetLabel.
The global settings for the most part do what they say on the tin. Directories can be excluded wholesale for things like
test or debug assets using Directories to Exclude
, and there’s some options for overriding cook rules:
Primary Asset Rules
, Custom Primary Asset Rules
, and Only Cook Production Assets
.
Should Manager Determine Type and Name
is only needed for Primary Assets that don’t implement GetPrimaryAssetId()
. I haven’t yet
found a compelling reason not to implement GetPrimaryAssetId()
so keep this off.
Likewise Should Guess Type and Name in Editor
is probably just going to cause you trouble when you start testing outside
of the editor in packaged builds, so keep that off as well.
I haven’t used Should Acquire Mission Chunks on Load
, it sounds like it might be for when chunks are dynamically made available
such as with DLC. and Should Warn About Invalid Assets
is ok to keep on.
Beyond Map
, a second Primary Asset Type comes configured out of the box with UE5 called PrimaryAssetLabel
. This serves as a useful
demonstration of an Asset Type that isn’t directly linked to a single concept within a project, and instead functions as
a higher level abstraction over many assets. They’re also a good demonstration of “Bundles” which are quite simply an FName
mapped
to a list of asset paths and associated with a Primary Asset. By default they’re stored and updated within the Asset Manager when a Primary
Asset is saved.
Out of the box, a Data Asset of this type can build asset bundles from a number of sources:
The blueprint and other asset lists will be added to an “Explicit” bundle, the directory assets added to a “Directory” bundle, and the collection added to a “Collection” bundle. The directory search is recursive, so any assets in subfolders will also be added.
Concretely, this means creating a PrimaryAssetLabel
Data Asset in a directory with some other assets gives a convenient shortcut to
load all assets within that directory via the following:
void LoadDirectory(UDataAsset* DataAsset)
{
UAssetManager &Manager = UAssetManager::Get();
FPrimaryAssetId PrimaryAssetId = Manager.GetPrimaryAssetIdForObject(DataAsset);
TArray<FName> Bundles { "Directory" };
Manager.LoadPrimaryAsset(PrimaryAssetId, Bundles, FStreamableDelegate::CreateUObject(this, &AMyActor::MyFunctionToCallOnLoad));
}
Note that while this example uses the DataAsset object, it’s also completely reasonable to use the FPrimaryAssetId directly, particularly in cases where the Data Assets themselves are left unloaded until needed.
The other notable property on PrimaryAssetLabel
is one called “Rules” which allows the cook rules to be overridden for this asset.
This is handy if you want to use Data Assets to add or remove things from cooking.
By adding more Primary Asset Types, the utility of the Asset Manager can be increased to cover more than just loading or cooking a specified list or a directory of assets. There are two major roles that the Asset Manager and Data Assets can fill with some additional work: decoupling data from code so that blueprints aren’t required to serve both functions, and extending the PrimaryAssetLabel style of Data Asset to include metadata consumed from assets.
The simplest type of data asset is described in the official doc, and is what I will refer to in the sample and here as a Static Data Asset, since the data is assigned to the Data Asset within fields that are not changeable in the editor. These types of assets are perfectly suited to being consumed via Interface since the properties available are known ahead of time.
In the example project, UExampleStaticData
is used to show this concept, with a mesh and a texture soft ref both added
to the "MyBundle"
bundle.
This strategy was proposed by Michael Allar on Twitter/Youtube and
results in a nice system where different consumers of Data Assets share the same Data Asset and consume its parts in different ways,
while producers need only push the relevant data asset to the consumer with no additional logic. This means you can have one Data
Asset for something like an Item, which contains a Material to be used in a vendor’s shop interface, the stats used for a GameplayAbility
and also the Static Mesh
used when it’s dropped on the ground.
This strategy is shown in the Example Project, where two trigger boxes with different Data Assets supply consumer actors with their Data Asset when the player enters the trigger volume. Both a Blueprint Class and a Native Class are tested.
This is as simple as creating an interface with a Data Asset child class or PrimaryAssetId as a parameter and having any class that needs to consume the Data Asset implement that interface.
#pragma once
#include "CoreMinimal.h"
#include "ExampleStaticData.h"
#include "UObject/Interface.h"
#include "DataAssetConsumer.generated.h"
UINTERFACE(BlueprintType)
class UDataAssetConsumer : public UInterface
{
GENERATED_BODY()
};
class ASSETMANAGERDEMO_API IDataAssetConsumer
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
void ConsumeAsset(UExampleStaticData *StaticDataAsset);
// This is another option, a little more flexible since the asset doesn't need to be loaded in order to be
// passed around. I prefer the above since I haven't found a case where a producer should supply a consumer
// an unloaded Asset, and in general Data Assets themselves have no hard refs and thus can be loaded Synchronously
// UFUNCTION(BlueprintNativeEvent, BlueprintCallable)
// void ConsumeAssetId(FPrimaryAssetId AssetId);
};
It’s possible to use a single interface for an entire project, and cast the UDataAsset to the specific type of asset required within the consumer, but I couldn’t think of a good example of a time when the producer doesn’t know what type of asset it’s going to supply to the consumer, so I think for now I’d make one interface per producer-consumer relationship.
The other major role Data Assets be used for is to easily control when groups of assets are loaded. PrimaryAssetLabel
provides a good
starting point for this, but can be extended to provide even more utility both for loading and other general categorisation tasks. The
sample project contains a Data Asset called ExampleProcessedData
, along with a Data Asset DA_ExampleDataAsset
in the AssetManagerDemo
folder. This class shows how to not only load all the assets in a directory, but also filter them based on a simple regular expression,
and store + save metadata about particular assets as well as the bundles for loading.
UExampleProcessedData
firstly inherits from PrimaryDataAsset
since it provides an implementation of GetPrimaryAssetId()
that can
be reused without modification. It then overrides PreSave
and UpdateAssetBundleData
in order to add an additional step during PreSave
after the asset’s UPROPERTY
have been parsed to populate bundles.
This additional step is based on the PreSave
method in PrimaryAssetLabel
, and searches a given directory for assets, then places them
in a given bundle.
UCLASS()
class ASSETMANAGERDEMO_API UExampleProcessedData : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UExampleProcessedData();
// Add metadata-specified UPROPERTY assets to bundles as usual
virtual void PreSave(FObjectPreSaveContext ObjectSaveContext) override;
// But also do our own processing here and add some customised bundles
virtual void ProcessData();
#if WITH_EDITORONLY_DATA
// This gets called during PostLoad and will wipe our customised bundles if left as default
virtual void UpdateAssetBundleData() override {};
#endif
protected:
UPROPERTY(EditAnywhere, Category = "Custom Processing")
FName CustomAssetBundle;
UPROPERTY(EditAnywhere, Category = "Custom Processing")
FName CustomAssetBaseDirectory;
UPROPERTY(EditAnywhere, Category = "Custom Processing")
bool bCustomAssetRecurseDirectory;
};
PreSave’s implementation can then do the following, so that we get both the automatic bundles and have the chance to do our own processing and make our own bundles.
void UExampleProcessedData::PreSave(FObjectPreSaveContext ObjectPreSaveContext)
{
Super::PreSave(ObjectPreSaveContext);
// By default parse the metadata of bundles
if (UAssetManager::IsValid())
{
AssetBundleData.Reset();
UAssetManager::Get().InitializeAssetBundlesFromMetadata(this, AssetBundleData);
// Do custom data processing
ProcessData();
if (UAssetManager::IsValid())
{
// Bundles may have changed, refresh
UAssetManager::Get().RefreshAssetData(this);
}
// Update asset rules
FPrimaryAssetId PrimaryAssetId = GetPrimaryAssetId();
UAssetManager::Get().SetPrimaryAssetRules(PrimaryAssetId, Rules);
}
}
void UExampleProcessedData::ProcessData()
{
UAssetManager& Manager = UAssetManager::Get();
IAssetRegistry& AssetRegistry = Manager.GetAssetRegistry();
FARFilter RegistryFilter;
RegistryFilter.PackagePaths.Add(CustomAssetBaseDirectory);
RegistryFilter.bRecursivePaths = bCustomAssetRecurseDirectory;
TArray<FAssetData> RegistryAssets;
AssetRegistry.GetAssets(RegistryFilter, RegistryAssets);
for (const FAssetData& AssetData : RegistryAssets)
{
FSoftObjectPath AssetRef = Manager.GetAssetPathForData(AssetData);
if (!AssetRef.IsNull())
{
NewPaths.Add(AssetRef);
}
}
AssetBundleData.SetBundleAssets(CustomAssetBundle, MoveTemp(NewPaths));
This is almost identical to the PrimaryAssetLabel
implementation, but instead of grabbing the asset’s current directory, we
can specify one of our own, control recursion and also the bundle to be used. Another good way to get more control is by exposing
the RegistryFilter
itself as a UPROPERTY
on the Data Asset.
Where this gets more interesting is by modifying the inner loop and considering a case where an asset has some data on it that we
wish to query. A use case I have for this is that I have different biomes in a procedural level generator, each with a FGameplayTagContainer
storing tags that describe its properties. I can create a Data Asset that consumes all of my Biome assets within the editor to create a pool
of choices for the level generator, and alongside each tileset I can store its Tags without needing to load the tileset itself.
Here is the updated ProcessData
that grabs the Tags using an interface from any asset that matches a regex and stores them in a property.
void UExampleProcessedData::ProcessData()
{
TaggedCustomAssets.Reset();
UAssetManager& Manager = UAssetManager::Get();
IAssetRegistry& AssetRegistry = Manager.GetAssetRegistry();
FARFilter RegistryFilter;
RegistryFilter.PackagePaths.Add(CustomAssetBaseDirectory);
RegistryFilter.bRecursivePaths = bCustomAssetRecurseDirectory;
TArray<FAssetData> RegistryAssets;
//AssetRegistry.GetAssetsByPath(PackagePath, RegistryAssets, true);
AssetRegistry.GetAssets(RegistryFilter, RegistryAssets);
TArray<FSoftObjectPath> NewPaths;
FRegexPattern Regex = FRegexPattern(InterfaceAssetMatchRegex);
for (const FAssetData& AssetData : RegistryAssets)
{
FSoftObjectPath AssetRef = Manager.GetAssetPathForData(AssetData);
if (!AssetRef.IsNull())
{
NewPaths.Add(AssetRef);
FRegexMatcher Matcher(Regex, AssetData.ObjectPath.ToString());
if (!InterfaceAssetMatchRegex.IsEmpty() && Matcher.FindNext())
{
UObject* AssetObj = AssetRef.TryLoad();
UBlueprintGeneratedClass* AssetBP = Cast<UBlueprintGeneratedClass>(AssetObj);
if (AssetBP)
{
auto CDO = AssetBP->GetDefaultObject<AActor>();
if (CDO && CDO->Implements<UExampleInterface>())
{
FGameplayTagContainer Tags = IExampleInterface::Execute_GetTagContainer(CDO);
if (!Tags.IsEmpty())
{
TaggedCustomAssets.Add(AssetRef, Tags);
}
}
}
}
}
}
AssetBundleData.SetBundleAssets(CustomAssetBundle, MoveTemp(NewPaths));
}
The Asset Path will then be stored along with its FGameplayTagContainer
for lookup at runtime, as well as within the bundle for loading.
Finally, I wanted to double check assets are loaded and unloaded correctly. There is a DemoActor
class with several functions that achieve
this, and it’s preferable to run this test in standalone mode since the editor itself may hold references to objects that prevent them from
being unloaded. A peer had mentioned to me some difficulty in getting a Data Asset to load without also bringing along all of its bundles,
which is easily testable using these functions.
UCLASS(Blueprintable)
class ASSETMANAGERDEMO_API ADemoActor : public AActor
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Asset Manager Demo")
void AsyncLoadBundle();
UFUNCTION(BlueprintCallable, Category = "Asset Manager Demo")
void PrintDataAssetState();
UFUNCTION(BlueprintCallable, Category = "Asset Manager Demo")
void LoadPrimaryAssetObject();
UFUNCTION(BlueprintCallable, Category = "Asset Manager Demo")
void UnloadPrimaryAssetObject();
UFUNCTION(BlueprintCallable, Category = "Asset Manager Demo")
void UnloadPrimaryAsset();
protected:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Asset Manager Demo")
FPrimaryAssetId PrimaryAssetId;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Asset Manager Demo")
FName Bundle;
};
void ADemoActor::AsyncLoadBundle()
{
UE_LOG(LogTemp, Verbose, TEXT("Load Primary Asset Bundles"));
PrintDataAssetState();
UAssetManager &Manager = UAssetManager::Get();
TArray<FName> Bundles { Bundle };
Manager.LoadPrimaryAsset(PrimaryAssetId, Bundles, FStreamableDelegate::CreateUObject(this, &ADemoActor::PrintDataAssetState));
}
void ADemoActor::UnloadPrimaryAsset()
{
UE_LOG(LogTemp, Verbose, TEXT("Unload Primary Asset"));
PrintDataAssetState();
UAssetManager &Manager = UAssetManager::Get();
Manager.UnloadPrimaryAsset(PrimaryAssetId);
GEngine->ForceGarbageCollection(true);
PrintDataAssetState();
}
void ADemoActor::LoadPrimaryAssetObject()
{
UE_LOG(LogTemp, Verbose, TEXT("Load Primary Asset Object"));
PrintDataAssetState();
UAssetManager &Manager = UAssetManager::Get();
auto Path = Manager.GetPrimaryAssetPath(PrimaryAssetId);
FSoftObjectPtr Ptr(Path);
UObject *Obj = Ptr.LoadSynchronous();
if (!Obj)
UE_LOG(LogTemp, Error, TEXT("Failed to load primary object %s using path %s"), *PrimaryAssetId.ToString(), *Path.ToString());
PrintDataAssetState();
}
void ADemoActor::UnloadPrimaryAssetObject()
{
UE_LOG(LogTemp, Verbose, TEXT("Unload Primary Asset Object"));
PrintDataAssetState();
UAssetManager &Manager = UAssetManager::Get();
auto Path = Manager.GetPrimaryAssetPath(PrimaryAssetId);
FStreamableManager StreamManager;
StreamManager.Unload(Path);
PrintDataAssetState();
}
void ADemoActor::PrintDataAssetState()
{
UAssetManager &Manager = UAssetManager::Get();
auto Path = Manager.GetPrimaryAssetPath(PrimaryAssetId);
FSoftObjectPtr Ptr(Path);
if (Ptr.IsValid())
UE_LOG(LogTemp, Warning, TEXT("Loaded: Primary Asset object %s"), *PrimaryAssetId.ToString())
else
UE_LOG(LogTemp, Error, TEXT("Not Loaded: Primary Asset object %s"), *PrimaryAssetId.ToString())
TArray<FAssetBundleEntry> Entries;
Manager.GetAssetBundleEntries(PrimaryAssetId, Entries);
for (auto &Entry : Entries)
{
for (auto &Asset : Entry.BundleAssets)
{
FSoftObjectPtr AssetPtr(Asset);
if (AssetPtr.IsValid())
UE_LOG(LogTemp, Warning, TEXT("Loaded: Asset %s in bundle %s"), *Asset.ToString(), *Bundle.ToString())
else
UE_LOG(LogTemp, Error, TEXT("Not Loaded: Asset %s in bundle %s"), *Asset.ToString(), *Bundle.ToString())
}
}
}
The Demo Actor with the code above is given a BeginPlay
in blueprint to run a simple test that loads and unloads the given Primary Asset
To check the results, run in standalone and then check AssetManagerDemo\Saved\Logs\AssetManagerDemo_2.log
, which should have the following
down the bottom once the test has run (potentially interleaved with other log output):
[2022.03.25-03.06.28:719][ 0]LogWorld: Bringing up level for play took: 0.002268
[2022.03.25-03.06.28:726][ 0]LogTemp: Error: Not Loaded: Primary Asset object ExampleProcessedData:DA_ExampleDataAsset
[2022.03.25-03.06.28:726][ 0]LogTemp: Error: Not Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorBWithIface.BP_ExampleActorBWithIface_C in bundle MyBundle
[2022.03.25-03.06.28:726][ 0]LogTemp: Error: Not Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorBNoIface.BP_ExampleActorBNoIface_C in bundle MyBundle
[2022.03.25-03.06.28:726][ 0]LogTemp: Error: Not Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorAWithIface.BP_ExampleActorAWithIface_C in bundle MyBundle
[2022.03.25-03.06.28:726][ 0]LogTemp: Error: Not Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorANoIface.BP_ExampleActorANoIface_C in bundle MyBundle
[2022.03.25-03.06.28:744][ 0]LogTemp: Warning: Loaded: Primary Asset object ExampleProcessedData:DA_ExampleDataAsset
[2022.03.25-03.06.28:744][ 0]LogTemp: Error: Not Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorBWithIface.BP_ExampleActorBWithIface_C in bundle MyBundle
[2022.03.25-03.06.28:744][ 0]LogTemp: Error: Not Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorBNoIface.BP_ExampleActorBNoIface_C in bundle MyBundle
[2022.03.25-03.06.28:744][ 0]LogTemp: Error: Not Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorAWithIface.BP_ExampleActorAWithIface_C in bundle MyBundle
[2022.03.25-03.06.28:744][ 0]LogTemp: Error: Not Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorANoIface.BP_ExampleActorANoIface_C in bundle MyBundle
[2022.03.25-03.06.32:637][396]LogTemp: Warning: Loaded: Primary Asset object ExampleProcessedData:DA_ExampleDataAsset
[2022.03.25-03.06.32:637][396]LogTemp: Error: Not Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorBWithIface.BP_ExampleActorBWithIface_C in bundle MyBundle
[2022.03.25-03.06.32:637][396]LogTemp: Error: Not Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorBNoIface.BP_ExampleActorBNoIface_C in bundle MyBundle
[2022.03.25-03.06.32:637][396]LogTemp: Error: Not Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorAWithIface.BP_ExampleActorAWithIface_C in bundle MyBundle
[2022.03.25-03.06.32:637][396]LogTemp: Error: Not Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorANoIface.BP_ExampleActorANoIface_C in bundle MyBundle
[2022.03.25-03.06.32:659][399]LogTemp: Warning: Loaded: Primary Asset object ExampleProcessedData:DA_ExampleDataAsset
[2022.03.25-03.06.32:659][399]LogTemp: Warning: Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorBWithIface.BP_ExampleActorBWithIface_C in bundle MyBundle
[2022.03.25-03.06.32:659][399]LogTemp: Warning: Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorBNoIface.BP_ExampleActorBNoIface_C in bundle MyBundle
[2022.03.25-03.06.32:659][399]LogTemp: Warning: Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorAWithIface.BP_ExampleActorAWithIface_C in bundle MyBundle
[2022.03.25-03.06.32:660][399]LogTemp: Warning: Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorANoIface.BP_ExampleActorANoIface_C in bundle MyBundle
[2022.03.25-03.06.35:641][835]LogBlueprintUserMessages: [BP_DemoActor_C_1] Unload
[2022.03.25-03.06.35:641][835]LogTemp: Warning: Loaded: Primary Asset object ExampleProcessedData:DA_ExampleDataAsset
[2022.03.25-03.06.35:641][835]LogTemp: Warning: Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorBWithIface.BP_ExampleActorBWithIface_C in bundle MyBundle
[2022.03.25-03.06.35:641][835]LogTemp: Warning: Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorBNoIface.BP_ExampleActorBNoIface_C in bundle MyBundle
[2022.03.25-03.06.35:641][835]LogTemp: Warning: Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorAWithIface.BP_ExampleActorAWithIface_C in bundle MyBundle
[2022.03.25-03.06.35:641][835]LogTemp: Warning: Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorANoIface.BP_ExampleActorANoIface_C in bundle MyBundle
[2022.03.25-03.06.35:641][835]LogTemp: Warning: Loaded: Primary Asset object ExampleProcessedData:DA_ExampleDataAsset
[2022.03.25-03.06.35:641][835]LogTemp: Warning: Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorBWithIface.BP_ExampleActorBWithIface_C in bundle MyBundle
[2022.03.25-03.06.35:641][835]LogTemp: Warning: Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorBNoIface.BP_ExampleActorBNoIface_C in bundle MyBundle
[2022.03.25-03.06.35:641][835]LogTemp: Warning: Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorAWithIface.BP_ExampleActorAWithIface_C in bundle MyBundle
[2022.03.25-03.06.35:641][835]LogTemp: Warning: Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorANoIface.BP_ExampleActorANoIface_C in bundle MyBundle
[2022.03.25-03.06.38:643][278]LogBlueprintUserMessages: [BP_DemoActor_C_1] Finished
[2022.03.25-03.06.38:643][278]LogTemp: Error: Not Loaded: Primary Asset object ExampleProcessedData:DA_ExampleDataAsset
[2022.03.25-03.06.38:643][278]LogTemp: Error: Not Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorBWithIface.BP_ExampleActorBWithIface_C in bundle MyBundle
[2022.03.25-03.06.38:643][278]LogTemp: Error: Not Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorBNoIface.BP_ExampleActorBNoIface_C in bundle MyBundle
[2022.03.25-03.06.38:643][278]LogTemp: Error: Not Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorAWithIface.BP_ExampleActorAWithIface_C in bundle MyBundle
[2022.03.25-03.06.38:643][278]LogTemp: Error: Not Loaded: Asset /Game/AssetManagerDemo/ProcessedDataExampleAssets/BP_ExampleActorANoIface.BP_ExampleActorANoIface_C in bundle MyBundle
We can see that initially nothing was loaded, then the Data Asset was loaded without its bundles, allowing us to query the TagContainers and available assets, then later once we loaded the bundles all the assets were pointing to live Objects, and then after an Unload and forcing Garbage Collection everything is unloaded again. Note: don’t force GC at runtime unless you really know what you’re doing, it’s only used here as part of testing.
Since we’ve cleanly separated the code from the data, we could theoretically serialise all the data in the game to text much more easily than in the previous situation where there might be blueprint graphs. There’s a plugin here that might help with this, the core of which is Open Source.