Skip to content

fix(unreal): TS 函数库静态方法生成为静态 UFunction,并修正函数库蓝图的 BlueprintType - #2354

Open
ZhiruiLi wants to merge 2 commits into
Tencent:masterfrom
ZhiruiLi:fix/unreal-static-function-library-flags
Open

fix(unreal): TS 函数库静态方法生成为静态 UFunction,并修正函数库蓝图的 BlueprintType#2354
ZhiruiLi wants to merge 2 commits into
Tencent:masterfrom
ZhiruiLi:fix/unreal-static-function-library-flags

Conversation

@ZhiruiLi

@ZhiruiLi ZhiruiLi commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

问题现象

TS 类继承 UE.BlueprintFunctionLibrary 并用 static 方法定义函数库函数时:

  1. 生成的蓝图中该方法是实例函数——调用节点多出 Target 引脚,与 C++/蓝图定义的函数库静态函数"无需 Target、直接调用"不一致;即使连上 Target 强行调用,执行的也是空函数体,TS 实现永远不会被调用。
  2. 修正上述问题后,调用该函数的蓝图开始编译失败:Could not find a pin for the parameter __WorldContext of <FuncName>。对调用节点执行 Refresh Nodes 无效;手动编译一次函数库蓝图可临时恢复;删除生成蓝图后首次启动正常、再次启动必现。
image

两者根因不同,但同源于生成端对"函数库"这一语义的处理缺失,因此一并修复。

根因分析

一、生成端从不产出 FUNC_Static

蓝图节点是否需要 Target 引脚取决于编译出的 UFunction 是否带 FUNC_Static,而整条生成链路都没有它:

  • PuertsEditor/CodeAnalyze.tsonBlueprintTypeAddOrChange 能识别方法的 static 修饰符(并强制函数库中的方法必须为 static),但计算函数 flags 时只收集 //@flags: 注解和 @flags()/set_flags()/clear_flags() 装饰器,从不把 static 映射为 FUNC_Static
  • UEMeta.ts@ufunction.ufunction(...) specifier 表中没有 Static(与 UE 一致:C++ 中 static 是语言关键字,不是 UFUNCTION specifier),元数据路径同样不会产出该标志;
  • UPEBlueprintAsset::AddFunction 只把传入 flags 写进函数入口节点的 ExtraFlags,由 Kismet 编译器合并进 UFunction::FunctionFlags——入口没有,产物自然没有。

而运行时对静态函数的支持其实是完备的JsEnvImpl.cpp):模块注入时静态函数从 JS 类构造器上查找方法(TS 的 static 方法挂在构造器而非 prototype 上);TryBindJsBlueprintFunctionLibrary 派生类的 CDO 只为带 FUNC_Static 的函数挂接 execLazyLoadCallJS;调用时以 undefined 作为 this 调用 JS 方法。即运行时早已就绪,仅因生成端缺少该 flag 而整条链路成为死代码。

UE 编辑器自身为函数库新增函数时也会显式补 FUNC_StaticSBlueprintPalette.cppBPTYPE_FunctionLibraryExtraFunctionFlags |= FUNC_Static),本修复正是对齐这一行为。

二、函数库蓝图的 BlueprintType 一直是 BPTYPE_Normal

实测生成出的 TS 函数库蓝图,ParentClassBlueprintFunctionLibrary,但 BlueprintType 全部为 BPTYPE_Normal。两处叠加造成:

  • UPEClassMetaData::SyncClassToBlueprint@uclass flags 重置类型:(InClass->ClassFlags & CLASS_Const) ? BPTYPE_Const : BPTYPE_Normal没有函数库这一档。它在 LoadOrCreateWithMetaData 中于 LoadOrCreate 之后执行,因此创建分支按父类选出的 BPTYPE_FunctionLibrary 会在同一次调用内被改回 BPTYPE_Normal
  • UPEBlueprintAsset::LoadOrCreate 只在创建分支按父类推导 BlueprintType,加载已有资产的分支只校正 ParentClass,不校正类型,因此错位状态无法自愈。

三、错位如何导致 __WorldContext 编译错误

隐藏的 __WorldContext 引脚由 K2Node_FunctionEntry::AllocateDefaultPinsFFunctionEntryHelper::RequireWorldContextParameterUEdGraphSchema_K2::IsStaticFunctionGraph 决定,后者有两条分支:函数库蓝图恒为真;否则退化为探测入口节点的 FUNC_Static

