Calmer的文章

  • 首页
  • 文章归档
  • 关于页面

  • 搜索
体验游戏 笔记 推荐 工具链 工具使用 小游戏 插件 UI 软件 教程

UE4/5中编辑器和命令的扩展(新)

发表于 2023-09-11 | 分类于 游戏开发 | 0 | 阅读次数 3255

前言

使用UE4/5时,开发者可以利用众多命令和编辑器工具来快速完成任务和调试。在日常游戏开发中,经常会遇到一些常见问题。一旦某个问题解决的次数增多,自然而然地会形成各种工具来进行封装。

那这些命令和编辑的工具是怎么自定义的呢?
这里根据平时涉及到使用的工具方式归纳为了几种方式。

  • UE4/5工具蓝图(EditorUtilityWidget)
    最方便快捷的开发工具方式,直接使用蓝图连连看。
  • UE4/5编辑器菜单栏扩展(常用)
    直接在常用的几个窗口菜单栏中进行追加和扩展,是最常用的一种开发方式。
    • 主(关卡)窗口菜单扩展
    • 蓝图窗口菜单扩展
    • 右键菜单扩展
  • 编辑器控制台命令(IConsoleCommand)
    在编辑器中按下"~"键,然后输入自定义的命令字即可执行。这种方式可以在运行时进行快速的调试,也可以在非运行时使用。UE4/5本身也有很多检测命令都是通过这种方式实现的,例如stat unit等。
  • 系统命令行(xxxCommandlet)
    用于通过系统命令行进行一些批量的操作,可以不用打开Editor。
  • 其他(暂不说明)

项目和插件的创建

  1. 新建一个EditorToolFramework的UE45 C++项目,为其新建一个EditorToolSet的插件
    20220220112200.png
  2. 插件设置包含Content目录
    image.png

Note: 后续所有和工具和命令相关的代码和资源都放在此插件中


UE4/5工具蓝图(EditorUtilityWidget)

创建

  1. 在插件EditorToolSet的Content目录下右键进行创建
    e09029b4e2333ccee476c6fd76c4fb60379dadca98ecc56440275dd22bafef94.png
    逻辑写在蓝图的Graph中,和普通的蓝图的使用方法一模一样

扩展1:可以使用一个C++类继承于EditorUtilityWidget,再创建蓝图的时候选择父类为自己继承后的C++,这样就可以把部分逻辑写在C++中。
扩展2:可以专门封装一个蓝图静态库以供调用

使用

  1. 右键创建好的蓝图,Run Editor Utility Widget
    787750851934a576d93783cb80777a5d1cd6f8820a14e1cadd82b546171895a3.png
    使用一次后会出现在菜单栏
    ba2fa9cf969d4cfddf7100d4e76ed3cff106542e2191c602ae4e1190b1b09f65.png

思考:是否有办法,引擎刚进入就可以直接在菜单栏使用,而不用预先使用一次。
参考解决方案:监听引擎刚初始化的事件,然后扫描插件Content目录下相关的资源,如果是EditorUtility类型就添加到菜单栏。


UE4/5编辑器菜单栏扩展

创建

上述创建插件的时候已经根据模板默认创建过了
20220220112200.png

使用

创建后,重新编译后打开菜单栏上的Window选项,一般会追加在最下方
20220220112237.png
点击使用后就是一个Message弹窗
image.png

新增

根据模板示例代码,大致分为3个步骤

  1. 定义和注册命令
  2. 实现和映射命令
  3. 添加菜单按钮响应,按钮归类

在插件源码目录新增ToolBars目录

5a6910dedd21de703aaf373bc11155c60f7354dd4d3ace1c49397132ec29cc80.png

  • 两个子目录
    • MainMenu目录:用于主(关卡)窗口菜单栏扩展
    • BPMenu目录:用于蓝图菜单栏扩展
  • 三个类
    • 以Commands结尾的类:用于命令定义和注册
    • 以Controller结尾的类:用于实现命令的具体逻辑
    • 以MenuBar结尾的类:用于界面表现逻辑的实现和归类

定义和注册命令

以FEditorMainMenuCommands为例子,在Commands类中进行定义和注册。

#pragma once
#include "CoreMinimal.h"
#include "EditorToolSetStyle.h"

