187 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			187 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| 
								 | 
							
								---
							 | 
						|||
| 
								 | 
							
								title: UnrealEngine的命令行操作方式
							 | 
						|||
| 
								 | 
							
								date: 2023-04-27 11:15:14
							 | 
						|||
| 
								 | 
							
								excerpt: 
							 | 
						|||
| 
								 | 
							
								tags: CommandLet
							 | 
						|||
| 
								 | 
							
								rating: ⭐
							 | 
						|||
| 
								 | 
							
								---
							 | 
						|||
| 
								 | 
							
								# 前言
							 | 
						|||
| 
								 | 
							
								最近想实现使用UnrealEditor-Cmd进行资产处理并且渲染的功能,这里简单归纳一下。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								# 命令行启动方式
							 | 
						|||
| 
								 | 
							
								## CommandLet
							 | 
						|||
| 
								 | 
							
								继承UCommandLet,重写Run()相关逻辑写在里面即可。启动参数大致如下:
							 | 
						|||
| 
								 | 
							
								```bash
							 | 
						|||
| 
								 | 
							
								UnrealEditor-Cmd.exe ProjectPath -run=CommandletName
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								这里很推荐去看一下`UImportAssetsCommandlet`,里面实现了使用Json传参的方法。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								### 变相执行ConsoleCommand
							 | 
						|||
| 
								 | 
							
								创建一个CommandLet并且接收参数,并在最后下列代码即可。
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								GEditor->Exec(World, TEXT("MAP REBUILD ALLDIRTYFORLIGHTING"));
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								## Python
							 | 
						|||
| 
								 | 
							
								- 官方文档:https://docs.unrealengine.com/4.27/en-US/ProductionPipelines/ScriptingAndAutomation/Python/
							 | 
						|||
| 
								 | 
							
								```bash
							 | 
						|||
| 
								 | 
							
								UnrealEditor-Cmd.exe ProjectPath -ExecutePythonScript="c:\my_script.py"
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								还存在另一种方法,编辑器启动时环境最小,不包含UI或渲染。该方法执行起来非常快,但是加载脚本需要交互的关卡和其他种类资源时比较棘手。在命令行中添加以下参数:-`run=pythonscript -script=<script_file>`比如:
							 | 
						|||
| 
								 | 
							
								```bash 
							 | 
						|||
| 
								 | 
							
								UnrealEditor-Cmd.exe -run=pythonscript -script="c:\\my_script.py"
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								## AutomationTest
							 | 
						|||
| 
								 | 
							
								一个另类的思路就是使用自动测试工具。通过`IMPLEMENT_COMPLEX_AUTOMATION_TEST`实现一个自动测试类之后实现`RunTest`即可。启动参数:
							 | 
						|||
| 
								 | 
							
								```bash
							 | 
						|||
| 
								 | 
							
								UnrealEditor-Cmd.exe ProjectPath -AutomationTestName -Execcmds="Command-Line Arguments"
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								大致过程可以参考视频:https://www.youtube.com/watch?v=kJd5-jY46Gk
							 | 
						|||
| 
								 | 
							
								视频中的启动参数:
							 | 
						|||
