开篇
其实UE4中有很多Commandlet这些Commandlet 为我们提供了不同的功能有自动导入资源的ImportAssetsCommandlet;烘焙的CookCommandlet等等。了解和熟悉这些Commandlet可以帮助我们实现自动化或者半自动化流程。
今天我们就来讲讲ImportAssetsCommandlet 这个Commandlet 可以实现静默的资源导入。想具体了解这个命令都干了些什么事情就需要去到引擎源代码中的Editor\UnrealEd\Private\Commandlets\ImportAssetsCommandlet.cpp 看源码了。 在这里我教大家如何使用或者是如何配置一个自动化导入资源的命令以及简单的剖析这个命令的源码 ,看看里面都做了些什么。
正文
如何使用
使用这个命令的方式很简单打开cmd命令行找到 引擎的Binaries\Win64文件夹 下有一个叫UE4Ediotr-cmd.exe 的可执行文件 将这个exe 拖进cmd窗口 然后 按照下面的格式输入命令:
项目的.uproject -run=ImportAssets -importSettings="导入设置的json文件路径"
完整示例:
D:\SoftWare\UE_4.25\Engine\Binaries\Win64\UE4Editor-Cmd.exe "D:\WorkSpace\Unreal Project\ACT\ACT.uproject" -run=ImportAssets -importSettings="D:/Export/Importsetting.json"
首先我们来讲下 第一个是引擎编辑器的cmd 第二个参数是你要导入的项目 第三个是需要导入的资源的json 文件
下面我们来看看这个导入设置的json 文件
{
"ImportGroups": [
{
"FileNames": [ "C:/Users/VR/Desktop/Export/43291A04.fbx" ],
"bReplaceExisting": "true",
"DestinationPath": "/Game/mesh/UserModel/638281/43291A04/",
"FactoryName": "FbxFactory",
"ImportSettings": {}
},
{
"FileNames": [ "C:/Users/VR/Desktop/Temp/43291A04/3d66Mat-855555-maps-1_diffuse.jpg", "C:/Users/VR/Desktop/Temp/43291A04/3d66Mat-855555-maps-1_normal.jpg", "C:/Users/VR/Desktop/Temp/43291A04/43291A04_001_AO.png", "C:/Users/VR/Desktop/Temp/43291A04/43291A04_002_AO.png", "C:/Users/VR/Desktop/Temp/43291A04/43291A04_003_AO.png", "C:/Users/VR/Desktop/Temp/43291A04/43291A04_006_AO.png"],
"bReplaceExisting": "true",
"DestinationPath": "/Game/mesh/UserModel/638281/43291A04/Texture/",
"bSkipReadOnly": "true",
"ImportSettings": {}
}
]
}
我们来解析一下 ImportGroups 里面是 代表的是每组导入的资源 可以分组导入
例如我第一组到的可能是fbx文件 然后指定使用fbx的工厂类去处理这批资源自动导入 其中还有
bReplaceExisting 参数指定是否替换现有资源;
FileNames 组里面都是些需要导入的资源的路径 ;
DestinationPath 需要将这组资源导入到什么位置
bSkipReadOnly 是否跳过自读的文件 ;
ImportSettings 里面没放参数 但是可以通过json去指定一些导入的设置 下面放一组fbx 的可指定参数参照; ------当然除了这种Json的方式指定导入时的设置我们还可以打开我们的项目将我们需要导入的资源不同类型的先导入一遍然后按照自己的设置去导入 然后会发现我们的项目save\Config\Windows\EditorPerProjectUserSettings.ini下的配置文件会帮我们记录这份导入设置 看下图
"ImportSettings": {
"AnimSequenceImportData":{},
"SkeletalMeshImportData":{},
"TextureImportData":{},
"StaticMeshImportData":{}
"bCombineMeshes":0,
"bAutoGenerateCollision":1,
"bRemoveDegenerates":1
}
上图中都是记录的一些导入设置 ImportAssets 命令执行时如果发现 ImportSettings 里面是空的就会去读项目的设置
代码的简单分析
由于代码过多这里没有办法对所有代码进行注解,只能对部分我认为比较重要的代码进行注解
bool UImportAssetsCommandlet::ImportAndSave(const TArray<UAutomatedAssetImportData*>& AssetImportList)
{
bool bImportAndSaveSucceeded = true; //初始化自动导入和自动保存
FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools"); //加载AssetTools模块 这个模块内包含了很多对资源进行操作的功能 具体可以看看AssetTools模块的源代码
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
for(const UAutomatedAssetImportData* ImportData : AssetImportList) //枚举每组导入资源
{
UE_LOG(LogAutomatedImport, Log, TEXT("Importing group %s"), *ImportData->GetDisplayName() );
UFactory* Factory = ImportData->Factory; //从这组导入资源中获取指定的工厂类 我们上面示例Json中导入 FBX就有指定
const TSharedPtr<FJsonObject>* ImportSettingsJsonObject = nullptr;
if(ImportData->ImportGroupJsonData.IsValid()) //判断这组导入资源的Json是否有效
{
ImportData->ImportGroupJsonData->TryGetObjectField(TEXT("ImportSettings"), ImportSettingsJsonObject); //尝试获取导入设置Json
}
if(Factory != nullptr && ImportSettingsJsonObject) //如果拿到了有效的Json 并且知道了改用什么工厂类去处理
{
IImportSettingsParser* ImportSettings = Factory->GetImportSettingsParser(); //拿到这个工程类对导入设置的解析器 因为每种资源的导入都有专门的工厂类去处理
if(ImportSettings)
{
ImportSettings->ParseFromJson(ImportSettingsJsonObject->ToSharedRef()); //解析导入设置
}
}
else if(Factory == nullptr && ImportSettingsJsonObject)
{
UE_LOG(LogAutomatedImport, Warning, TEXT("A vaild factory name must be specfied in order to specify settings"));
}
// Load a level if specified
bImportAndSaveSucceeded = LoadLevel(ImportData->LevelToLoad); //尝试去加载指定的关卡 无指定则 会自动忽略 具体可以看里面的代码实现
// Clear dirty packages that were created as a result of loading the level. We do not want to save these
ClearDirtyPackages(); //资源操作 ---- 清理所有资源身上的需要保存的标记
TArray<UObject*> ImportedAssets = AssetToolsModule.Get().ImportAssetsAutomated(ImportData); //根据解析好的数据以及设置自动进行资源呢导入 返回一组以及导入的资源
if(ImportedAssets.Num() > 0 && bImportAndSaveSucceeded)
{
TArray<UPackage*> DirtyPackages; //创建包
TArray<FSourceControlStateRef> PackageStates;
FEditorFileUtils::GetDirtyContentPackages(DirtyPackages); //将内容浏览器中刚刚自动导入的资源并标记为需要保存的添加到这个包的数组内
FEditorFileUtils::GetDirtyWorldPackages(DirtyPackages);//这个则是世界中的 同上
bool bUseSourceControl = bHasSourceControl && SourceControlProvider.IsAvailable(); //这个是版本管理的 git 或者 svn 等
if(bUseSourceControl)
{
SourceControlProvider.GetState(DirtyPackages, PackageStates, EStateCacheUsage::ForceUpdate);
}
for(int32 PackageIndex = 0; PackageIndex < DirtyPackages.Num(); ++PackageIndex)
{
UPackage* PackageToSave = DirtyPackages[PackageIndex];
FString PackageFilename = SourceControlHelpers::PackageFilename(PackageToSave);
bool bShouldAttemptToSave = false;
bool bShouldAttemptToAdd = false;
if(bUseSourceControl) //下面都是版本管理的内容
{
FSourceControlStateRef PackageSCState = PackageStates[PackageIndex];
bool bPackageCanBeCheckedOut = false;
if(PackageSCState->IsCheckedOutOther())
{
// Cannot checkout, file is already checked out
UE_LOG(LogAutomatedImport, Error, TEXT("%s is already checked out by someone else, can not submit!"), *PackageFilename);
bImportAndSaveSucceeded = false;
}
else if(!PackageSCState->IsCurrent())
{
// Cannot checkout, file is not at head revision
UE_LOG(LogAutomatedImport, Error, TEXT("%s is not at the head revision and cannot be checked out"), *PackageFilename);
bImportAndSaveSucceeded = false;
}
else if(PackageSCState->CanCheckout())
{
const bool bWasCheckedOut = SourceControlHelpers::CheckOutOrAddFile(PackageFilename);
bShouldAttemptToSave = bWasCheckedOut;
if(!bWasCheckedOut)
{
UE_LOG(LogAutomatedImport, Error, TEXT("%s could not be checked out"), *PackageFilename);
bImportAndSaveSucceeded = false;
}
}
else
{
// package was not checked out by another user and is at the current head revision and could not be checked out
// this means it should be added after save because it doesn't exist
bShouldAttemptToSave = true;
bShouldAttemptToAdd = true;
}
}
else
{
bool bIsReadOnly = IFileManager::Get().IsReadOnly(*PackageFilename); //判断是否只读
if(bIsReadOnly && ImportData->bSkipReadOnly) //判断是否跳过只读
{
bShouldAttemptToSave = false;
if(bIsReadOnly)
{
UE_LOG(LogAutomatedImport, Error, TEXT("%s is read only and -skipreadonly was specified. Will not save"), *PackageFilename);
bImportAndSaveSucceeded = false;
}
}
else if(bIsReadOnly)
{
bShouldAttemptToSave = FPlatformFileManager::Get().GetPlatformFile().SetReadOnly(*PackageFilename, false); //设置为不是只读
if(!bShouldAttemptToSave)
{
UE_LOG(LogAutomatedImport, Error, TEXT("%s is read only and could not be made writable. Will not save"), *PackageFilename);
bImportAndSaveSucceeded = false;
}
}
else
{
bShouldAttemptToSave = true;
}
}
if(bShouldAttemptToSave)
{
SavePackage(PackageToSave, PackageFilename); //将包保存下来
if(bShouldAttemptToAdd)
{
const bool bWasAdded = SourceControlHelpers::MarkFileForAdd(PackageFilename);
if(!bWasAdded)
{
UE_LOG(LogAutomatedImport, Error, TEXT("%s could not be added to source control"), *PackageFilename);
bImportAndSaveSucceeded = false;
}
}
}
}
}
else
{
bImportAndSaveSucceeded = false;
UE_LOG(LogAutomatedImport, Error, TEXT("Failed to import all assets in group %s"), *ImportData->GetDisplayName());
}
}
return bImportAndSaveSucceeded;
}
上面这部分是资源的的保存了,资源的导入过程中还做了很多事情。不过那都是在一个个工厂类中的事情了,还有前期的Commandlet是如何在为指定工厂类的情况下 知道改用那种工厂类去解析的 。 具体这些代码都可以翻看UE4的源码找到答案
最后
还是那句话如果文章中有一些错误内容请帮我指出,我会十分感谢!!!
文章虽然写的不是很好,但希望转载时注明出处。