UE4 C++(21):Cook单个资源

03/14/2020

Cook 单个资源

HotPatcher有一个函数叫做OnCookPlatform函数,需要资源名字和烘培平台就可以烘培资源了。简单把这个函数提取出来,我这边使用的是控制台来烘焙鼠标选中Content面板中的资源。

控制台烘培选中资源

控制台命令

传递烘培资源的平台,然后创建烘培的路径和开始烘培,最后完成烘培结果的显示。

  • GetCookedAssetDir():获取烘培路径
  • CookPackage():烘培函数,即生成文件
  • CreateSaveFileNotify():完成烘培结果显示
// Cheat command to load all assets of a given type
static FAutoConsoleCommand CookChoosedAssets(
	TEXT("CookChoosedAssets"),
	TEXT("Cook Choosed Assets in the Content Broweser"),
	FConsoleCommandWithArgsDelegate::CreateStatic(
		[](const TArray<FString>& Platforms)
		{
			//Cook A single asset

			//1. Get Asset Path + Name  on the ContentBrowser
			FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
			TArray<FAssetData> AssetsData;
			ContentBrowserModule.Get().GetSelectedAssets(AssetsData);

			if (AssetsData.Num() == 0)
			{
				UE_LOG(LogTemp, Warning, TEXT("Plase Choose assets"));
				return;
			}

			TArray<UPackage*> AssetsPackage;
			for (const auto& AssetData : AssetsData)
			{
				AssetsPackage.AddUnique(AssetData.GetPackage());
			}

			for (auto CurrentPlatform : Platforms)
			{
				//Create folder and Cook assets
				FString CookedDir = FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("Cooked")));
				for (const auto& AssetPacakge : AssetsPackage)
				{
					FString CookedSavePath = GetCookAssetsSaveDir(CookedDir, AssetPacakge, CurrentPlatform);

					//Start Cook Assets
					bool bCookStatus = CookPackage(AssetPacakge, Platforms, CookedDir);

					auto Msg = FText::Format(
						LOCTEXT("CookAssetsNotify", "Cook Platform {1} for {0} {2}!"),
						UKismetTextLibrary::Conv_StringToText(AssetPacakge->FileName.ToString()),
						UKismetTextLibrary::Conv_StringToText(CurrentPlatform),
						bCookStatus ? UKismetTextLibrary::Conv_StringToText(TEXT("Successfuly")) : UKismetTextLibrary::Conv_StringToText(TEXT("Faild"))
					);
					SNotificationItem::ECompletionState CookStatus = bCookStatus ? SNotificationItem::ECompletionState::CS_Success : SNotificationItem::ECompletionState::CS_Fail;
					CreateSaveFileNotify(Msg, CookedSavePath, CookStatus);
					UE_LOG(LogTemp, Warning, TEXT("Cook Single Assets, %s"), bCookStatus?TEXT("True"):TEXT("False"));

				}
			}
			UE_LOG(LogTemp, Warning, TEXT("Finish Cook Single Assets"));
		}), ECVF_Default);
  1. 学会使用ContentBrowser模块获取选中的资源并保存路径
  2. 认识FAssetData和UPackage类
  3. UPackage 和EditorEngine 都有一个Save函数
// Copyright Epic Games, Inc. All Rights Reserved.
#include "HAL/IConsoleManager.h"
#include "ContentBrowserModule.h"
#include "IContentBrowserSingleton.h"
#include "UObject/Package.h"
#include "IPlatformFileSandboxWrapper.h"
#include "Interfaces/IPluginManager.h"
#include "Misc/SecureHash.h"
#include "Kismet/KismetTextLibrary.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Interfaces/ITargetPlatformManagerModule.h"
#include "Interfaces/ITargetPlatform.h"

#include "IPlatformFileSandboxWrapper.h"
#include "Misc/SecureHash.h"

#include "Editor.h"

#define LOCTEXT_NAMESPACE "EditorCommand"

