I’m hoping someone will be able to explain loading content at runtime to me. I’ve dug endlessly over the last few days at the very scant documentation and Answer Hub questions about this, and nothing I try is able to work. As of right now, I made a .pak file by creating a new project, a new blank level, dropping a single static mesh into the assets and level, then packaging it out. Then I ran UnrealPak.exe with the following -create= manifest:
"D:\Unreal\Projects\PackageBuilder\Saved\Cooked\WindowsNoEditor\PackageBuilder\Content\dronepackmaterial.uasset" "../../../PackageBuilder/Content/dronepackmaterial.uasset"
"D:\Unreal\Projects\PackageBuilder\Saved\Cooked\WindowsNoEditor\PackageBuilder\Content\dronepackmesh.uasset" "../../../PackageBuilder/Content/dronepackmesh.uasset"
"D:\Unreal\Projects\PackageBuilder\Saved\Cooked\WindowsNoEditor\PackageBuilder\Content\emptymap.umap" "../../../PackageBuilder/Content/emptymap.umap"
This created a “testpak.pak” file which I am trying to load at runtime. To do this, I have created a plugin, which contains a class “ULevelLoaderObject”
UCLASS(ClassGroup="Level Loading Plugin", meta = (BlueprintSpawnableComponent))
class ULevelLoaderObject : public UObject
{
GENERATED_UCLASS_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Level Loading Plugin", meta = (WorldContext = "WorldContextObject"))
static bool LoadLevelFromPak(UObject* WorldContextObject, const FString pakPath, UStaticMesh*& LoadedStaticMesh);
UFUNCTION(BlueprintCallable, Category = "Level Loading Plugin", meta = (WorldContext = "WorldContextObject"))
static bool AlternateLoadLevelFromPak(UObject* WorldContextObject, const FString pakPath, UStaticMesh*& LoadedStaticMesh);
This being static allows me to call it from my level blueprint and have a world context available to operate on GEngine/World.
So I have attempted two ways to do this. One is “LevelLoadFromPak” which is…
bool ULevelLoaderObject::LoadLevelFromPak(UObject* WorldContextObject, const FString pakPath, UStaticMesh*& LoadedStaticMesh)
{
UWorld* World = WorldContextObject->GetWorld();
bool bSuccessfulInitialization = false;
IPlatformFile* LocalPlatformFile = &FPlatformFileManager::Get().GetPlatformFile();
if (LocalPlatformFile != nullptr)
{
IPlatformFile* PakPlatformFile = FPlatformFileManager::Get().GetPlatformFile(TEXT("PakFile"));
if (PakPlatformFile != nullptr)
bSuccessfulInitialization = true;
}
if (bSuccessfulInitialization)
{
const TCHAR* cmdLine = TEXT("");
FPakPlatformFile* PakPlatform = new FPakPlatformFile();
IPlatformFile* InnerPlatform = LocalPlatformFile;
PakPlatform->Initialize(InnerPlatform, cmdLine);
FPlatformFileManager::Get().SetPlatformFile(*PakPlatform);
FString FilePath = pakPath;
UE_LOG(LevelPluginLog, Log, TEXT("Attempting to load pak: %s"), *FilePath);
FPakFile PakFile(*FilePath, false);
if (!PakFile.IsValid())
{
UE_LOG(LevelPluginLog, Error, TEXT("Invalid pak file: %s"), *FilePath);
return false;
}
FString MountPoint(TEXT("/Engine/"));
PakFile.SetMountPoint(*MountPoint);
const int32 PakOrder = 0;
if (!PakPlatform->Mount(*FilePath, PakOrder, *MountPoint))
{
UE_LOG(LevelPluginLog, Error, TEXT("Failed to mount pak file: %s"), *FilePath);
return false;
}
TArray<FString> AssetFileNames;
PakFile.FindFilesAtPath(AssetFileNames, *MountPoint);
for (const FString& AssetName : AssetFileNames)
{
UE_LOG(LevelPluginLog, Log, TEXT("Asset Found: %s"), *AssetName);
}
FString meshFilePath = MountPoint + TEXT("dronepackmesh.dronepackmesh");
FString meshFileContent;
FFileHelper::LoadFileToString(meshFileContent, *meshFilePath);
UE_LOG(LevelPluginLog, Log, TEXT("Path: %s, Content: %s"), *meshFilePath, *meshFileContent);
FStreamableManager StreamableManager;
FString assetName(meshFilePath);
FStringAssetReference AssetRef(assetName);
Cast<UStaticMesh>(StaticLoadObject(UStaticMesh::StaticClass(), nullptr, *assetName));
UObject* object = StreamableManager.SynchronousLoad(AssetRef);
return true;
}
return false;
}
I’m simply hard coding my values right now just to try to get anything functional. What happens in this scenario is output to my log
LevelPluginLog: Attempting to load pak: D:/Unreal/testpak.pak
LevelPluginLog: Asset Found: /Engine/dronepackmaterial.uasset
LevelPluginLog: Asset Found: /Engine/dronepackmesh.uasset
LevelPluginLog: Asset Found: /Engine/emptymap.umap
LevelPluginLog: Path: /Engine/dronepackmesh.dronepackmesh, Content:
LogLinker:Warning: Can’t find file ‘/Engine/dronepackmesh’
LogLinker:Warning: Can’t find file ‘/Engine/dronepackmesh’
LogUObjectGlobals:Warning: Failed to find object ‘StaticMesh /Engine/dronepackmesh.dronepackmesh’
LogLinker:Warning: Can’t find file ‘/Engine/dronepackmesh’
LogUObjectGlobals:Warning: Failed to find object ‘Object /Engine/dronepackmesh.dronepackmesh’
I’ve tried this as well with dronepackmesh.uasset which results in the same.
I also found references to people saying “OH don’t go through all that process, just use FCoreDelegates::OnMountPak.Execute instead!”. But no one really explains what to do after you have mounted this. The previous way, I have a PakFile that I can operate on to get a list of assets, but if I just OnMountPak, I cannot find anyway to actually get a list of assets in the loaded pak to get references to use.
My function “AlternateLoadLevelFromPak” is attempting to use OnMountPak:
bool ULevelLoaderObject::AlternateLoadLevelFromPak(UObject* WorldContextObject, const FString pakPath, UStaticMesh*& LoadedStaticMesh)
{
UWorld* World = WorldContextObject->GetWorld();
UE_LOG(LevelPluginLog, Log, TEXT("Game Content Dir: %s"), *FPaths::GameContentDir());
if (FCoreDelegates::OnMountPak.IsBound())
{
if (FCoreDelegates::OnMountPak.Execute(*pakPath, 0))
{
FStreamableManager AssetLoader;
FStringAssetReference AssetRef(TEXT("/Engine/dronepackmesh.dronepackmesh"));
LoadedStaticMesh = Cast<UStaticMesh>(AssetLoader.SynchronousLoad(AssetRef));
AStaticMeshActor* actor = World->SpawnActor<AStaticMeshActor>(FVector::ZeroVector, FRotator::ZeroRotator);
actor->GetStaticMeshComponent()->Mobility = EComponentMobility::Movable;
actor->GetStaticMeshComponent()->SetStaticMesh(LoadedStaticMesh);
UE_LOG(LevelPluginLog, Log, TEXT("Pak successfully mounted"));
}
else
UE_LOG(LevelPluginLog, Error, TEXT("Pak failed to mount"));
}
else
UE_LOG(LevelPluginLog, Error, TEXT("OnMountPak is not bound"));
return true;
}
This again results in:
LevelPluginLog: Game Content Dir: …/…/…/…/…/Projects/BasicProject/Content/
LogLinker:Warning: Can’t find file ‘/Engine/dronepackmesh’
LogLinker:Warning: Can’t find file ‘/Engine/dronepackmesh’
LogUObjectGlobals:Warning: Failed to find object ‘Object /Engine/dronepackmesh.dronepackmesh’
LogStreamableManager: Failed attempt to load /Engine/dronepackmesh.dronepackmesh
Apologies for the massive wall of text, but I am just simply stumped and cannot find myself able to piece together the fragments of knowledge scattered around. Most of the posts either end with no answer, or someone just saying “Oh I fixed it.” I’m trying to get a better grasp of how this all works and what exactly I am doing wrong. All I am trying to do is take a .pak file at runtime and load a map from it. I feel like this should be a non-issue with various games utilizing DLC mechanics, and I MUST be missing something super obvious. The idea for this is that we, as the developer, or even other users, will be able to just create a level utilizing their own assets, package it up, and deploy it for other users to load into their game to play with our existing GameMode/player blueprints. I can’t even get an asset loaded, let alone a map