并不适合阅读的个人文档。
UE4 4.9版本 实现 DLC 重新挂载来解决UE4加载pak包的BUG
当工程中添加了3个以上的pak包,例如2016_10_20_1.pak、2016_10_20_2.pak、2016_10_20_3.pak,这三个包里面都存在相同的资源(同样路径下,同样名字)…/a.ussat,错误的现象是:UE4编辑器将该资源加载到资源注册表中,读取该资源并不是版本号最新的哪一个,即2016_10_20_3.pak中a.usset读取的,该资源读取是混乱的(经过测试当目录下面只有2个pak包时是对的,3个以上就是出问题了)。
据了解4.17版本这个bug依然存在…
出错原因分析:
先上引擎代码
IPlatformFilePak.cpp
bool FPakPlatformFile::Initialize(IPlatformFile* Inner, const TCHAR* CmdLine) 截取部分代码:
for (int32 PakFileIndex = 0; PakFileIndex < FoundPakFiles.Num(); PakFileIndex++)
{
const FString& PakFilename = FoundPakFiles[PakFileIndex];
bool bLoadPak = true;
if (PaksToLoad.Num() && !PaksToLoad.Contains(FPaths::GetBaseFilename(PakFilename)))
{
bLoadPak = false;
}
if (bLoadPak)
{
// hardcode default load ordering of game main pak -> game content -> engine content -> saved dir
// would be better to make this config but not even the config system is initialized here so we can't do that
uint32 PakOrder = 0;
if (PakFilename.StartsWith(FString::Printf(TEXT("%sPaks/%s-"), *FPaths::GameContentDir(), FApp::GetGameName())))
{
PakOrder = 4;
}
else if (PakFilename.StartsWith(FPaths::GameContentDir()))
{
PakOrder = 3;
}
else if (PakFilename.StartsWith(FPaths::EngineContentDir()))
{
PakOrder = 2;
}
else if (PakFilename.StartsWith(FPaths::GameSavedDir()))
{
PakOrder = 1;
}
Mount(*PakFilename, PakOrder);
}
}
IPlatformFilePak.cpp
bool FPakPlatformFile::Mount(const TCHAR* InPakFilename, uint32 PakOrder, const TCHAR* InPath /= NULL/)
bool FPakPlatformFile::Mount(const TCHAR* InPakFilename, uint32 PakOrder, const TCHAR* InPath /*= NULL*/)
{
bool bSuccess = false;
TSharedPtr<IFileHandle> PakHandle = MakeShareable(LowerLevel->OpenRead(InPakFilename));
if (PakHandle.IsValid())
{
FPakFile* Pak = new FPakFile(LowerLevel, InPakFilename, bSigned);
if (Pak->IsValid())
{
if (InPath != NULL)
{
Pak->SetMountPoint(InPath);
}
FString PakFilename = InPakFilename;
if ( PakFilename.EndsWith(TEXT("_P.pak")) )
{
PakOrder += 100;
}
{
// Add new pak file
FScopeLock ScopedLock(&PakListCritical);
FPakListEntry Entry;
Entry.ReadOrder = PakOrder;
Entry.PakFile = Pak;
PakFiles.Add(Entry);
PakFiles.StableSort();
}
bSuccess = true;
}
else
{
UE_LOG(LogPakFile, Warning, TEXT("Failed to mount pak \"%s\", pak is invalid."), InPakFilename);
}
}
else
{
UE_LOG(LogPakFile, Warning, TEXT("Pak \"%s\" does not exist!"), InPakFilename);
}
return bSuccess;
}
分析:
引擎官方规定pak的默认加载顺序: Game/Content/Paks的文件夹 - >Game/Content - >Engine/Content - >Game/Save,定义了个PakOrder来指定不同的加载顺序。所以问题来了,如果多个pak包放在同一个级别的目录下如:都放在Game/Content/Paks的文件夹下,这几个pak的PakOrder都是4。再看Mount代码:在Mount的时候PakOrder都是一样的,Entry.ReadOrder = PakOrder; 这样问题就出现了,导致当加载到资源注册表的时候(详情请看 AssetRegistry.cpp void FAssetRegistry::Serialize(FArchive& Ar))如果读取到了相同的资源 a.ussset,他们的ReadOrder是一样的,所以出现了混乱。
解决方案
本文在不改变引擎代码的前提下修改这个问题。即重新挂载pak,读入到资源注册表中,定义了个最大索引数PakMaxOrder来取代PakOrder,设了个很大的值(感觉不太好,但管用 –!),当然需要在游戏启动的时候GameInstance中加载如下。
LoadPakResourse = NewObject<ULoadPakResourse>(this, TEXT("LoadPakResourse"));
LoadPakResourse->LoadPakFile();
.h
#pragma once
#include "../PakFile/Public/IPlatformFilePak.h"
#include "LoadPakResourse.generated.h"
UCLASS()
class ULoadPakResourse : public UObject
{
GENERATED_BODY()
void GetPaksUnderGameContentFolder(TArray<FString>& OutPakFolders);
void FindAllPakFiles(IPlatformFile* LowLevelFile, const TArray<FString>& PakFolders, TArray<FString>& OutPakFiles);
void FindPakFilesInDirectory(IPlatformFile* LowLevelFile, const TCHAR* Directory, TArray<FString>& OutPakFiles);
bool Mount(const TCHAR* InPakFilename, uint32 PakOrder, const TCHAR* InPath /*= NULL*/);
//自己添加了个最大索引数
int32 PakMaxOrder = 100000000;
TArray<FString> AllMountFileList;
public:
ULoadPakResourse();
~ULoadPakResourse();
void LoadPakFile();
TArray<FString> GetAllMountFile();
};
.cpp
#include "VRLogicPluginPrivatePCH.h"
#include "LoadPakResourse.h"
#include "Kismet/GameplayStatics.h"
#include "AssetRegistryModule.h"
#include "IAssetRegistry.h"
#include "GenericPlatformChunkInstall.h"
ULoadPakResourse::ULoadPakResourse()
{
}
ULoadPakResourse::~ULoadPakResourse()
{
}
TArray<FString> ULoadPakResourse::GetAllMountFile()
{
return AllMountFileList;
}
void ULoadPakResourse::LoadPakFile()
{
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
FPakPlatformFile* PakPlatformFile = new FPakPlatformFile();
PakPlatformFile->Initialize(&PlatformFile, TEXT(""));
TArray<FString> PakFolders;
GetPaksUnderGameContentFolder(PakFolders);
TArray<FString> FoundPakFiles;
FindAllPakFiles(&PlatformFile, PakFolders, FoundPakFiles);
//排序降序
FoundPakFiles.Sort(TGreater<FString>());
// Mount pak 包
for (int32 PakFileIndex = 0; PakFileIndex < FoundPakFiles.Num(); PakFileIndex++)
{
//Pak文件路径
const FString PakFilename = FoundPakFiles[PakFileIndex];
if (FPaths::FileExists(PakFilename))
{
FPakFile PakFile(&PlatformFile, *PakFilename, false);
if (PakPlatformFile->Mount(*PakFilename, PakMaxOrder--, NULL))
{
TArray<FString> FileList;
PakFile.FindFilesAtPath(FileList, *PakFile.GetMountPoint(), true, false, true);
AllMountFileList.Append(FileList);
UE_LOG(LogClass, Error, TEXT("Mount Success"));
}
else
{
UE_LOG(LogClass, Error, TEXT("Mount Failed"));
}
}
}
}
void ULoadPakResourse::GetPaksUnderGameContentFolder(TArray<FString>& OutPakFolders)
{
FString GameContentPath = FPaths::GameContentDir();
GameContentPath = FPaths::ConvertRelativePathToFull(GameContentPath + TEXT("ResourcePackage"));
OutPakFolders.Add(GameContentPath);
}
void ULoadPakResourse::FindAllPakFiles(IPlatformFile* LowLevelFile, const TArray<FString>& PakFolders, TArray<FString>& OutPakFiles)
{
// Find pak files from the specified directories.
for (int32 FolderIndex = 0; FolderIndex < PakFolders.Num(); ++FolderIndex)
{
FindPakFilesInDirectory(LowLevelFile, *PakFolders[FolderIndex], OutPakFiles);
}
}
void ULoadPakResourse::FindPakFilesInDirectory(IPlatformFile* LowLevelFile, const TCHAR* Directory, TArray<FString>& OutPakFiles)
{
// Helper class to find all pak files.
class FPakSearchVisitor : public IPlatformFile::FDirectoryVisitor
{
TArray<FString>& FoundPakFiles;
IPlatformChunkInstall* ChunkInstall;
public:
FPakSearchVisitor(TArray<FString>& InFoundPakFiles, IPlatformChunkInstall* InChunkInstall)
: FoundPakFiles(InFoundPakFiles)
, ChunkInstall(InChunkInstall)
{}
virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory)
{
if (bIsDirectory == false)
{
FString Filename(FilenameOrDirectory);
if (FPaths::GetExtension(Filename) == TEXT("pak"))
{
// if a platform supports chunk style installs, make sure that the chunk a pak file resides in is actually fully installed before accepting pak files from it
if (ChunkInstall)
{
FString ChunkIdentifier(TEXT("pakchunk"));
FString BaseFilename = FPaths::GetBaseFilename(Filename);
if (BaseFilename.StartsWith(ChunkIdentifier))
{
int32 DelimiterIndex = 0;
int32 StartOfChunkIndex = ChunkIdentifier.Len();
BaseFilename.FindChar(TEXT('-'), DelimiterIndex);
FString ChunkNumberString = BaseFilename.Mid(StartOfChunkIndex, DelimiterIndex - StartOfChunkIndex);
int32 ChunkNumber = 0;
TTypeFromString<int32>::FromString(ChunkNumber, *ChunkNumberString);
if (ChunkInstall->GetChunkLocation(ChunkNumber) == EChunkLocation::NotAvailable)
{
return true;
}
}
}
FoundPakFiles.Add(Filename);
}
}
return true;
}
};
// Find all pak files.
FPakSearchVisitor Visitor(OutPakFiles, FPlatformMisc::GetPlatformChunkInstall());
LowLevelFile->IterateDirectoryRecursively(Directory, Visitor);
}
版权声明:本文为博主原创文章,未经博主允许不得转载。