Unreal5 研究笔记 蓝图自定义节点

环境:Unreal5.6 语言:C++

面向:Unreal初学者

总起

蓝图在Unreal中非常重要,基本是个必学概念,它分别承担了以下的功能:

  1. 游戏逻辑编写(类似Unity中的C#脚本),拥有定义或调用事件、函数、变量等能力;
  2. 可复用的预制件(类似Unity中的Prefab);
  3. 动画蓝图(类似于Unity中的Animator);
  4. 关卡蓝图,这个其实没什么特别的,就是每个关卡独有的全局事件列表;
  5. 控件蓝图,将UI视觉元素与交互逻辑整合在一起。

蓝图的前身是Kismet,Unreal3时期功能较为有限,逻辑大部分还使用Unreal Script来编写。到了Unreal4,蓝图正式推出,整合了前两者的能力。

因此如果程序员想脱离蓝图,纯用C++开发会痛苦无比。

当然最近我有了解到现在AI编程针对蓝图的适配有限(因为现在的AI编程本质就是做文本分析嘛),所以有人尝试让AI直接生成C++代码,似乎结果还不错,不过C++这个编译使用起来还是比较麻烦的......(想念C# 1秒钟)

自定义蓝图节点

说回到当前的一个需求:想要定义一个两个Int值相加的节点。

首先Unreal中本身自带了一个 Add 节点,并且支持任意类型的相加:

其次是使用 UFUNCTION,直接在C++中定义一个函数,这应该最常用的方式了:

cpp 复制代码
// .h文件
UFUNCTION(BlueprintPure, Category = "MyTestActor|Math", meta = (DisplayName = "Actor Static Add"))
static int32 StaticMyAddInts(int32 a, int32 b);

// .cpp文件
int32 AMyTestActor::StaticMyAddInts(int32 a, int32 b)
{
	return a + b;
}

这里需要注意一些细节:

  1. BlueprintPure,无执行引脚,需要有返回值(区别于BlueprintCallable有执行引脚);
  2. Category描述是右键添加节点时的分类;
  3. DisplayName就是显示的名字。

结果:

最后是继承于 K2Node,完全自定义节点,灵活度最高,也是最麻烦的一种。好处是可以实现动态引脚、完全自定义外观、自定义编译时展开等。

cpp 复制代码
// .h文件
class MYEDITOR_API UMyBPNode_AddTwoInts : public UK2Node
{
	GENERATED_BODY()

public:
    // 纯节点(无执行引脚)
    virtual bool IsNodePure() const override { return true; }

    // 创建引脚
    virtual void AllocateDefaultPins() override;

    // 节点外观
    virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
    virtual FText GetTooltipText() const override;

    // 菜单栏的注册
    virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override;
    virtual FText GetMenuCategory() const override;

    // 编译时展开
    virtual void ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override;
};

// .cpp文件(样式类的就不做多过多赘述,主要是展开节点)
void UMyBPNode_AddTwoInts::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
    Super::ExpandNode(CompilerContext, SourceGraph);

    // 1. 找到目标函数:UKismetMathLibrary::Add_IntInt
    UFunction* AddFunction = UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UKismetMathLibrary, Add_IntInt));
    if (!AddFunction)
    {
        CompilerContext.MessageLog.Error(*LOCTEXT("AddFunctionNotFound", "Add_IntInt function not found.").ToString(), this);
        return;
    }

    // 2. 在编译图中生成一个中间函数调用节点
    UK2Node_CallFunction* CallAddNode = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph);
    CallAddNode->SetFromFunction(AddFunction);
    CallAddNode->AllocateDefaultPins();

    // 3. 获取原节点和中间节点的引脚
    UEdGraphPin* InputAPin = FindPinChecked(TEXT("A"));
    UEdGraphPin* InputBPin = FindPinChecked(TEXT("B"));
    UEdGraphPin* ResultPin = FindPinChecked(TEXT("Result"));

    UEdGraphPin* CallInputAPin = CallAddNode->FindPinChecked(TEXT("A"));
    UEdGraphPin* CallInputBPin = CallAddNode->FindPinChecked(TEXT("B"));
    UEdGraphPin* CallReturnPin = CallAddNode->GetReturnValuePin();

    // 4. 将原节点的引脚链接转移到中间节点
    CompilerContext.MovePinLinksToIntermediate(*InputAPin, *CallInputAPin);
    CompilerContext.MovePinLinksToIntermediate(*InputBPin, *CallInputBPin);
    CompilerContext.MovePinLinksToIntermediate(*ResultPin, *CallReturnPin);

    // 5. 断开原节点的所有链接(编译器将忽略它)
    BreakAllNodeLinks();
}

这种方式定义的节点类主要存在于编辑器和编译期,给Kismet进行编译使用,上述两数相加的节点便不会打到游戏中,取而代之的是编译时的展开(ExpandNode),将自定义的函数放到UK2Node_CallFunction 的一个中间节点中,然后将现有的连接转移到这种中间节点上。

结果:

如果想要做到像Unreal中添加任意类型的端口,则需要使用到wildcard端口。

定义的这个类应该需要放在UncookedOnly 模块中。其他的模块类型:

  1. Runtime,游戏的核心逻辑,在任意情况下都会加载,一般Unreal工程默认创建的模块就是Runtime的;
  2. Editor,纯编辑器专用的,与UncookedOnly的区别在于这个Cook的过程:Cook过程会将Editor数据转换成运行时数据,此时如果需要这个过程的选择UncookedOnly,不需要选Editor即可。

想要创建模块的话:创建模块文件夹,在下面创建[模块名].Build.cs、[模块名].cpp、[模块名].h文件,然后在 [项目名].uproject、[项目名].Target.cs文件中注册。(具体操作就不赘述了,另外如果想要做成Plugin则不需要处理[项目名].Target.cs,当然创建Plugin引擎内可以通过Edit -> Plugins -> Add创建)

相关推荐
问心无愧05132 小时前
ctf show web入门37
笔记
羊群智妍3 小时前
2026生成式AI搜索优化:GEO监测工具全解析
笔记
koo3644 小时前
周报5.10
笔记
kdxiaojie5 小时前
U-Boot分析【学习笔记】(3)
linux·笔记·学习
智者知已应修善业6 小时前
【51单片机模拟生日蜡烛】2023-10-10
c++·经验分享·笔记·算法·51单片机
智者知已应修善业6 小时前
【51单片机如何让LED灯从一亮到八,再从八亮到一】2023-10-13
c++·经验分享·笔记·算法·51单片机
qeen876 小时前
【数据结构】二叉树相关经典函数C语言实现
c语言·数据结构·c++·笔记·学习·算法·二叉树
Alice-YUE6 小时前
深入解析 JS 事件循环:浏览器与 Node.js 的差异全解析
前端·javascript·笔记·学习
fanged6 小时前
高通平台IMU的Bringup(TODO)
笔记
远离UE47 小时前
Vulkan学习笔记
笔记·学习