BlueprintType 错位为 BPTYPE_Normal 时只能走第二条分支,于是:

  • 该隐式参数的有无跟随本 PR 新增的 FUNC_Static,而不是跟随蓝图类型;
  • skeleton 侧 bIsStaticFunction = (BPTYPE_FunctionLibrary == BP->BlueprintType) 为假,FastGenerateSkeletonClass 中为函数库准备的兜底(硬编码补 FUNC_Static__WorldContext 参数、MD_WorldContext 元数据)全部失效,skeleton 的参数完全来自入口节点引脚。

两个类的更新节奏并不相同:SkeletonGeneratedClass 每次加载都按当前入口节点引脚重建,GeneratedClass上一次 full 编译时的签名并序列化到磁盘。而 AddFunction 先创建函数图与入口节点(此时 FUNC_Static 尚未写入 → 没有 __WorldContext 引脚),之后才写入 flags 并编译,因此:

  • 首次生成:generated 与 skeleton 都是"无该参数"的一致签名 → 调用方编译通过;
  • 之后:入口节点在某次重建时按已写入的 FUNC_Static 新增该引脚 → 每次加载重建的 skeleton 都带上该参数,磁盘上的 generated 仍是旧签名 → 必现报错。

编译调用方蓝图时,被调蓝图非 up-to-date 时签名校验会经 FBlueprintEditorUtils::GetMostUpToDateClass 重定向到 skeletonCallFunctionHandler.cpp),按其每个参数在节点引脚中查找,找不到 __WorldContext 即报此错。Refresh Nodes 无效是因为建引脚(UK2Node_CallFunction::AllocateDefaultPins)与签名校验解析到的类不同——注意引脚创建对每个参数是无条件的,GetHiddenPinsForFunction 只负责置 bHidden,所以节点缺该引脚只能是当时解析到的签名里确实没有。

修复方案

1. CodeAnalyze.ts:按 static 修饰符设置/清除 FUNC_Static

if (ts.getCombinedModifierFlags(symbol.valueDeclaration) & ts.ModifierFlags.Static) {
    flags |= FunctionFlags.FUNC_Static;
} else {
    clearFlags |= FunctionFlags.FUNC_Static;
}

静态方法(仅允许出现在 BlueprintFunctionLibrary 派生类中,上方已有过滤)置 FUNC_Static;非静态方法显式清除该位——AddFunction 合并 flags 时会保留入口节点上的旧 ExtraFlags,显式清除保证删除 static 后重新生成能收敛。

2. PEBlueprintAsset.cpp:加载分支按父类校正 BlueprintType

if (IsValid(ParentClass))
{
    const EBlueprintType DesiredBlueprintType =
        ParentClass->IsChildOf(UBlueprintFunctionLibrary::StaticClass()) ? BPTYPE_FunctionLibrary : BPTYPE_Normal;
    if (Blueprint->BlueprintType != DesiredBlueprintType)
    {
        CanChangeCheckWithBoolRet();
        Blueprint->BlueprintType = DesiredBlueprintType;
        NeedSave = true;
    }
}

与创建分支使用同一规则,不一致时置 NeedSave 触发重新编译与保存,使已有资产能自愈。

3. PEBlueprintMetaData.cpp:元数据同步不再覆盖函数库类型

if (InBlueprint->BlueprintType != BPTYPE_FunctionLibrary)
{
    const EBlueprintType newType = (InClass->ClassFlags & CLASS_Const) ? BPTYPE_Const : BPTYPE_Normal;
    if (InBlueprint->BlueprintType != newType)
    {
        InBlueprint->BlueprintType = newType;
        onChange = true;
    }
}

职责划分:是否函数库LoadOrCreate 依父类推导(唯一真源),@uclass flags 只决定 Const / Normal。仅改第 2 处无效——元数据同步会在同一次 LoadOrCreateWithMetaData 调用内把它重置回去。