static FString ConvertToFullSandboxPath(const FString& FileName, bool bForWrite)
{
	FString ProjectContentAbsir = FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir());
	if (FileName.StartsWith(ProjectContentAbsir))
	{
		FString GameFileName = FileName;
		GameFileName.RemoveFromStart(ProjectContentAbsir);
		return FPaths::Combine(FApp::GetProjectName(), TEXT("Content"), GameFileName);
	}
	if (FileName.StartsWith(FPaths::EngineContentDir()))
	{
		FString EngineFileName = FileName;
		EngineFileName.RemoveFromStart(FPaths::EngineContentDir());
		return FPaths::Combine(TEXT("Engine/Content"), EngineFileName);;
	}
	TArray<TSharedRef<IPlugin> > PluginsToRemap = IPluginManager::Get().GetEnabledPlugins();
	// Ideally this would be in the Sandbox File but it can't access the project or plugin
	if (PluginsToRemap.Num() > 0)
	{
		// Handle remapping of plugins
		for (TSharedRef<IPlugin> Plugin : PluginsToRemap)
		{
			FString PluginContentDir;
			if (FPaths::IsRelative(FileName))
				PluginContentDir = Plugin->GetContentDir();
			else
				PluginContentDir = FPaths::ConvertRelativePathToFull(Plugin->GetContentDir());
			// UE_LOG(LogHotPatcherEditorHelper,Log,TEXT("Plugin Content:%s"),*PluginContentDir);
			if (FileName.StartsWith(PluginContentDir))
			{
				FString SearchFor;
				SearchFor /= Plugin->GetName() / TEXT("Content");
				int32 FoundAt = FileName.Find(SearchFor, ESearchCase::IgnoreCase, ESearchDir::FromEnd);
				check(FoundAt != -1);
				// Strip off everything but <PluginName/Content/<remaing path to file>
				FString SnippedOffPath = FileName.RightChop(FoundAt);

				FString LoadingFrom;
				switch (Plugin->GetLoadedFrom())
				{
				case EPluginLoadedFrom::Engine:
				{
					LoadingFrom = TEXT("Engine/Plugins");
					break;
				}
				case EPluginLoadedFrom::Project:
				{
					LoadingFrom = FPaths::Combine(FApp::GetProjectName(), TEXT("Plugins"));
					break;
				}
				}

				return FPaths::Combine(LoadingFrom, SnippedOffPath);
			}
		}
	}

	return TEXT("");
}

static FString GetCookAssetsSaveDir(const FString& BaseDir, UPackage* Package, const FString& Platform)
{
	FString Filename;
	FString PackageFilename;
	FString StandardFilename;
	FName StandardFileFName = NAME_None;
	//Content folder
	if (FPackageName::DoesPackageExist(Package->FileName.ToString(), NULL, &Filename, false))
	{
		StandardFilename = PackageFilename = FPaths::ConvertRelativePathToFull(Filename);

		FPaths::MakeStandardFilename(StandardFilename);
		StandardFileFName = FName(*StandardFilename);
	}
	//MakeSure Engine or Project or Plugin Contents
	FString SandboxFilename = ConvertToFullSandboxPath(*StandardFilename, true);
	// UE_LOG(LogHotPatcherEditorHelper,Log,TEXT("Filename:%s,PackageFileName:%s,StandardFileName:%s"),*Filename,*PackageFilename,*StandardFilename);

	FString CookDir = FPaths::Combine(BaseDir, Platform, SandboxFilename);

	return 	CookDir;
}


static bool CookPackage(UPackage* Package, const TArray<FString>& Platforms, const FString& SavePath)
{
	bool bSuccessed = false;
	const bool bSaveConcurrent = FParse::Param(FCommandLine::Get(), TEXT("ConcurrentSave"));
	bool bUnversioned = false;
	uint32 SaveFlags = SAVE_KeepGUID | SAVE_Async | SAVE_ComputeHash | (bUnversioned ? SAVE_Unversioned : 0);
	EObjectFlags CookedFlags = RF_Public;
	if (Cast<UWorld>(Package))
	{
		CookedFlags = RF_NoFlags;
	}
	if (bSaveConcurrent)
	{
		SaveFlags |= SAVE_Concurrent;
	}
	ITargetPlatformManagerModule& TPM = GetTargetPlatformManagerRef();
	const TArray<ITargetPlatform*>& TargetPlatforms = TPM.GetTargetPlatforms();
	TArray<ITargetPlatform*> CookPlatforms;
	for (ITargetPlatform* TargetPlatform : TargetPlatforms)
	{
		if (Platforms.Contains(TargetPlatform->PlatformName()))
		{
			CookPlatforms.AddUnique(TargetPlatform);
		}
	}
	if (Package->FileName.IsNone())
		return bSuccessed;
	for (auto& Platform : CookPlatforms)
	{
		struct FFilterEditorOnlyFlag
		{
			FFilterEditorOnlyFlag(UPackage* InPackage, ITargetPlatform* InPlatform)
			{
				Package = InPackage;
				Platform = InPlatform;
				if (!Platform->HasEditorOnlyData())
				{
					Package->SetPackageFlags(PKG_FilterEditorOnly);
				}
				else
				{
					Package->ClearPackageFlags(PKG_FilterEditorOnly);
				}
			}
			~FFilterEditorOnlyFlag()
			{
				if (!Platform->HasEditorOnlyData())
				{
					Package->ClearPackageFlags(PKG_FilterEditorOnly);
				}
			}
			UPackage* Package;
			ITargetPlatform* Platform;
		};

		FFilterEditorOnlyFlag SetPackageEditorOnlyFlag(Package, Platform);

		FString CookedSavePath = GetCookAssetsSaveDir(SavePath, Package, Platform->PlatformName());
		// delete old cooked assets
		if (FPaths::FileExists(CookedSavePath))
		{
			IFileManager::Get().Delete(*CookedSavePath);
		}
		// UE_LOG(LogHotPatcherEditorHelper,Log,TEXT("Cook Assets:%s"),*Package->GetName());
		Package->FullyLoad();
		TArray<UObject*> ExportMap;
		GetObjectsWithOuter(Package, ExportMap);
		for (const auto& ExportObj : ExportMap)
		{
			ExportObj->BeginCacheForCookedPlatformData(Platform);
		}


		GIsCookerLoadingPackage = true;
		//UE_LOG(LogTemp, Warning, TEXT("Cook Assets:%s save to %s"), *Package->GetName(), *CookedSavePath);
		FSavePackageResultStruct Result = GEditor->Save(Package, nullptr, CookedFlags, *CookedSavePath,
			GError, nullptr, false, false, SaveFlags, Platform,
			FDateTime::MinValue(), false, /*DiffMap*/ nullptr);
		GIsCookerLoadingPackage = false;
		bSuccessed = Result == ESavePackageResult::Success;
	}
	return bSuccessed;
}




