跳至主要內容

P0.插件开发

Mr.Si大约 8 分钟unreal

闲聊

头像
Baba!我记不住这么多委托函数的写法怎么办?
头像
为什么不用蓝图直接开发呢!
头像
总要提升挑战一下自己吧!有没有办法呢?
头像
也许你可以写一个可视化界面辅助我们写这个宏函数。比如这种:
头像
对哦!可是我们该选择什么框架来做呢?
头像
方法有很多啊:
1.WPF/QT写一个独立程序
2.JS写成Web端
3.UE用蓝图编辑器控件
4.UE中用基于Slate的编辑器插件
头像
WPF/QT太重了!很多UE代码库没法直接用啊。JS呢虽然也不能直接链接UE,但随时随地可用!是个备选方案! 至于蓝图编辑器控件这个总觉得像在写UMG还是高级点的Slate来做吧!
头像
安排!

新建插件开始

头像
点击插件,Baba用的是UE5.2
头像
新建编辑器插件
头像
注意名称不要和现有插件重名。

等待IED编译后点击窗口-就能看到你创建的插件了

头像
此时回到编辑器就能看到我们的插件了

UI扩展点

头像
你的编辑器怎么有这些绿色的字啊?有什么用呢?
头像
这个呢叫UI扩展点,你可以在编辑器偏好设置中找到。见名知意就是方便咱确认UI插件启动按钮该放哪里用的。

插件基本结构

头像
下一步之前我们复习一下基本的插件目录结构
YourPluginName/
|-- Config/--配置文件
|   |-- DefaultGame.ini
|-- Content/--目录文件
|   |-- YourPluginName/
|       |-- Materials/
|       |-- Textures/
|-- Resources/--图标
|-- Source/--源文件
|   |-- YourPluginName/
|       |-- YourPluginName.Build.cs
|       |-- Private/
|           |-- YourPluginName.cpp
|       |-- Public/
|           |-- YourPluginName.h
|-- YourPluginName.uplugin

.Buid.cs

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

using UnrealBuildTool;

public class ExorcistTool : ModuleRules
{
	public ExorcistTool(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
		
		PublicIncludePaths.AddRange(
			new string[] {
				// ... add public include paths required here ...
			}
			);
				
		
		PrivateIncludePaths.AddRange(
			new string[] {
				// ... add other private include paths required here ...
			}
			);
			
		
		PublicDependencyModuleNames.AddRange(
			new string[]
			{
				"Core",
				// ... add other public dependencies that you statically link with here ...
			}
			);
			
		
		PrivateDependencyModuleNames.AddRange(
			new string[]
			{
				"Projects",
				"InputCore",
				"EditorFramework",
				"UnrealEd",
				"ToolMenus",
				"CoreUObject",
				"Engine",
				"Slate",
				"SlateCore",
				// ... add private dependencies that you statically link with here ...	
			}
			);
		
		
		DynamicallyLoadedModuleNames.AddRange(
			new string[]
			{
				// ... add any modules that your module loads dynamically here ...
			}
			);
	}
}

头像
没啥可说的,就是一些必备基础模块。

.uplugin解析

头像
基操先看.uplugin
ExorcistTool.uplugin
{
	"FileVersion": 3,
	"Version": 1,
	"VersionName": "1.0",
	"FriendlyName": "ExorcistTool",
	"Description": "a tool for code help",
	"Category": "Other",
	"CreatedBy": "sigaohe",
	"CreatedByURL": "",
	"DocsURL": "",
	"MarketplaceURL": "",
	"SupportURL": "",
	"CanContainContent": false,
	"IsBetaVersion": false,
	"IsExperimentalVersion": false,
	"Installed": false,
	"Modules": [
		{
			"Name": "ExorcistTool",
			"Type": "Editor",
			"LoadingPhase": "Default"
		}
	]
}
头像
除了Modules所有都是单一键值对,重点关注一下这个Modules。
  1. Modules是个数组,也就是说插件中可以有多个模块。
  2. Name:没啥好说的模块名
  3. Type:模块类型,翻看源码open in new window可以确定是个枚举值。