| 
								 | 
							
								```python
							 | 
						|||
| 
								 | 
							
								import subprocess
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								engine = "C:/work/Epic/UE_5.0/Engine/Binaries/win64/UnrealEditor.exe"
							 | 
						|||
| 
								 | 
							
								project = "C:/Work/Prototypes/CompileBlueprintProj/CompileBlueprintProj.uproject"
							 | 
						|||
| 
								 | 
							
								log_location = "C:/Work/Prototypes/CompileBlueprintProj/blueprint_results.log"
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								cmd =[
							 | 
						|||
| 
								 | 
							
								engine,project,
							 | 
						|||
| 
								 | 
							
								f"-abslog={flog_location}",
							 | 
						|||
| 
								 | 
							
								"-editortest",
							 | 
						|||
| 
								 | 
							
								"-Execcmds= "Automation SetFilter Stress, Automation list,Automation RunTest Project.Blueprints.compile Blueprints\"","-testexit=\ "Automation Test Queue Empty\"",
							 | 
						|||
| 
								 | 
							
								cmd = " ".join(cmd)
							 | 
						|||
| 
								 | 
							
								subprocess.run(cmd)
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								引擎内的FCompileBlueprintsTest具体代码:
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								IMPLEMENT_COMPLEX_AUTOMATION_TEST(FCompileBlueprintsTest, "Project.Blueprints.Compile Blueprints", EAutomationTestFlags::EditorContext | EAutomationTestFlags::StressFilter)
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								/************************************************************************/  
							 | 
						|||
| 
								 | 
							
								/* FCompileBlueprintsTest                                              */  
							 | 
						|||
| 
								 | 
							
								/************************************************************************/  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								/** Requests a enumeration of all blueprints to be loaded */  
							 | 
						|||
| 
								 | 
							
								void FCompileBlueprintsTest::GetTests(TArray<FString>& OutBeautifiedNames, TArray <FString>& OutTestCommands) const  
							 | 
						|||
| 
								 | 
							
								{  
							 | 
						|||
| 
								 | 
							
								   FBlueprintAutomationTestUtilities::CollectTestsByClass(UBlueprint::StaticClass(), OutBeautifiedNames, OutTestCommands, /*bool bIgnoreLoaded =*/false);  
							 | 
						|||
| 
								 | 
							
								}  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
								bool FCompileBlueprintsTest::RunTest(const FString& Parameters)  
							 | 
						|||
| 
								 | 
							
								{  
							 | 
						|||
| 
								 | 
							
								   UE_LOG(LogBlueprintAutomationTests, Log, TEXT("Beginning compile test for %s"), *Parameters);  
							 | 
						|||
| 
								 | 
							
								   return FBlueprintAutomationTestUtilities::CompileBlueprint(Parameters);  
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								/**  
							 | 
						|||
| 
								 | 
							
								 * Simulates the user pressing the blueprint's compile button (will load the * blueprint first if it isn't already). ** @param  BlueprintAssetPath  The asset object path that you wish to compile. * @return False if we failed to load the blueprint, true otherwise 
							 | 
						|||
| 
								 | 
							
								 */
							 | 
						|||
| 
								 | 
							
								static bool CompileBlueprint(const FString& BlueprintAssetPath)  
							 | 
						|||
| 
								 | 
							
								{  
							 | 
						|||
| 
								 | 
							
									UBlueprint* BlueprintObj = Cast<UBlueprint>(StaticLoadObject(UBlueprint::StaticClass(), NULL, *BlueprintAssetPath));  
							 | 
						|||
| 
								 | 
							
									if (!BlueprintObj || !BlueprintObj->ParentClass)  
							 | 
						|||
| 
								 | 
							
									{      
							 | 
						|||
| 
								 | 
							
										UE_LOG(LogBlueprintAutomationTests, Error, TEXT("Failed to compile invalid blueprint, or blueprint parent no longer exists."));  
							 | 
						|||
| 
								 | 
							
										return false;  
							 | 
						|||
| 
								 | 
							
									}  
							 | 
						|||
| 
								 | 
							
									UPackage* const BlueprintPackage = BlueprintObj->GetOutermost();  
							 | 
						|||
| 
								 | 
							
									// compiling the blueprint will inherently dirty the package, but if there   
							 | 
						|||
| 
								 | 
							
									// weren't any changes to save before, there shouldn't be after  
							 | 
						|||
| 
								 | 
							
									bool const bStartedWithUnsavedChanges = (BlueprintPackage != nullptr) ? BlueprintPackage->IsDirty() : true;  
							 | 
						|||
| 
								 | 
							
									
							 | 
						|||
| 
								 | 
							
									FKismetEditorUtilities::CompileBlueprint(BlueprintObj, EBlueprintCompileOptions::SkipGarbageCollection);  
							 | 
						|||
| 
								 | 
							
									
							 | 
						|||
| 
								 | 
							
									if (BlueprintPackage != nullptr)  
							 | 
						|||
| 
								 | 
							
									{      
							 | 
						|||
| 
								 | 
							
									   BlueprintPackage->SetDirtyFlag(bStartedWithUnsavedChanges);  
							 | 
						|||
| 
								 | 
							
									}  
							 | 
						|||
| 
								 | 
							
									return true;  
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								# 编辑器内调用
							 | 
						|||
| 
								 | 
							
								## Python
							 | 
						|||
| 
								 | 
							
								- 官方文档:https://docs.unrealengine.com/4.27/en-US/ProductionPipelines/ScriptingAndAutomation/Python/
							 | 
						|||
| 
								 | 
							
								将OutputLog的ConsoleCommand的类型从CMD=>Python,之后输入Python命令即可。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								## CustomConsoleCommand
							 | 
						|||
| 
								 | 
							
								除了UFUNCTION中指定Exec之外(因为这个只能在部分类中实现并不通用)就是使用`IConsoleManager::Get().RegisterConsoleCommand()`,卸载函数为`IConsoleManager::Get().UnregisterConsoleObject()`,一般会在Module的StartupModule()/ShutdownModule()或者Subsystem的对应函数中进行注册/卸载。用法如下:
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								IConsoleManager::Get().RegisterConsoleCommand(  
							 | 
						|||
| 
								 | 
							
								TEXT("ConsoleCommandName"),  
							 | 
						|||
| 
								 | 
							
								TEXT("Useage Info"),  
							 | 
						|||
| 
								 | 
							
								FConsoleCommandDelegate::CreateStatic(&UYourClass::Function),  
							 | 
						|||
| 
								 | 
							
								ECVF_Default);
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								相关的FConsoleCommandDelegate委托都位于IConsoleManager.h,根据需求选择。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								# 其他
							 | 
						|||
| 
								 | 
							
								## CommandLet Server
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								int32 FEditorDomainSaveServer::Run()
							 | 
						|||
| 
								 | 
							
								{
							 | 
						|||
| 
								 | 
							
									if (!TryInitialize())
							 | 
						|||
| 
								 | 
							
									{
							 | 
						|||
| 
								 | 
							
										Shutdown();
							 | 
						|||
| 
								 | 
							
										return 1;
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									while (PollShouldRun())
							 | 
						|||
| 
								 | 
							
									{
							 | 
						|||
| 
								 | 
							
										bool bIsIdle = true;
							 | 
						|||
| 
								 | 
							
										TickPendingPackages(bIsIdle);
							 | 
						|||
| 
								 | 
							
										PollIncomingConnections(bIsIdle);
							 | 
						|||
| 
								 | 
							
										PollConnections(bIsIdle);
							 | 
						|||
| 
								 | 
							
										TickMaintenance(bIsIdle);
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									Shutdown();
							 | 
						|||
| 
								 | 
							
									return 0;
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								void FEditorDomainSaveServer::TickMaintenance(bool bIsIdle)  
							 | 
						|||
| 
								 | 
							
								{  
							 | 
						|||
| 
								 | 
							
									using namespace UE::EditorDomainSave;  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
									SetIdle(bIsIdle);  
							 | 
						|||
| 
								 | 
							
									double CurrentTime = FPlatformTime::Seconds();  
							 | 
						|||
| 
								 | 
							
									if (bIsIdle)  
							 | 
						|||
| 
								 | 
							
									{  
							 | 
						|||
| 
								 | 
							
										if (!HasExpectedConnections() && CurrentTime - IdleStartTime > Constants::ServerAbdicationCooldownSeconds)  
							 | 
						|||
| 
								 | 
							
										{  
							 | 
						|||
| 
								 | 
							
											if (TryAbdicate())  
							 | 
						|||
| 
								 | 
							
											{  
							 | 
						|||
| 
								 | 
							
												return;  
							 | 
						|||
| 
								 | 
							
											}  
							 | 
						|||
| 
								 | 
							
										}  
							 | 
						|||
| 
								 | 
							
									}  
							 | 
						|||
| 
								 | 
							
								  
							 | 
						|||
| 
								 | 
							
									double CollectGarbageCooldownSeconds = bIsIdle ?  
							 | 
						|||
| 
								 | 
							
									Constants::CollectGarbageIdleCooldownSeconds :  
							 | 
						|||
| 
								 | 
							
									Constants::CollectGarbageActiveCooldownSeconds;  
							 | 
						|||
| 
								 | 
							
									bool bCollectedGarbageAfterIdle = bIsIdle && LastGarbageTime >= IdleStartTime;  
							 | 
						|||
| 
								 | 
							
									if (!bCollectedGarbageAfterIdle && CurrentTime - LastGarbageTime > CollectGarbageCooldownSeconds)  
							 | 
						|||
| 
								 | 
							
									{  
							 | 
						|||
| 
								 | 
							
										CollectGarbage(RF_NoFlags);  
							 | 
						|||
| 
								 | 
							
										LastGarbageTime = FPlatformTime::Seconds();  
							 | 
						|||
| 
								 | 
							
									}  
							 | 
						|||
| 
								 | 
							
									  
							 | 
						|||
| 
								 | 
							
									if (bIsIdle)  
							 | 
						|||
| 
								 | 
							
									{  
							 | 
						|||
| 
								 | 
							
										FPlatformProcess::Sleep(Constants::ServerIdleSleepPeriodSeconds);  
							 | 
						|||
| 
								 | 
							
									}  
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								## 日志写入文件
							 | 
						|||
| 
								 | 
							
								```bash
							 | 
						|||
| 
								 | 
							
								f"-abslog={log_location}"
							 | 
						|||
| 
								 | 
							
								```
							 |