void CreateSaveFileNotify(const FText& InMsg, const FString& InSavedFile, SNotificationItem::ECompletionState NotifyType)
{
	auto Message = InMsg;
	FNotificationInfo Info(Message);
	Info.bFireAndForget = true;
	Info.ExpireDuration = 5.0f;
	Info.bUseSuccessFailIcons = false;
	Info.bUseLargeFont = false;

	const FString HyperLinkText = InSavedFile;
	Info.Hyperlink = FSimpleDelegate::CreateLambda(
		[](FString SourceFilePath)
		{
			FPlatformProcess::ExploreFolder(*SourceFilePath);
		},
		HyperLinkText
			);
	Info.HyperlinkText = FText::FromString(HyperLinkText);

	FSlateNotificationManager::Get().AddNotification(Info)->SetCompletionState(NotifyType);
}




注意事项

如何引用库文件和改写Build.cs

// Copyright Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;

public class TestPython : ModuleRules
{
	public TestPython(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
	
		PublicDependencyModuleNames.AddRange(new string[] { 
			"Core", 
			"CoreUObject", 
			"Engine", 
			"InputCore",
			"ContentBrowser",
			"SandboxFile",
			"TargetPlatform"

		});

		PrivateDependencyModuleNames.AddRange(
			new string[]
			{
				"Projects",
				"UnrealEd",
				"Engine",
				"Slate",
				"SlateCore",
				"RenderCore"
				// ... add private dependencies that you statically link with here ...	
			}
			);

	}
}

补充

烘培整个(UE4Editor.exe)

查看MainFrameActions.cpp::CookContent函数

D:\UE4\UE_4.25\Engine\Binaries\Win64\UE4Editor-Cmd.exe D:\unrealEngin4WorkStation\TestPython\TestPython.uproject -run=Cook  -TargetPlatform=WindowsNoEditor

烘培地图

UE4Editor.exe <GameName or uproject> -run=cook -targetplatform=<Plat1>+<Plat2> [-cookonthefly] [-iterate] [-map=<Map1>+<Map2>]
or
UE4Editor-Cmd.exe <GameName> -run=cook -targetplatform=<Plat1>+<Plat2> [-cookonthefly] [-iterate] [-map=<Map1>+<Map2>] 

UE4官网

简化单个资源烘培


bool SaveToCookAsset(UPackage * InPackage, const FString & InSavePath)
{
	ITargetPlatformManagerModule& TPM = GetTargetPlatformManagerRef();
	const TArray<ITargetPlatform*>& TargetPlatforms = TPM.GetTargetPlatforms();
	ITargetPlatform* Target = nullptr;
	for (ITargetPlatform *TargetPlatform : TargetPlatforms)
	{
		if (TargetPlatform->PlatformName() == "WindowsNoEditor")
		{
			Target = TargetPlatform;
			break;
		}
	}
	InPackage->SetPackageFlags(PKG_FilterEditorOnly);
	uint32 saveFlags = SAVE_KeepGUID | SAVE_Async | SAVE_Unversioned;
	FSavePackageResultStruct Result = GEditor->Save(InPackage, nullptr, RF_Public, *InSavePath, GError, NULL, false, false, saveFlags, Target, FDateTime::MinValue(), false);
	return Result == ESavePackageResult::Success;

简单使用GEditor->Save()函数来烘培资源

总结

这次烘培仅仅只能在Editor打开的时候才能使用。

参考资料

  1. 简化烘培
  2. UE4官网烘培

版权声明:本文为weixin_44200074原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。