class EDITORTOOLSET_API FEditorMainMenuCommands : public TCommands<FEditorMainMenuCommands>
{
public:
	FEditorMainMenuCommands()
		: TCommands<FEditorMainMenuCommands>(
			TEXT("EditorMainMenu"), NSLOCTEXT("Contexts", "EditorToolSet", "EditorToolSet Plugin"), NAME_None,
			FEditorToolSetStyle::GetStyleSetName())
	{
	}
	
	virtual ~FEditorMainMenuCommands() override;
	//2.注册命令
	virtual void RegisterCommands() override;
public:
	//1.定义命令
	TSharedPtr<FUICommandInfo> MainMenuShowMessage;
	TSharedPtr<FUICommandInfo> MainMenuOpenWindow;
	TSharedPtr<FUICommandInfo> MainMenuChooseFolder;
};
#include "ToolBars/MainMenu/FEditorMainMenuCommands.h"
#define LOCTEXT_NAMESPACE "FEditorMainMenuCommands"
FEditorMainMenuCommands::~FEditorMainMenuCommands(){}

void FEditorMainMenuCommands::RegisterCommands()
{
	UI_COMMAND(MainMenuShowMessage, "MainMenuShowMessage", "MainMenuShowMessage Help", EUserInterfaceActionType::Button, FInputGesture());
	UI_COMMAND(MainMenuOpenWindow, "MainMenuOpenWindow", "MainMenuOpenWindow Help", EUserInterfaceActionType::Button, FInputGesture());
	UI_COMMAND(MainMenuChooseFolder, "MainMenuChooseFolder", "MainMenuChooseFolder Help", EUserInterfaceActionType::Button, FInputGesture());
}
#undef LOCTEXT_NAMESPACE

实现和映射命令

以FEditorMainMenuController为例,在Controller里实现和映射命令

#pragma once
#include "CoreMinimal.h"

class EDITORTOOLSET_API FEditorMainMenuController
{
public:
	FEditorMainMenuController();
	~FEditorMainMenuController();

	TSharedRef<FUICommandList> GetCommandList();
	//映射命令
	void BindCommands();
	//命令实现具体逻辑
	void MainMenuShowMessage();
	void MainMenuOpenWindow();
	void MainMenuChooseFolder();
	
protected:
	//命令列表
	TSharedRef<FUICommandList> CommandList;
};
#include "ToolBars/MainMenu/FEditorMainMenuController.h"
#include "EditorToolSetBPLibrary.h"
#include "ToolBars/MainMenu/FEditorMainMenuCommands.h"

FEditorMainMenuController::FEditorMainMenuController():CommandList(new FUICommandList)
{
	//在构造函数中进行命令上下文注册
	FEditorMainMenuCommands::Register();
}

FEditorMainMenuController::~FEditorMainMenuController()
{
	FEditorMainMenuCommands::Unregister();
}

//打开消息弹窗命令
void FEditorMainMenuController::MainMenuShowMessage()
{
	FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(TEXT("MainMenuShowMessage")));
}

//打开BPUtility命令
void FEditorMainMenuController::MainMenuOpenWindow()
{
	UEditorToolSetBPLibrary::OpenBPUtilityByPath(TEXT("EditorUtilityWidgetBlueprint'/EditorToolSet/BP_RootEditorUtility.BP_RootEditorUtility'"));
}

//打开文件夹选择弹窗命令
void FEditorMainMenuController::MainMenuChooseFolder()
{
	const FString Path = UEditorToolSetBPLibrary::ChooseFolderByExplorer();
	FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Path));
}

TSharedRef<FUICommandList> FEditorMainMenuController::GetCommandList()
{
	return CommandList;
}

//映射
void FEditorMainMenuController::BindCommands()
{
	const auto& Commands = FEditorMainMenuCommands::Get();
	CommandList->MapAction(Commands.MainMenuOpenWindow,
	                       FExecuteAction::CreateRaw(this, &FEditorMainMenuController::MainMenuOpenWindow));
	CommandList->MapAction(Commands.MainMenuShowMessage,
	                       FExecuteAction::CreateRaw(this, &FEditorMainMenuController::MainMenuShowMessage));
	CommandList->MapAction(Commands.MainMenuChooseFolder,
						   FExecuteAction::CreateRaw(this, &FEditorMainMenuController::MainMenuChooseFolder));
}

