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创建)

相关推荐
210Brian2 小时前
嘉立创EDA硬件设计与实战学习笔记(二):元件符号与封装的绘制
大数据·笔记·学习
oi..3 小时前
python Get/Post请求练习
开发语言·经验分享·笔记·python·程序人生·安全·网络安全
努力学习的小廉3 小时前
redis学习笔记(九)—— Redis 持久化
redis·笔记·学习
busideyang4 小时前
函数指针类型定义笔记
c语言·笔记·stm32·单片机·算法·嵌入式
叶子野格4 小时前
Notepad++编写html文件使用D3绘图:数据可视化
笔记·学习·信息可视化·开源·notepad++
卖报的大地主6 小时前
Learn Claude Code Agent 开发 | 5、按需技能加载:领域知识不用全塞系统提示
人工智能·笔记
亓才孓7 小时前
【SQLAlchemy】个人快速学习笔记
笔记·学习
210Brian7 小时前
嘉立创EDA硬件设计与实战学习笔记(三):51单片机核心板原理图设计
笔记·学习·51单片机
chushiyunen7 小时前
检索模型cross-encoder笔记
笔记