EHostType::Type
namespace EHostType
{
    enum Type
    {
        Runtime,
        RuntimeNoCommandlet,
        RuntimeAndProgram,
        CookedOnly,
        UncookedOnly,
        Developer,
        DeveloperTool,
        Editor,
        EditorNoCommandlet,
        EditorAndProgram,
        Program,
        ServerOnly,
        ClientOnly,
        ClientOnlyNoCommandlet,
        Max,
    }
}

4.LoadingPhase:模块加载阶段 依然是个枚举源码APIopen in new window

ELoadingPhase::Type
namespace ELoadingPhase
{
    enum Type
    {
        EarliestPossible,
        PostConfigInit,
        PostSplashScreen,
        PreEarlyLoadingScreen,
        PreLoadingScreen,
        PreDefault,
        Default,
        PostDefault,
        PostEngineInit,
        None,
        Max,
    }
}

插件目录案例

头像
编辑器运行和Runtime内容分开。主要是为了防止用户错误的打包仅编辑器的内容。
	"Modules": [
		{
			"Name": "DataSystem",
			"Type": "Runtime",
			"LoadingPhase": "Default"
		},
		{
			"Name": "DataSystemEditor",
			"Type": "Editor",
			"LoadingPhase": "PostEngineInit"
		}
	]

插件热编译

头像
插件和源码一样,也可以热编译

插件初探

头像
现在来看看这个全新的插件目录结构
头像
系统为我们生成了三个头/源文件
ExorcistTool.h
#pragma once

#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"

class FToolBarBuilder;
class FMenuBuilder;

class FExorcistToolModule : public IModuleInterface
{
public:

    /** IModuleInterface 实现 */
    virtual void StartupModule() override;               // 模块启动时的回调
    virtual void ShutdownModule() override;              // 模块关闭时的回调
    
    /** 该函数将绑定到 Command(默认情况下将打开插件窗口) */
    void PluginButtonClicked();
    
private:

    void RegisterMenus();                                // 注册菜单回调

    TSharedRef<class SDockTab> OnSpawnPluginTab(const class FSpawnTabArgs& SpawnTabArgs);  // 生成插件标签页回调

private:
    TSharedPtr<class FUICommandList> PluginCommands;     // 插件命令列表
};
  1. ExorcistTool

    • 作为插件的主要模块接口,实现了模块的启动和关闭功能。
    • 在这个文件中,通常定义了模块的生命周期函数以及模块的重要成员,比如用于创建菜单、工具栏等。
  2. ExorcistToolCommands

  • 定义了插件的命令集合,这些命令可以被UE编辑器或者蓝图调用。
  • 这里的 OpenPluginWindow 是一个打开插件窗口的命令。
  1. ExorcistToolStyle
  • 是插件的样式管理类,用于管理插件的Slate样式。Slate是UE的UI框架,
  • 该文件定义了样式的初始化、关闭以及重新加载等功能。

ExorcistTool解析

头像
为什么没有构造函数?
class FExorcistToolModule : public IModuleInterface
头像
在C++中,类不是必须要有构造函数的,尤其是对于实现接口的类。接口是一种抽象规范,而不是具体的实例。这里`IModuleInterface` 是一个标记接口,用于标识该类为模块接口, 而不需要实例化。因此,它并不需要一个显式的构造函数。

/** IModuleInterface implementation */
	virtual void StartupModule() override;
	virtual void ShutdownModule() override;
头像
模块的启动时函数和关闭时函数。

/** This function will be bound to Command (by default it will bring up plugin window) */
	void PluginButtonClicked();
头像
很显然,按钮触发时的回调函数。

void RegisterMenus();
头像
注册菜单回调,具体点就是告诉编辑器这个按钮要显示在哪个位置,可以用之前的 UI扩展点定位参考。
TSharedRef<class SDockTab> OnSpawnPluginTab(const class FSpawnTabArgs& SpawnTabArgs);

OnSpawnPluginTab是一个函数,返回类型是 TSharedRef<class SDockTab>,接受一个 const FSpawnTabArgs& 类型的参数 SpawnTabArgs

头像
具体地说,这个函数声明是用于生成插件的SDockTab对象,用于创建插件模块的Dockable Tab。

参考文档

UE扩展开发,插件的基础结构解析open in new window