4.4 蓝图节点扩展与 C++ 库导出

让非程序员用上你的 C++ 能力

工具的核心逻辑常是 C++(性能/API 需要),但又想让策划在蓝图里用起来。三条标准路线:

方式 场景 例子
蓝图函数库(UBlueprintFunctionLibrary) 无状态工具函数 UMyToolBPLibrary::BatchValidate()
蓝图资产操作节点 Editor Scripting 那类 已有官方实现,多数不用自己做
派生 UObject 带蓝图可调用方法 有状态对象 数据处理管线对象
UCLASS()
class UMyToolBPLibrary : public UBlueprintFunctionLibrary
{
    GENERATED_BODY()
public:
    UFUNCTION(BlueprintCallable, Category = "MyTool")
    static TArray<FString> FindBrokenAssets(const FString& RootPath);
};

这是「C++ 做重活、蓝图做交互」的标准接口,与 3.4 的混合策略天然衔接。

参数类型的选择

蓝图节点参数有边界,选类型时记住:

  • 支持:基础类型、FString/FText/FName、UObject*、结构体(USTRUCT(BlueprintType)、枚举(UENUM(BlueprintType)、数组/集合
  • 不支持/麻烦:指针的指针、void*、模板类、std:: 容器、TMap 的蓝图支持有限
  • 可选:蓝图里参数默认值/高级显示(advanced display)

USTRUCT 的蓝图友好性

USTRUCT(BlueprintType)
struct FAssetCheckResult
{
    GENERATED_BODY()
    UPROPERTY(BlueprintReadWrite, Category="MyTool") TArray<FString> BrokenAssets;
    UPROPERTY(BlueprintReadWrite, Category="MyTool") int32 TotalChecked = 0;
};

注意:结构体的成员变化是「不兼容旧存档/旧蓝图引脚」的破坏性改动。给结构体留扩展空间(比如额外的 Reserved 字段)是坏味道的设计——对外暴露的结构体就按新版本处理,配合版本号管理。

蓝图异步节点

如果有耗时操作想从蓝图触发且不卡:

UCLASS()
class UMyAsyncTask : public UBlueprintAsyncActionBase
{
    GENERATED_BODY()
public:
    UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly="true"))
    static UMyAsyncTask* RunCheck();
    DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnDone);
    UPROPERTY(BlueprintAssignable) FOnDone OnDone;
    virtual void Activate() override;   // 在这起后台任务
};

蓝图里 spawn 出来后 bind OnDone 事件。这套模式适合「处理几万个资产」之类的场景。

函数命名与兼容

暴露给蓝图的函数一旦发布,参数和返回值类型就不能随便改(蓝图引脚会断)。规范:

  • 新功能 → 加新函数名
  • 改行为 → 加版本后缀(FindBrokenAssets2)或新参数且必须给默认值
  • 废弃函数 → 留着,EditorOnly 或标记 deprecated 提示

命名空间与 C++ 导出

模块对外暴露 C++ API(让别的插件/项目 C++ 直接调):类放 Public 目录、用 MODULENAME_API 宏。约定:插件的公开 C++ API 比蓝图 API 更难改——提前把「核心稳定接口」和「内部工具实现」分开放。

心得:把蓝图 API 当「产品 API」对待。我们插件的核心蓝图函数三年没改签名——不是需求没变,是每次变都加新函数而不是改旧的。生态的稳定感就是这么积累的。