添加菜单按钮响应命令,按钮归类

  1. 主(关卡)窗口菜单扩展

以FEditorMainMenuBar为例,在其中添加一个下拉菜单和二级菜单选项
325b91f552b97ac97dda115a653d8631b3259e08ab9784967a5389d5ab41e903.png

#pragma once
#include "CoreMinimal.h"
#include "FEditorMainMenuController.h"

class EDITORTOOLSET_API FEditorMainMenuBar
{
public:
	FEditorMainMenuBar();
	virtual ~FEditorMainMenuBar() = default;
	virtual void Initialize();

	void BindController(FEditorMainMenuController* Controller);
public:
	FText ToolName;
	
protected:
	void BuildMenubar(FMenuBarBuilder& MenuBarBuilder);
	void FillPullDownMenu(FMenuBuilder& MenuBuilder);

private:
	FEditorMainMenuController* EditorMainMenuController;
};

#include "ToolBars/MainMenu/FEditorMainMenuBar.h"
#include "LevelEditor.h"
#include "ToolBars/MainMenu/FEditorMainMenuCommands.h"

FEditorMainMenuBar::FEditorMainMenuBar()
{
	ToolName = FText::FromString(TEXT("MainTools"));
}

void FEditorMainMenuBar::BindController(FEditorMainMenuController* Controller)
{
	EditorMainMenuController = Controller;
}

void FEditorMainMenuBar::Initialize()
{
	FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>(FName("LevelEditor"));
	TSharedRef<FExtender> MenuExtender = MakeShareable(new FExtender);
	MenuExtender->AddMenuBarExtension("Help", EExtensionHook::After, EditorMainMenuController->GetCommandList(), FMenuBarExtensionDelegate::CreateRaw(this, &FEditorMainMenuBar::BuildMenubar));
	LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);
}

void FEditorMainMenuBar::BuildMenubar(FMenuBarBuilder& MenuBarBuilder)
{
	//添加下拉菜单
	MenuBarBuilder.AddPullDownMenu(
		ToolName,
		FText::GetEmpty(),
		FNewMenuDelegate::CreateRaw(this, &FEditorMainMenuBar::FillPullDownMenu));
}

void FEditorMainMenuBar::FillPullDownMenu(FMenuBuilder& MenuBuilder)
{
	//二级菜单
	MenuBuilder.AddSubMenu(
		FText::FromString(TEXT("Common")),
		FText::GetEmpty(),
		FNewMenuDelegate::CreateLambda([](FMenuBuilder& MenuBuilder)
		{
			MenuBuilder.AddMenuEntry(FEditorMainMenuCommands::Get().MainMenuShowMessage);
			MenuBuilder.AddMenuEntry(FEditorMainMenuCommands::Get().MainMenuChooseFolder);
		}));
	
	//响应打开窗口命令
	MenuBuilder.AddMenuEntry(FEditorMainMenuCommands::Get().MainMenuOpenWindow);
	MenuBuilder.AddSeparator();
	MenuBuilder.BeginSection(FName("S"), FText::FromString("Others"));
	{
		MenuBuilder.AddEditableText(
			FText::FromString("EditableText"),
			FText::FromString("EditableText_Tips"),
			FSlateIcon(),
			FText::FromString("Hello Editor!!!")
		);
		MenuBuilder.AddWidget(SNew(SImage),FText::FromString("ImageWidget"));
	}
	MenuBuilder.EndSection();
}

上述核心代码在Initialize方法中获得LevelEditorModule后,添加Extender,并在创建的Extender中注册了一个委托BuildMenubar,这个方法中可以看到这个新增项为一个下拉菜单项。

  1. 蓝图窗口的扩展(具体代码见插件源码)

e949bec0a10b77fc6f416f99427d57507978bf370ff3d07c5ae14a41d9232e6c.png

7a9e4a21e131fa18972139ef7886a37e854bb730438798d63ba5080fbb3970a2.png

对比于在关卡编辑器的菜单中新增一个项,其实蓝图编辑器也是大同小异,这里额外书写了在蓝图编辑器的ToolBar上新增的方法。如下Initialize方法:

void FEditorBPMenuBar::Initialize()
{
	
	//蓝图编辑器菜单栏,BPMenuTools
	TSharedRef<FExtender> ToolbarExtender(new FExtender());
	const auto ExtensionDelegate = FMenuBarExtensionDelegate::CreateRaw(this, &FEditorBPMenuBar::BuildMenubar);
	ToolbarExtender->AddMenuBarExtension("Debug", EExtensionHook::After, EditorBPMenuController->GetCommandList(),
	                                     ExtensionDelegate);
	BlueprintEditorModule.GetMenuExtensibilityManager()->AddExtender(ToolbarExtender);

	//蓝图编辑器ToolBar
	FBlueprintEditorModule& BlueprintEditorModule = FModuleManager::LoadModuleChecked<FBlueprintEditorModule>(FName("Kismet"));
	auto& ExtenderDelegates = BlueprintEditorModule.GetMenuExtensibilityManager()->GetExtenderDelegates();
	ExtenderDelegates.Add(FAssetEditorExtender::CreateLambda(
		[&](const TSharedRef<FUICommandList>, const TArray<UObject*> ContextSensitiveObjects)
		{
			ContextObject = ContextSensitiveObjects.Num() < 1 ? nullptr : Cast<UBlueprint>(ContextSensitiveObjects[0]);
			TSharedRef<FExtender> ToolbarExtender(new FExtender());
			const auto ExtensionDelegate = FToolBarExtensionDelegate::CreateRaw(this, &FEditorBPMenuBar::BuildToolbar);
			ToolbarExtender->AddToolBarExtension("Debugging", EExtensionHook::After,
			                                     EditorBPMenuController->GetCommandList(), ExtensionDelegate);
			return ToolbarExtender;
		}));

}

这里类似也能用到关卡编辑器中

初始化

  1. 在插件StartupModule里构造
    a577484dfd22def1443ebe297e3af33c78f45ccd2163ab16fafe4d49d677a054.png
  2. 在引擎初始化回调里进行初始化
    10718d6d21d085f9c7f55d56cbdad7af506f6898c132e0276db327bccbc79509.png

编辑器控制台命令(IConsoleCommand)

创建

23dfa5b2285bd4a4ce91e01857bf214a204a829d57b77967d7922cd7b018bab9.png

  • FOpenConsoleCommand:用于模块注册和管理
  • FEditorCommandBase: 命令基类
  • Commands目录下,每一个类都是一条Console命令

示例

以FShowWindowCommand为例

#pragma once
#include "CoreMinimal.h"
#include "EditorConsoleCommands/FEditorCommandBase.h"

class EDITORTOOLSET_API FShowWindowCommand :public FEditorCommandBase
{
public:
	FShowWindowCommand();
	virtual ~FShowWindowCommand() override;

	virtual void Execute(const TArray<FString>& Args) override;
};
#include "EditorConsoleCommands/Commands/FShowWindowCommand.h"
#include "Interfaces/IMainFrameModule.h"

FShowWindowCommand::FShowWindowCommand()
{
	CommandName = TEXT("ShowWindow");
	HelpContent = TEXT("ShowWindowHelp");
}

FShowWindowCommand::~FShowWindowCommand()
{}

void FShowWindowCommand::Execute(const TArray<FString>& Args)
{
	FEditorCommandBase::Execute(Args);

	FString WindowTitle;
	for (int i = 0; i < Args.Num(); i++)
	{
		WindowTitle.Append(Args[i]);
	}
	const TSharedRef<SWindow> CookbookWindow = SNew(SWindow)
		.Title(FText::FromString(WindowTitle))
		.ClientSize(FVector2D(800, 400))
		.SupportsMaximize(false)
		.SupportsMinimize(false);
	const IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));
	if (MainFrameModule.GetParentWindow().IsValid())
	{
		FSlateApplication::Get().AddWindowAsNativeChild(CookbookWindow, MainFrameModule.GetParentWindow().ToSharedRef());
	}
	else
	{
		FSlateApplication::Get().AddWindow(CookbookWindow);
	}
}

其继承与命令基类FEditorCommandBase,实现虚函数Execute即可。运行命令ShowWindow:打开一个以参数拼接成名字的窗口