影响面

  • 函数库静态方法:从"生成实例函数(节点带 Target、TS 实现不执行)"修复为正确的静态函数,且 __WorldContext 隐式参数由蓝图类型稳定决定,不再随 flag 漂移;
  • 普通类的非静态方法:新增的 clearFlags |= FUNC_Static 为幂等无操作——此前没有任何路径会给这些函数写入该位,且生成的函数节点不允许用户在编辑器手动修改 static;
  • 普通类的静态方法:仍被既有的 do not support static function 警告拦截,行为不变;
  • 非函数库蓝图:BlueprintType 的目标值仍是 Const / Normal,行为不变;
  • 函数库蓝图禁止声明类变量(FKismetCompilerContext::CreateVariableVariableInFunctionLibrary),因此继承 UE.BlueprintFunctionLibrary 的 TS 类不应声明属性字段;
  • 迁移成本:已有生成资产在下一次启动编辑器时自动收敛(NeedSave → 重新编译并保存)。修复前放置的调用节点需要重建一次引脚(签名从"无 __WorldContext"变为"有")——修复后两个类签名一致,Refresh Nodes 才真正有效,刷新后保存该蓝图即可。

验证

在 UE5 工程中实测:

  1. TS 侧定义 class TS_XxxFunctionLibrary extends UE.BlueprintFunctionLibrary,包含 @ufunction.ufunction(ufunction.BlueprintCallable) static Foo(): void 及带参重载;
  2. 修复前:5 个函数库蓝图的 BlueprintType 全为 BPTYPE_Normal,调用节点带 Target 引脚,调用方蓝图编译报 Could not find a pin for the parameter __WorldContext
  3. 修复后重启编辑器:5 个蓝图自动收敛为 BPTYPE_FunctionLibrary,无新增编译错误(无 VariableInFunctionLibrary、无 LogBlueprint 错误);调用节点不再出现 Target 引脚,形态与 C++ 函数库静态函数一致;调用方蓝图编译通过,__WorldContext 报错消失;
  4. PIE 中调用该节点可正常进入 TS 实现;带参函数的参数不错位(FFunctionTranslatorSkipWorldContextInArg0 已处理无声明参数时 __WorldContext 位于首参的情形,有声明参数时它位于参数列表末尾、由 JS 侧自然忽略)。

涉及文件

文件 修改
unreal/Puerts/PuertsEditor/CodeAnalyze.ts 函数 flags 计算处按 static 修饰符设置/清除 FUNC_Static
unreal/Puerts/Source/PuertsEditor/Private/PEBlueprintAsset.cpp LoadOrCreate 加载分支按父类校正 BlueprintType
unreal/Puerts/Source/PuertsEditor/Private/PEBlueprintMetaData.cpp SyncClassToBlueprint 不再把函数库蓝图降级为 BPTYPE_Normal
image

…nt functions

Static methods of TypeScript classes derived from BlueprintFunctionLibrary were generated as instance UFunctions. Blueprint call nodes then exposed a Target pin, and the runtime only wires JS dispatch for FUNC_Static functions, so the TS implementation was never invoked.

Set FUNC_Static when the method declaration has the static modifier, and explicitly clear it otherwise so regeneration converges when the modifier is added or removed. This matches how the UE editor marks functions created in a blueprint function library (SBlueprintPalette adds FUNC_Static for BPTYPE_FunctionLibrary).
@ZhiruiLi ZhiruiLi closed this Aug 6, 2026
LoadOrCreate derives BPTYPE_FunctionLibrary from the parent class, but only on the
create path, and UPEClassMetaData::SyncClassToBlueprint then resets BlueprintType using
@Uclass flags alone, which only knows Const and Normal. Since the metadata sync runs
right after LoadOrCreate inside LoadOrCreateWithMetaData, a function library blueprint is
downgraded to BPTYPE_Normal within the same call and never recovers.

With the wrong type, UEdGraphSchema_K2::IsStaticFunctionGraph falls back to probing
FUNC_Static on the function entry node, so the implicit hidden __WorldContext parameter
follows that flag instead of the blueprint type, and the function library fallbacks in
FastGenerateSkeletonClass stay disabled. The serialized generated class and the skeleton
class, rebuilt on every load, then disagree on the signature: callers fail to compile with
"Could not find a pin for the parameter __WorldContext", and Refresh Nodes cannot fix it
because pin creation and signature validation resolve different classes.

Derive BlueprintType from the parent class on the load path as well, and stop the metadata
sync from overwriting the function library case.
@ZhiruiLi ZhiruiLi reopened this Aug 6, 2026
@ZhiruiLi ZhiruiLi changed the title fix(unreal): 生成的蓝图函数补充 FUNC_Static,修复 TS 函数库静态方法无法直接调用 fix(unreal): TS 函数库静态方法生成为静态 UFunction,并修正函数库蓝图的 BlueprintType Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant