BPVM 小食包 #19 - 自定义蓝图:扩展系统 | ebp BPVM 小食包 #19 - 自定义蓝图:扩展系统 | ebp BPVM 小食包 #19 - 自定义蓝图:扩展系统 | ebp
文章

BPVM 小食包 #19 - 自定义蓝图:扩展系统

现在你理解了蓝图如何编译。想要创建你自己的具有特殊编译规则的自定义蓝图类型吗?这就是扩展系统的工作原理。

BPVM 小食包 #19 - 自定义蓝图:扩展系统

本文内容基于Unreal Engine 5.6.0

BPVM 小食包 - 蓝图知识快速投喂!是蓝图到字节码系列的一部分。

超越常规蓝图

动画蓝图、Widget 蓝图、游戏能力蓝图 - 它们都使用自定义编译!

你也可以创建自己的!

扩展点

蓝图系统到处都有钩子:

1
2
3
4
5
6
7
8
9
10
11
12
13
class UBlueprint {
    // 自定义蓝图类型
    TSubclassOf<UBlueprint> BlueprintClassType;

    // 自定义编译器
    TSubclassOf<FKismetCompilerContext> CompilerType;

    // 自定义节点
    TArray<UEdGraphNode*> CustomNodes;

    // 扩展钩子
    TArray<UBlueprintExtension*> Extensions;
};

你可以自定义每个阶段!

自定义蓝图类

创建你自己的蓝图类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
// YourCustomBlueprint.h
UCLASS()
class UCustomBlueprint : public UBlueprint
{
    GENERATED_BODY()

    // 自定义数据
    UPROPERTY()
    TArray<FCustomData> SpecialData;

    // 覆盖编译
    virtual FKismetCompilerContext* CreateCompilerContext() override;
};

现在你在编辑器中有一个新的资产类型!

自定义编译器上下文

控制你的蓝图如何编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class FCustomCompilerContext : public FKismetCompilerContext
{
public:
    FCustomCompilerContext(UCustomBlueprint* Blueprint)
        : FKismetCompilerContext(Blueprint)
    {}

    // 覆盖编译阶段
    virtual void SpawnNewClass(const FString& NewClassName) override;
    virtual void CreateFunctionList() override;
    virtual void CompileClassLayout() override;

    // 添加自定义验证
    virtual void ValidateCustomData();

    // 添加自定义节点
    virtual void RegisterCustomNodes();
};

每个编译阶段完全控制!

自定义节点类型

为你的蓝图创建特殊节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
UCLASS()
class UK2Node_CustomOperation : public UK2Node
{
    GENERATED_BODY()

    // 自定义节点行为
    virtual void ExpandNode(FKismetCompilerContext& Context) override;

    // 自定义编译
    virtual FNodeHandlingFunctor* CreateNodeHandler(FKismetCompilerContext& Context) override;
};

// 自定义处理器
class FKCHandler_CustomOperation : public FNodeHandlingFunctor
{
    virtual void RegisterNets(Context, Node) override {
        // 注册自定义终端
    }

    virtual void Compile(Context, Node) override {
        // 生成自定义语句
    }
};

你的节点可以做任何事情!

真实示例:动画蓝图

动画蓝图扩展系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class UAnimBlueprint : public UBlueprint
{
    // 自定义图表类型
    UPROPERTY()
    UAnimationGraph* AnimGraph;

    UPROPERTY()
    UAnimationStateMachineGraph* StateMachineGraph;

    // 自定义编译器
    virtual FKismetCompilerContext* CreateCompilerContext() override {
        return new FAnimBlueprintCompilerContext(this);
    }
};

编译器添加状态机编译!

动画编译器扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class FAnimBlueprintCompilerContext : public FKismetCompilerContext
{
    virtual void CreateFunctionList() override {
        // 调用基础实现
        FKismetCompilerContext::CreateFunctionList();

        // 添加自定义:处理状态机!
        ProcessStateMachines();
        ProcessAnimationNodes();
    }

    void ProcessStateMachines() {
        // 将状态机图转换为字节码
        for (auto* StateMachine : AnimBP->StateMachines) {
            CompileStateMachine(StateMachine);
        }
    }
};

自定义验证

添加特殊检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class FCustomCompilerContext : public FKismetCompilerContext
{
    virtual void ValidateLink() override {
        // 基础验证
        FKismetCompilerContext::ValidateLink();

        // 自定义验证
        if (!Blueprint->HasSpecialProperty()) {
            Error("自定义蓝图需要 SpecialProperty!");
        }

        if (FunctionCount > MAX_FUNCTIONS) {
            Warning("函数太多,无法获得最佳性能");
        }
    }
};

蓝图扩展

在不子类化的情况下添加功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
UCLASS()
class UMyBlueprintExtension : public UBlueprintExtension
{
    GENERATED_BODY()

    // 在编译期间调用
    virtual void HandleBeginCompilation(FCompilerContext& Context) override {
        // 注入自定义行为
    }

    virtual void HandleEndCompilation(FCompilerContext& Context) override {
        // 后处理编译的类
    }
};

// 注册扩展
MyBlueprint->Extensions.Add(NewObject<UMyBlueprintExtension>());

扩展是模块化的!

自定义生成的类

控制生成的类:

1
2
3
4
5
6
7
8
9
10
11
12
UCLASS()
class UCustomBlueprintGeneratedClass : public UBlueprintGeneratedClass
{
    GENERATED_BODY()

    // 自定义运行时数据
    UPROPERTY()
    TArray<FRuntimeData> SpecialRuntimeData;

    // 自定义初始化
    virtual void InitializeCustomData();
};

图表模式自定义

控制允许哪些节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class UCustomGraphSchema : public UEdGraphSchema_K2
{
    // 覆盖节点创建
    virtual void GetGraphContextActions(FGraphContextMenuBuilder& Context) override {
        // 添加自定义节点到上下文菜单
        Context.AddAction(NewCustomNodeAction());
    }

    // 覆盖连接规则
    virtual bool CanCreateConnection(const UEdGraphPin* A, const UEdGraphPin* B) override {
        // 自定义连接验证
        return IsValidCustomConnection(A, B);
    }
};

真实世界用例

状态机蓝图:

  • 状态的自定义图表类型
  • 转换的特殊编译
  • 运行时状态执行

游戏能力蓝图:

  • 能力特定的节点
  • 预测编译
  • 网络复制处理

行为树蓝图:

  • 自定义任务节点
  • 特殊装饰器
  • AI 特定的编译

快速要点

  • 蓝图系统是完全可扩展的
  • 创建自定义蓝图子类
  • 覆盖编译器上下文以进行自定义编译
  • 添加带有处理器的自定义节点类型
  • 使用蓝图扩展实现模块化
  • 控制允许的节点的模式
  • 示例:动画、Widget、游戏能力蓝图
  • 编译流水线的完全控制!

扩展的力量

蓝图系统不仅仅是用于游戏玩法 - 它是可视化脚本系统的框架。理解编译流水线让你创建功能强大的自定义工具,看起来和感觉起来像原生虚幻功能!

想要更多细节?

完整的自定义蓝图指南:

最后一份小食:从这里去哪里!


🍿 BPVM 小食包系列

本文由作者按照 CC BY 4.0 进行授权