初始化

  • OpenConsoleCommand在模块Startup时初始化
    afd53cffcc1ba78f55b0bdf5eaa82c789e3b7a4b28aa84723c08e95a38b6af53.png
  • 单独命令初始,以FShowWindowCommand为例
    a4ab8aa3d7fe5cfb6e8cb8a01af98a530af1a026f5e6e504c3d1d2e48aaf6412.png

使用

c3c3710508aaba3a65169e371aec26cfaff76a932ea30c99bbf8f4bd9139a990.png

acedee1cce1185cbd0f4ed03d7fb1d17dc874b6c359ab99fe8834d7340fd0035.png

根据上述定义的命令,在编辑器的命令行,输入ShowWindow命令,就可以很方便的执行了。

可以很方便的进行代码的调试


系统命令行(Commandlet)

如何创建

先在插件目录新增一个Commandlets目录
a2805d365f4f1a14e7bcb707417132b819ea4b1077932c93c38e9659f696ed17.png

然后创建一个Commandlet结尾的.h和.cpp。并继承于UCommandlet类
例如上图的ExportFileCommandlet

#pragma once
#include "CoreMinimal.h"
#include "Commandlets/Commandlet.h"
#include "ExportFileCommandlet.generated.h"

UCLASS()
class EDITORTOOLSET_API UExportFileCommandlet : public UCommandlet
{
	GENERATED_BODY()
public:
	virtual int32 Main(const FString& Params) override;

private:
	void SaveFile(const FString& ContentStr) const;
};

#include "Commandlets/ExportFileCommandlet.h"
#include "Interfaces/IPluginManager.h"
#include "Misc/FileHelper.h"

int32 UExportFileCommandlet::Main(const FString& Params)
{
	TArray<FString> Tokens;
	TArray<FString> Switches;
	TMap<FString, FString> ParamsMap;
	ParseCommandLine(*Params, Tokens, Switches, ParamsMap);
	const FString Content = ParamsMap[TEXT("content")];
	SaveFile(Content);
	return 0;
}

void UExportFileCommandlet::SaveFile(const FString& ContentStr) const
{
	const FString Directory = IPluginManager::Get().FindPlugin(TEXT("EditorToolSet"))->GetBaseDir() / TEXT("Saved");
	IFileManager& FileManager = IFileManager::Get();
	if (!FileManager.DirectoryExists(*Directory))
	{
		FileManager.MakeDirectory(*Directory);
	}
	const FString FileName(TEXT("Test"));
	const FString FilePath = FString::Printf(TEXT("%s/%s.txt"), *Directory, *FileName);
	if (!FFileHelper::SaveStringToFile(ContentStr, *FilePath))
	{
		UE_LOG(LogTemp, Log, TEXT("SaveFailed"));
	}
}

这样就定义了一个系统命令行,也就是ExportFile

使用

直接打开window的cmd,然后找到UE的编辑器和我们所创建的项目CommandProj的uproject文件,添加上相关的参数就可以执行所定义commandlet,这里还是以ExportFile为例.
9c8238b3cce163cfaa58c371036c4dd111abb20df928c71d6b7634c997004698.png

./UnrealEditor.exe E:\UEProjects\EditorToolFramework\EditorToolFramework.uproject --skipcompile -run=ExportFile -content=HelloEditor

上述命令执行完毕后会在相应的Plugins目录下,创建一个Saved目录,然后创建Test.txt,并写入HelloEditor
71d8a2a1edc9b8f7412150a8cceb88b7e2ea68a19e5a15db6fea354c901452b6.png

可以在非运行游戏执行游戏某些操作,用于封装bat进行批量操作


其他

  • 常用c++库方法导出为dll给其他语言使用,如:python来实现
  • 其他软件

扩展

控制台命令,直接调用接口去执行(运行时)

void UKismetSystemLibrary::ExecuteConsoleCommand(const UObject* WorldContextObject, const FString& Command, APlayerController* Player)
{
	// First, try routing through the primary player
	UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::ReturnNull);
	APlayerController* TargetPC = Player || !World ? Player : World->GetFirstPlayerController();
	if (TargetPC)
	{
		TargetPC->ConsoleCommand(Command, true);
	}
	else
	{
		GEngine->Exec(World, *Command);
	}
}

常用EditorToolSetBPLibaray

#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "EditorToolSetBPLibrary.generated.h"

UCLASS()
class EDITORTOOLSET_API UEditorToolSetBPLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()
public:

	UFUNCTION(BlueprintCallable, Category="EditorToolSet")
	static void OpenBPUtilityByPath(const FString& Path);

	UFUNCTION(BlueprintCallable, Category="EditorToolSet")
	static FString ChooseFolderByExplorer();

	UFUNCTION(BlueprintCallable, Category="EditorToolSet")
	static FString GetToolSetPluginBasePath();
};
#include "EditorToolSetBPLibrary.h"
#include "DesktopPlatformModule.h"
#include "EditorUtilitySubsystem.h"
#include "EditorUtilityWidgetBlueprint.h"
#include "Interfaces/IPluginManager.h"

void UEditorToolSetBPLibrary::OpenBPUtilityByPath(const FString& Path)
{
	if (UEditorUtilityWidgetBlueprint* EditorWidget = LoadObject<UEditorUtilityWidgetBlueprint>(nullptr, *Path))
	{
		UEditorUtilitySubsystem* EditorUtilitySubsystem = GEditor->GetEditorSubsystem<UEditorUtilitySubsystem>();
		EditorUtilitySubsystem->SpawnAndRegisterTab(EditorWidget);
	}
	else
	{
		FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Cannot load the EditorUtilityWidgetBlueprint!"));
	}
}

FString UEditorToolSetBPLibrary::ChooseFolderByExplorer()
{
	IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
	FString OutputDirectory;
	const FString DefaultPath = FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir());
	UE_LOG(LogTemp, Log, TEXT("DefaultPath: %s"), *DefaultPath);
	bool successfullySelected = false;

	if (DesktopPlatform)
	{
		const void* ParentWindowWindowHandle = FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr);

		successfullySelected = DesktopPlatform->OpenDirectoryDialog(
			ParentWindowWindowHandle,
			FString(TEXT("选择目录")),
			DefaultPath,
			OutputDirectory
		);
	}
	else
	{
		UE_LOG(LogTemp, Error, TEXT("Cannot find the DesktopPlatform!"));
	}

	if (!successfullySelected)
	{
		return FString(TEXT(""));
	}

	FPaths::MakePathRelativeTo(OutputDirectory, *DefaultPath);
	OutputDirectory = TEXT("/Game/") + OutputDirectory;

	return OutputDirectory;
}

FString UEditorToolSetBPLibrary::GetToolSetPluginBasePath()
{
	return IPluginManager::Get().FindPlugin(TEXT("EditorToolSet"))->GetBaseDir();
}

总结

使用UE4/5开发必然涉及到各种编辑器的开发,并且UE4/5引擎源码是开放的,因此几乎可以实现你能想到的所有功能。在此对工具命令的开发方式和框架进行了总结,后续可以在此基础上进行快速开发。


框架源码

Github: https://github.com/CalmLoader/UE4-5-EditorToolFramework


参考链接

  1. UE工具蓝图(EditorUtilityWidget)
    https://docs.unrealengine.com/4.27/en-US/InteractiveExperiences/UMG/UserGuide/EditorUtilityWidgets/
  2. UE编辑器菜单栏扩展(MenuToolBar)
    参考unlua蓝图的按钮功能源码
    https://zhuanlan.zhihu.com/p/331677329
    https://zhuanlan.zhihu.com/p/432072854
    https://zhuanlan.zhihu.com/p/302480201
  3. 编辑器控制台命令(IConsoleCommand)
    stat unlua
    stat unit
    https://www.bilibili.com/read/cv5545647
  4. 系统命令行(Commandlet)
    https://zhuanlan.zhihu.com/p/377903983
    https://docs.unrealengine.com/4.27/zh-CN/ProductionPipelines/CommandLineArguments/#概述
  5. 其他
    https://segmentfault.com/a/1190000018367388
  • 本文作者: Calmer
  • 本文链接: https://mytechplayer.com/archives/ue45中编辑器和命令的扩展新
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
# 工具链
UE5集成ChatGPT和Stable Diffusion(SD)
延迟和预测回滚(GAS)
  • 文章目录
  • 站点概览
Calmer

Calmer

88 日志
7 分类
10 标签
RSS
Creative Commons
0%
© 2020 — 2025 Calmer
由 Halo 强力驱动
蜀ICP备20010026号-1川公网安备51019002006543
Copyright © 2020-2025 Calmer的文章