UE5源码分析之Editor——AnimGraph模块全面分析

AnimGraph 模块全面系统分析


源文档:https://gitee.com/chouchouxia/share-ue5

目录

  1. 模块概述
  2. 必须知道的问题
  3. 模块整体架构解析
  4. 核心类代码详解
  5. 功能使用示例编写
  6. 总结与最佳实践

1 模块概述

1.1 基本信息

属性
模块名称AnimGraph
类型Editor(EngineDeveloper)
位置Engine/Source/Editor/AnimGraph
描述动画蓝图图形编辑器的核心模块,负责动画蓝图节点的可视化编辑、编译、属性绑定和预览

AnimGraph 是 UE5 动画蓝图编辑器的"心脏"。它一端连接着动画蓝图(UAnimBlueprint),另一端连接着动画蓝图运行时(AnimGraphRuntime),把"在编辑器中拖拽连线"这个直观操作,翻译成"运行时高效执行的动画节点图"。

这个模块的代码量非常大——Public 和 Private 目录下加起来超过 200 个文件,涵盖了上百种动画蓝图节点的编辑器表现、完整的编译管线、编辑器模式、属性绑定、图属性系统等方方面面。

1.2 模块文件结构

AnimGraph/
├── AnimGraph.Build.cs
├── Public/
│   ├── AnimGraphNode_Base.h              # 所有动画蓝图节点的编辑器基类
│   ├── AnimGraphNode_SkeletalControlBase.h  # 骨骼控制节点基类
│   ├── AnimGraphNode_AssetPlayerBase.h   # 动画资产播放节点基类
│   ├── AnimGraphNode_StateMachineBase.h  # 状态机节点基类
│   ├── AnimationGraphSchema.h            # 动画图 Schema
│   ├── AnimBlueprintExtension.h          # 动画蓝图扩展系统
│   ├── IAnimBlueprintCompilationContext.h # 编译上下文接口
│   ├── AnimGraphCommands.h               # 编辑器命令
│   ├── AnimNodeEditModes.h               # 节点编辑模式 ID
│   ├── AnimGraphSettings.h               # 模块设置
│   ├── AnimGraphAttributes.h             # 图属性系统
│   ├── AnimGraphNode_*.h                 # 各类动画蓝图节点(约 80+ 个)
│   ├── AnimationGraph.h                  # 动画图
│   ├── AnimationStateMachineGraph.h      # 状态机图
│   ├── AnimationStateGraph.h             # 状态图
│   ├── AnimationTransitionGraph.h        # 过渡图
│   └── ...
├── Private/
│   ├── AnimGraphModule.cpp               # 模块入口
│   ├── AnimationGraphSchema.cpp          # Schema 实现
│   ├── AnimBlueprintCompiler.cpp/.h      # 动画蓝图编译器
│   ├── AnimBlueprintExtension_*.cpp/.h   # 各类扩展实现(约 10 个)
│   ├── AnimGraphNode_*.cpp               # 各类节点实现
│   ├── EditModes/                        # 编辑器模式(IK、PoseDriver 等)
│   ├── AnimGraphDetails.cpp              # 图自定义
│   ├── PoseDriverDetails.cpp             # PoseDriver 细节面板
│   └── ...
└── Internal/
    ├── AnimBlueprintExtension_Base.h
    └── AnimGraphNodeBinding.h

1.3 模块依赖

AnimGraph 模块的依赖关系在 [AnimGraph.Build.cs](file:///f:/UE/UE_5.5/Engine/Source/Editor/AnimGraph/AnimGraph.Build.cs) 中定义:

公共依赖(PublicDependencyModuleNames):

模块用途
Core / CoreUObject / EngineUE 核心基础设施
SlateUI 框架
AnimGraphRuntime动画蓝图运行时节点(FAnimNode_Base 等)
BlueprintGraph蓝图图形编辑基础设施(UK2NodeUEdGraphSchema_K2 等)

私有依赖(PrivateDependencyModuleNames):

模块用途
UnrealEd编辑器核心
GraphEditor通用图形编辑器框架
PropertyEditor属性面板(细节面板)
KismetCompiler / Kismet蓝图编译基础设施
ToolMenus右键菜单
EditorWidgets / ToolWidgets编辑器 UI 组件
AnimationEditMode动画编辑模式
DeveloperSettings开发者设置
ContentBrowser内容浏览器集成

2 必须知道的问题

2.1 为什么要做 AnimGraph 模块?如果没有它会怎么样?

核心定位

AnimGraph 的定位可以这样理解:它是动画蓝图编辑器的"编辑器层"

在 UE 中,动画系统分为两大块:

层级模块职责
运行时AnimGraphRuntimeEngine 的一部分)定义 FAnimNode_Base 和各种运行时动画节点,负责实际的动画计算
编辑器AnimGraphEditor 的一部分)为每个运行时节点提供编辑器可视化表现、属性编辑、编译、预览

AnimGraph 模块做的事情,本质上就是把每一个运行时 FAnimNode_Base 子类,映射到一个对应的 UAnimGraphNode_Base 编辑器子类,然后在这个编辑器子类上实现:

  • 图上的可视化连线(Pose Link)
  • 属性引脚暴露与绑定(Property Binding)
  • 编译时把编辑器数据翻译成运行时数据结构
  • 编辑器中的骨骼操作预览(Edit Mode)
  • 右键菜单、拖拽、快捷键等编辑器交互

如果没有这个模块会怎么样?

那就没有动画蓝图编辑器了。具体来说:

  • 无法在编辑器中可视化地编辑动画蓝图:现在大家打开动画蓝图,看到的那张节点图——状态机、BlendSpace、IK、各种混合节点——全部是 AnimGraph 模块提供的。没有它,就只能靠 C++ 代码手写动画逻辑。
  • 无法编译动画蓝图FAnimBlueprintCompilerContext 负责把图上的节点翻译成 UAnimBlueprintGeneratedClass 中的运行时数据结构。没有编译器,蓝图就只是一张图,无法真正运行。
  • 无法在编辑器中预览动画:编辑模式(Edit Mode)系统允许在视口中拖拽 IK 目标、调整骨骼位置,这是 AnimGraph 模块通过 FAnimNodeEditMode 体系实现的。
  • 属性绑定(Property Binding)不可用:动画节点上那些"从蓝图变量取值"的绑定功能,是 AnimGraph 在编译阶段处理的。

AnimGraph 帮我们省掉了动画系统中最"可视化密集"的那部分工作,让大家能像搭积木一样搭建动画逻辑。

2.2 这个模块和 AnimGraphRuntime 是什么关系?

这是一个非常关键的问题。两者的关系可以概括为:一一映射 + 编译器桥接

编辑器层 (AnimGraph)                    运行时层 (AnimGraphRuntime)
─────────────────                      ─────────────────────────
UAnimGraphNode_SequencePlayer  ←──→    FAnimNode_SequencePlayer
UAnimGraphNode_StateMachine    ←──→    FAnimNode_StateMachine
UAnimGraphNode_TwoBoneIK       ←──→    FAnimNode_TwoBoneIK
UAnimGraphNode_BlendSpacePlayer ←──→   FAnimNode_BlendSpacePlayer
...                                    ...

每个运行时 FAnimNode_Base 子类都有一个对应的 UAnimGraphNode_Base 子类。编辑器节点内部持有一个对应运行时节点的实例,编译时把编辑器的数据拷贝到运行时结构中。

关键设计点:

  • 编辑器节点继承自 UK2Node,所以它能参与蓝图图的编辑流程(引脚、连线、菜单等)
  • 运行时节点继承自 FAnimNode_Base,它是一个纯 C++ 结构体,不包含任何编辑器逻辑
  • 编译器(FAnimBlueprintCompilerContext 负责把编辑器节点中的数据"烘焙"到运行时结构里

2.3 核心工作机制:从"图上连线"到"运行时执行"

动画蓝图编辑器的工作流程大致如下:

1. 在动画蓝图中创建动画图(AnimationGraph)
   ↓
2. 在图上拖入动画节点(如 SequencePlayer、BlendSpace、TwoBoneIK 等)
   ↓
3. 通过 Pose Link 连线定义数据流向
   ↓
4. 通过属性绑定将节点属性连接到蓝图变量
   ↓
5. 点击"编译"(Compile)
   ↓
6. FAnimBlueprintCompilerContext 处理所有节点:
   a. 分配节点索引(AllocateNodeIndexCounter)
   b. 分析 Pose Link 连接关系
   c. 折叠常量属性(Constant Folding)
   d. 创建运行时数据结构(AnimBlueprintGeneratedClass)
   e. 处理扩展系统(Extension System)
   ↓
7. 生成 UAnimBlueprintGeneratedClass,可在运行时执行

3 模块整体架构解析

3.1 架构图

┌──────────────────────────────────────────────────────────────────────────────┐
│                           动画蓝图编辑器 (Persona)                              │
│  ┌─────────────────────────────────────────────────────────────────────────┐  │
│  │                        动画蓝图 (UAnimBlueprint)                          │  │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐                  │  │
│  │  │ AnimationGraph│  │ StateMachine │  │ Transition   │   ...           │  │
│  │  │  (动画图)     │  │    Graph     │  │    Graph     │                  │  │
│  │  │              │  │  (状态机图)   │  │  (过渡图)    │                  │  │
│  │  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘                  │  │
│  └─────────┼──────────────────┼──────────────────┼─────────────────────────┘  │
└────────────┼──────────────────┼──────────────────┼────────────────────────────┘
             │                  │                  │
             └──────────────────┴──────────────────┘
                                │
                                ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│                     AnimGraph 模块 (Editor层)                                  │
│                                                                               │
│  ┌─────────────────────────────────────────────────────────────────────────┐ │
│  │                    UAnimationGraphSchema (图 Schema)                      │ │
│  │  - 管理连线规则、引脚类型、拖放行为、右键菜单                               │ │
│  │  - 定义 Pose Link 引脚(本地空间 / 组件空间)                               │ │
│  │  - 继承自 UEdGraphSchema_K2                                             │ │
│  └─────────────────────────────────────────────────────────────────────────┘ │
│                                      │                                        │
│  ┌───────────────────────────────────┼────────────────────────────────────┐  │
│  │                      UAnimGraphNode_Base (节点基类)                      │  │
│  │  ┌──────────────────────────────────────────────────────────────────┐   │  │
│  │  │  核心功能:                                                          │   │  │
│  │  │  - 引脚管理 (CreateOutputPins, AllocateDefaultPins)               │   │  │
│  │  │  - 属性绑定 (PropertyBinding)                                      │   │  │
│  │  │  - 编译钩子 (OnProcessDuringCompilation)                           │   │  │
│  │  │  - 编辑模式 (GetEditorMode, OnNodeSelected)                        │   │  │
│  │  │  - 预览调试 (Draw, GetOnScreenDebugInfo, FindDebugAnimNode)       │   │  │
│  │  │  - 函数绑定 (InitialUpdateFunction, BecomeRelevantFunction,       │   │  │
│  │  │              UpdateFunction)                                        │   │  │
│  │  └──────────────────────────────────────────────────────────────────┘   │  │
│  └─────────────────────────────────────────────────────────────────────────┘  │
│                                      │                                        │
│          ┌───────────────────────────┼───────────────────────────┐            │
│          ▼                           ▼                           ▼            │
│  ┌───────────────────┐  ┌───────────────────────┐  ┌───────────────────┐     │
│  │ AssetPlayerBase   │  │ SkeletalControlBase  │  │ StateMachineBase  │     │
│  │ (资产播放节点)     │  │ (骨骼控制节点)        │  │ (状态机节点)      │     │
│  ├───────────────────┤  ├───────────────────────┤  ├───────────────────┤     │
│  │ SequencePlayer    │  │ TwoBoneIK             │  │ AnimStateNode     │     │
│  │ BlendSpacePlayer  │  │ CCDIK                 │  │ StateMachineNode  │     │
│  │ SequenceEvaluator │  │ Fabrik                │  │ TransitionNode    │     │
│  │ RandomPlayer      │  │ LookAt                │  │ ConduitNode       │     │
│  │ PoseDriver        │  │ ModifyBone            │  │ StateAliasNode    │     │
│  │ ...               │  │ ...                   │  │ ...               │     │
│  └───────────────────┘  └───────────────────────┘  └───────────────────┘     │
│                                                                               │
│  ┌─────────────────────────────────────────────────────────────────────────┐ │
│  │                    UAnimBlueprintExtension (扩展系统)                      │ │
│  │  ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────────┐  │ │
│  │  │ Extension_Base   │ │ Extension_State  │ │ Extension_PropertyAccess │  │ │
│  │  │ (基础扩展)        │ │    Machine       │ │ (属性访问扩展)            │  │ │
│  │  │                  │ │ (状态机扩展)      │ │                          │  │ │
│  │  └──────────────────┘ └──────────────────┘ └──────────────────────────┘  │ │
│  │  ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────────┐  │ │
│  │  │ Extension_       │ │ Extension_       │ │ Extension_LinkedAnim     │  │ │
│  │  │ CachedPose       │ │ CallFunction     │ │      Graph               │  │ │
│  │  │ (缓存姿势扩展)    │ │ (函数调用扩展)    │ │ (链接动画图扩展)          │  │ │
│  │  └──────────────────┘ └──────────────────┘ └──────────────────────────┘  │ │
│  └─────────────────────────────────────────────────────────────────────────┘ │
│                                                                               │
│  ┌─────────────────────────────────────────────────────────────────────────┐ │
│  │              FAnimBlueprintCompilerContext (编译器)                       │ │
│  │  - 继承自 FKismetCompilerContext                                        │ │
│  │  - ProcessAllAnimationNodes() → 处理所有节点                             │ │
│  │  - 常量折叠 (Constant Folding)                                          │ │
│  │  - 生成 AnimBlueprintGeneratedClass                                     │ │
│  │  - 协调 Extension 系统参与编译                                            │ │
│  └─────────────────────────────────────────────────────────────────────────┘ │
│                                                                               │
│  ┌─────────────────────────────────────────────────────────────────────────┐ │
│  │                    EditModes (编辑器模式)                                  │ │
│  │  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐   │ │
│  │  │ TwoBoneIK    │ │ CCDIK        │ │ Fabrik       │ │ PoseDriver   │   │ │
│  │  │ EditMode     │ │ EditMode     │ │ EditMode     │ │ EditMode     │   │ │
│  │  └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘   │ │
│  │  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐   │ │
│  │  │ ModifyBone   │ │ ObserveBone  │ │ LookAt       │ │ SplineIK     │   │ │
│  │  │ EditMode     │ │ EditMode     │ │ EditMode     │ │ EditMode     │   │ │
│  │  └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘   │ │
│  └─────────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
                                      │
                                      ▼
┌──────────────────────────────────────────────────────────────────────────────┐
│                     AnimGraphRuntime (运行时层)                                 │
│  ┌─────────────────────────────────────────────────────────────────────────┐ │
│  │  FAnimNode_Base → FAnimNode_AssetPlayerBase → FAnimNode_SkeletalControl │ │
│  │                 → FAnimNode_StateMachine → ...                          │ │
│  └─────────────────────────────────────────────────────────────────────────┘ │
│  ┌─────────────────────────────────────────────────────────────────────────┐ │
│  │  UAnimBlueprintGeneratedClass (编译产物)                                  │ │
│  │  - 节点数组 AnimNodeData[]                                              │ │
│  │  - 属性绑定数据                                                           │ │
│  │  - 扩展数据                                                               │ │
│  └─────────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘

3.2 模块划分

1. 节点层(Node Layer)

职责:

  • 为每个运行时 FAnimNode_Base 子类提供编辑器可视化表现
  • 管理引脚(Pin)的创建、连接、默认值
  • 提供属性绑定接口
  • 处理编译钩子

核心类:

  • UAnimGraphNode_Base — 所有动画蓝图编辑器节点的基类
  • UAnimGraphNode_SkeletalControlBase — 骨骼控制节点基类(IK、LookAt 等)
  • UAnimGraphNode_AssetPlayerBase — 动画资产播放节点基类(SequencePlayer、BlendSpacePlayer 等)
  • UAnimGraphNode_StateMachineBase — 状态机节点基类
  • UAnimGraphNode_BlendListBase — 混合列表节点基类

节点分类:

分类基类典型节点
资产播放UAnimGraphNode_AssetPlayerBaseSequencePlayer, BlendSpacePlayer, SequenceEvaluator, RandomPlayer, PoseDriver
骨骼控制UAnimGraphNode_SkeletalControlBaseTwoBoneIK, CCDIK, Fabrik, LookAt, ModifyBone, SplineIK, SpringBone, Trail
状态机UAnimGraphNode_StateMachineBaseAnimStateNode, StateMachineNode, TransitionNode, ConduitNode
混合UAnimGraphNode_BlendListBaseBlendListByBool, BlendListByEnum, BlendListByInt, LayeredBoneBlend
空间转换UAnimGraphNode_BaseLocalToComponentSpace, ComponentToLocalSpace
缓存UAnimGraphNode_BaseSaveCachedPose, UseCachedPose
姿势引用UAnimGraphNode_BaseMeshRefPose, IdentityPose, LocalRefPose
其他UAnimGraphNode_BaseRoot, Slot, Sync, Mirror, CopyPoseFromMesh 等

2. 图 Schema 层(Schema Layer)

职责:

  • 定义动画图中的连线规则
  • 管理 Pose Link 引脚类型(本地空间 / 组件空间)
  • 处理拖放资产到图上的行为
  • 提供右键菜单

核心类:

  • UAnimationGraphSchema — 动画蓝图主图的 Schema
  • UAnimationStateMachineSchema — 状态机图的 Schema
  • UAnimationStateGraphSchema — 单个状态内部图的 Schema
  • UAnimationTransitionSchema — 过渡图(Transition Rule)的 Schema
  • UAnimationConduitGraphSchema — 管道(Conduit)图的 Schema

3. 编译层(Compiler Layer)

职责:

  • 将编辑器节点图翻译成运行时数据结构
  • 处理 Pose Link 映射
  • 常量折叠优化
  • 生成 UAnimBlueprintGeneratedClass

核心类:

  • FAnimBlueprintCompilerContext — 动画蓝图编译器主体
  • IAnimBlueprintCompilationContext — 编译上下文接口
  • IAnimBlueprintCompilerCreationContext — 编译器创建上下文接口
  • IAnimBlueprintGeneratedClassCompiledData — 编译产物数据接口

4. 扩展系统层(Extension Layer)

职责:

  • 允许为动画蓝图添加"子系统"级别的数据和处理逻辑
  • 在编译过程的各个阶段执行自定义逻辑
  • 管理扩展专属的实例数据和类数据

核心类:

  • UAnimBlueprintExtension — 扩展基类
  • UAnimBlueprintExtension_Base — 基础扩展(处理节点处理)
  • UAnimBlueprintExtension_StateMachine — 状态机扩展
  • UAnimBlueprintExtension_CachedPose — 缓存姿势扩展
  • UAnimBlueprintExtension_CallFunction — 函数调用扩展
  • UAnimBlueprintExtension_PropertyAccess — 属性访问扩展
  • UAnimBlueprintExtension_LinkedAnimGraph — 链接动画图扩展
  • UAnimBlueprintExtension_LinkedInputPose — 链接输入姿势扩展
  • UAnimBlueprintExtension_Tag — 标签扩展
  • UAnimBlueprintExtension_BlendSpaceGraph — BlendSpace 图扩展
  • UAnimBlueprintExtension_Attributes — 属性扩展

5. 编辑模式层(Edit Mode Layer)

职责:

  • 在视口中提供骨骼级别的交互式编辑
  • 允许拖拽 IK 目标、调整骨骼位置
  • 提供节点专属的视口可视化

核心类:

  • FAnimNodeEditMode — 编辑模式基类
  • FTwoBoneIKEditMode — 双骨骼 IK 编辑模式
  • FCCDIKEditMode — CCD IK 编辑模式
  • FFabrikEditMode — FABRIK 编辑模式
  • FLookAtEditMode — LookAt 编辑模式
  • FModifyBoneEditMode — 修改骨骼编辑模式
  • FObserveBoneEditMode — 观察骨骼编辑模式
  • FPoseDriverEditMode — PoseDriver 编辑模式
  • FSplineIKEditMode — 样条 IK 编辑模式
  • FAnimDynamicsEditMode — 动画动力学编辑模式

6. 基础设施层(Infrastructure Layer)

职责:

  • 模块加载/卸载
  • 注册编辑器命令、编辑模式、细节面板自定义
  • 管理图属性(Graph Attributes)
  • 提供模块设置

核心类:

  • FAnimGraphModule — 模块入口
  • FAnimGraphCommands — 编辑器命令
  • UAnimGraphSettings — 模块设置
  • UAnimGraphAttributes — 图属性系统

3.3 模块间依赖关系

FAnimGraphModule (模块入口)
    │
    ├─ 注册 → FAnimGraphCommands (编辑器命令)
    ├─ 注册 → FAnimBlueprintCompilerContext (编译器)
    ├─ 注册 → FAnimNodeEditMode 系列 (编辑器模式)
    ├─ 注册 → 细节面板自定义 (FAnimBlueprintDetails, FPoseDriverDetails)
    ├─ 注册 → FAnimGraphDetails (图自定义)
    └─ 注册 → UAnimGraphAttributes (图属性)
            │
            ▼
    UAnimationGraphSchema (图 Schema)
            │
            ▼
    UAnimGraphNode_Base (节点基类)
            │
    ┌───────┼───────────┬──────────────┐
    ▼       ▼           ▼              ▼
  Asset   Skeletal   StateMachine   BlendList
  Player  Control    Base           Base
            │
            ▼
    FAnimBlueprintCompilerContext (编译器)
            │
            ├─ 使用 → UAnimBlueprintExtension (扩展系统)
            │         ├─ Extension_Base
            │         ├─ Extension_StateMachine
            │         ├─ Extension_PropertyAccess
            │         └─ ...
            │
            └─ 生成 → UAnimBlueprintGeneratedClass (编译产物)

3.4 数据流走向

编译流程:

1. 用户点击 Compile
   ↓
2. FAnimBlueprintCompilerContext 被创建
   ↓
3. CreateClassVariablesFromBlueprint() — 创建类变量
   ↓
4. SpawnNewClass() — 创建新的 UAnimBlueprintGeneratedClass
   ↓
5. ProcessAllAnimationNodes() — 处理所有动画节点
   ├─ 遍历所有 AnimationGraph 和子图
   ├─ 对每个 UAnimGraphNode_Base:
   │   ├─ 分配节点索引 (AllocateNodeIndexCounter)
   │   ├─ 创建 FProperty 到 AnimBlueprintGeneratedClass
   │   ├─ 分析 PoseLink 连接关系
   │   ├─ 收集常量折叠记录
   │   └─ 调用 OnProcessDuringCompilation()
   ├─ PruneIsolatedAnimationNodes() — 裁剪孤立节点
   └─ ProcessExtensions() — 处理扩展系统
   ↓
6. ProcessFoldedPropertyRecords() — 常量折叠
   ↓
7. CopyTermDefaultsToDefaultObject() — 复制默认值到 CDO
   ↓
8. FinishCompilingClass() — 完成编译
   ↓
9. 生成 UAnimBlueprintGeneratedClass,可在运行时使用

属性绑定流程:

1. 在编辑器中,右键点击节点属性 → "Bind" → 选择蓝图变量
   ↓
2. 创建 FAnimGraphNodePropertyBinding 记录
   ├─ PropertyName: 绑定的属性名
   ├─ PropertyPath: 属性路径
   ├─ Type: Property / Function
   └─ bIsBound: true
   ↓
3. 编译时,FAnimBlueprintCompilerContext 处理绑定
   ├─ 对于 Property 绑定: 生成属性访问代码
   ├─ 对于 Function 绑定: 生成函数调用代码
   └─ 处理 Promotion (如 bool → int 转换)
   ↓
4. 运行时,通过 PropertyAccess 系统高效读写属性

编辑模式交互流程:

1. 用户在动画蓝图中选中一个骨骼控制节点(如 TwoBoneIK)
   ↓
2. UAnimGraphNode_Base::OnNodeSelected() 被调用
   ↓
3. 节点返回 GetEditorMode() → AnimNodeEditModes::TwoBoneIK
   ↓
4. FTwoBoneIKEditMode 被激活
   ↓
5. 在视口中显示 IK 目标的 3D 操作手柄
   ↓
6. 用户拖拽手柄 → DoTranslation/DoRotation/DoScale
   ↓
7. 节点的运行时数据被更新 → 预览骨骼实时响应

3.5 核心技术栈

技术/类用途
UK2Node蓝图图节点基类,AnimGraph 节点继承自它
UEdGraphSchema_K2蓝图图 Schema 基类
FAnimNode_Base运行时动画节点基类
FKismetCompilerContext蓝图编译器基类
UBlueprintExtension蓝图扩展系统基类
FEditorModeTools编辑器模式管理
FPrimitiveDrawInterface3D 视口绘制接口
FStreamableManager异步资源加载(用于 PoseWatch 等)
FGameplayTag游戏标签(用于 UI 层)
FMemberReference函数引用(用于节点函数绑定)
IPropertyAccessEditor属性访问编辑器接口
FStructProperty结构体属性反射
FPropertyEditorModule属性编辑器模块

3.6 架构设计优势

  1. 编辑器-运行时分离UAnimGraphNode_Base(编辑器)与 FAnimNode_Base(运行时)完全分离,运行时不会携带任何编辑器开销
  2. 可扩展的节点体系:添加新节点只需继承 UAnimGraphNode_Base 并实现相应虚函数,Schema 自动处理连线
  3. 扩展系统UAnimBlueprintExtension 提供了类似"ECS 子系统"的机制,让不同功能模块独立参与编译
  4. 常量折叠优化:编译时自动识别并折叠常量属性,减少运行时开销
  5. 编辑模式可插拔:通过 FEditorModeID 注册,每个节点可以独立指定自己的视口交互模式
  6. 属性绑定系统:统一的属性绑定机制,支持 Property 和 Function 两种绑定类型
  7. 图属性系统UAnimGraphAttributes 提供了统一的属性流可视化(Pose、Curves、Attributes、Sync、RootMotion 等)

4 核心类代码详解

4.1 UAnimGraphNode_Base 类

[UAnimGraphNode_Base](file:///f:/UE/UE_5.5/Engine/Source/Editor/AnimGraph/Public/AnimGraphNode_Base.h) 是整个 AnimGraph 模块的基石。所有动画蓝图编辑器节点都继承自它,而它本身继承自 UK2Node

4.1.1 概述

核心设计理念:

  • 每个 UAnimGraphNode_Base 子类内部持有一个对应运行时 FAnimNode_Base 子类的实例
  • 编译时,编译器通过 GetFNodeType()GetFNodeProperty() 获取运行时类型信息
  • 节点通过 FPoseLinkMappingRecord 记录姿势链接关系

4.1.2 关键成员

// 显示的可选引脚
UPROPERTY(EditAnywhere, Category=PinOptions, EditFixedSize)
TArray<FOptionalPinFromProperty> ShowPinForProperties;

// 属性绑定(新系统使用 UAnimGraphNodeBinding)
UPROPERTY(EditAnywhere, Instanced, Category=Bindings)
TObjectPtr<UAnimGraphNodeBinding> Binding;

// 总是动态的属性(不会被常量折叠)
UPROPERTY()
TSet<FName> AlwaysDynamicProperties;

// 节点标签(可用于动画节点引用)
UPROPERTY(EditAnywhere, Category = Tag)
FName Tag;

// 函数绑定:OnInitialUpdate / OnBecomeRelevant / OnUpdate
UPROPERTY(EditAnywhere, Category = Functions)
FMemberReference InitialUpdateFunction;
UPROPERTY(EditAnywhere, Category = Functions)
FMemberReference BecomeRelevantFunction;
UPROPERTY(EditAnywhere, Category = Functions)
FMemberReference UpdateFunction;

4.1.3 核心方法

引脚管理:

  • AllocateDefaultPins() — 创建默认引脚(包括 Pose Link 引脚)
  • CreateOutputPins() — 创建输出姿势引脚
  • ReallocatePinsDuringReconstruction() — 重建时重新分配引脚
  • GetPinAssociatedProperty() — 获取引脚关联的运行时属性

属性绑定:

  • IsPinBindable() — 判断引脚是否可绑定
  • HasBinding() — 检查是否有绑定
  • MakePropertyBindingWidget() — 创建属性绑定 UI 控件
  • GetPinBindingInfo() — 获取引脚绑定信息

编译钩子:

  • OnProcessDuringCompilation() — 编译时处理节点数据
  • OnCopyTermDefaultsToDefaultObject() — 复制默认值到 CDO
  • OnOverrideAssets() — 子蓝图覆盖资产
  • ValidateAnimNodeDuringCompilation() — 编译时验证
  • ValidateAnimNodePostCompile() — 编译后验证
  • BakeDataDuringCompilation() — 烘焙数据

编辑模式:

  • OnNodeSelected() — 节点选中/取消选中时调用
  • GetEditorMode() — 返回节点对应的编辑模式 ID
  • Draw() — 视口绘制(用于骨骼可视化)
  • GetOnScreenDebugInfo() — 获取屏幕调试信息

调试:

  • FindDebugAnimNode() — 查找运行时节点实例
  • GetDebuggedAnimNode<T>() — 获取调试中的运行时节点
  • GetActiveInstanceNode<T>() — 获取活动实例中的运行时节点

4.1.4 FPoseLinkMappingRecord

FPoseLinkMappingRecord 是连接编辑器节点和运行时 Pose Link 的关键数据结构:

struct FPoseLinkMappingRecord
{
    UAnimGraphNode_Base* LinkedNode;   // 被连接的节点
    UAnimGraphNode_Base* LinkingNode;  // 发起连接的节点
    FProperty* ChildProperty;          // PoseLink 属性
    int32 ChildPropertyIndex;          // 数组索引(如果是数组属性)
};

编译时,编译器遍历所有 Pose Link 连接,生成 FPoseLinkMappingRecord 列表,然后在运行时通过 PatchLinkIndex() 把连接关系"写入"到运行时节点数据中。

4.2 UAnimationGraphSchema 类

[UAnimationGraphSchema](file:///f:/UE/UE_5.5/Engine/Source/Editor/AnimGraph/Public/AnimationGraphSchema.h) 继承自 UEdGraphSchema_K2,定义了动画蓝图主图的连线规则和行为。

核心功能

Pose 引脚类型管理:

  • IsPosePin() — 判断是否为姿势引脚
  • IsLocalSpacePosePin() — 判断是否为本地空间姿势引脚
  • IsComponentSpacePosePin() — 判断是否为组件空间姿势引脚
  • MakeLocalSpacePosePin() / MakeComponentSpacePosePin() — 创建姿势引脚类型

连线规则:

  • DetermineConnectionResponseOfCompatibleTypedPins() — 判断两个引脚是否可以连接
  • TryCreateConnection() — 尝试创建连接
  • ArePinsCompatible() — 检查引脚兼容性
  • 自动插入空间转换节点(LocalToComponentSpace / ComponentToLocalSpace

拖放行为:

  • DroppedAssetsOnGraph() — 处理拖放动画资产到图上
  • DroppedAssetsOnNode() — 处理拖放资产到节点上
  • SpawnNodeFromAsset() — 从资产生成对应节点
  • UpdateNodeWithAsset() — 更新节点的资产引用

右键菜单:

  • GetContextMenuActions() — 构建右键菜单

4.3 FAnimBlueprintCompilerContext 类

[FAnimBlueprintCompilerContext](file:///f:/UE/UE_5.5/Engine/Source/Editor/AnimGraph/Private/AnimBlueprintCompiler.h) 是整个动画蓝图编译的核心。它继承自 FKismetCompilerContext,负责把编辑器中的节点图翻译成可执行的 UAnimBlueprintGeneratedClass

核心成员

// 常量数据结构和可变数据结构
UScriptStruct* NewAnimBlueprintConstants;
UScriptStruct* NewAnimBlueprintMutables;

// 节点分配映射
TMap<UAnimGraphNode_Base*, FProperty*> AllocatedAnimNodes;
TMap<UAnimGraphNode_Base*, int32> AllocatedAnimNodeIndices;

// Pose Link 映射记录
TArray<FPoseLinkMappingRecord> ValidPoseLinkList;

// 常量折叠记录
TArray<TSharedRef<FFoldedPropertyRecord>> ConstantPropertyRecords;
TArray<TSharedRef<FFoldedPropertyRecord>> MutablePropertyRecords;

// 扩展系统映射
TMap<UAnimBlueprintExtension*, FStructProperty*> ExtensionToInstancePropertyMap;
TMap<UAnimBlueprintExtension*, FStructProperty*> ExtensionToClassPropertyMap;

编译流程

  1. ProcessAllAnimationNodes() — 遍历所有图形,找到所有 UAnimGraphNode_Base 节点
  2. PruneIsolatedAnimationNodes() — 从 Root 节点开始 BFS,裁剪掉没有连接到输出的孤立节点
  3. ProcessAnimationNode() — 处理单个节点:
    • 分配节点索引
    • 创建对应的 FProperty
    • 分析 Pose Link 连接
    • 收集常量折叠记录
    • 调用 OnProcessDuringCompilation()
  4. ProcessFoldedPropertyRecords() — 将可折叠的常量属性放入常量数据结构
  5. CopyTermDefaultsToDefaultObject() — 把编译好的数据拷贝到 CDO

常量折叠(Constant Folding)

常量折叠是编译器的关键优化。如果一个节点属性:

  • 没有被引脚暴露(bExposedOnPin == false
  • 没有被引脚连接(bPinConnected == false
  • 没有被标记为总是动态(!bAlwaysDynamic

那么它就会被折叠到类的常量数据中,而不是放在每个实例的可变数据里。这大大减少了运行时的内存访问开销。

4.4 UAnimBlueprintExtension 扩展系统

[UAnimBlueprintExtension](file:///f:/UE/UE_5.5/Engine/Source/Editor/AnimGraph/Public/AnimBlueprintExtension.h) 是 UE5 中引入的扩展系统,它允许不同功能模块以"子系统"的方式参与动画蓝图的编译过程。

设计理念

传统方式下,所有动画蓝图逻辑都塞在 FAnimBlueprintCompilerContext 里,导致编译器越来越臃肿。扩展系统把这些逻辑拆分成独立的 UAnimBlueprintExtension 子类,每个子类负责一个特定领域。

扩展的生命周期钩子

HandleBeginCompilation()           → 编译器创建时
    ↓
HandleStartCompilingClass()        → 类开始编译
    ↓
HandlePreProcessAnimationNodes()   → 处理节点之前
    ↓
HandlePostProcessAnimationNodes()  → 处理节点之后
    ↓
HandlePostExpansionStep()          → 图展开后
    ↓
HandleFinishCompilingClass()       → 类编译完成
    ↓
HandleCopyTermDefaultsToDefaultObject() → 复制到 CDO
    ↓
HandleEndCompilation()             → 编译器销毁

扩展类型一览

扩展类用途
UAnimBlueprintExtension_Base基础扩展,处理节点处理的核心逻辑
UAnimBlueprintExtension_StateMachine状态机相关逻辑
UAnimBlueprintExtension_CachedPoseSaveCachedPose / UseCachedPose 逻辑
UAnimBlueprintExtension_CallFunction动画函数调用逻辑
UAnimBlueprintExtension_PropertyAccess属性访问(Property Access)逻辑
UAnimBlueprintExtension_LinkedAnimGraph链接动画图层逻辑
UAnimBlueprintExtension_LinkedInputPose链接输入姿势逻辑
UAnimBlueprintExtension_Tag节点标签逻辑
UAnimBlueprintExtension_BlendSpaceGraphBlendSpace 图逻辑
UAnimBlueprintExtension_Attributes属性逻辑

数据管理

每个扩展可以声明自己的实例数据GetInstanceData<T>())和类数据GetClassData<T>()):

  • 实例数据:每个 UAnimInstance 实例一份
  • 类数据:每个 UAnimBlueprintGeneratedClass 一份(放在 SparseClassData 中)

4.5 FAnimGraphModule 模块入口

[FAnimGraphModule](file:///f:/UE/UE_5.5/Engine/Source/Editor/AnimGraph/Private/AnimGraphModule.cpp) 在 StartupModule() 中完成了大量初始化工作:

  1. 注册编辑器命令FAnimGraphCommands::Register()
  2. 注册编译器:将 FAnimBlueprintCompilerContext 注册为 UAnimBlueprint 的编译器
  3. 注册 PropertyAccess 绑定:将 FPropertyAccessAnimBlueprintBinding 注册为模块化特性
  4. 注册编辑器模式:注册 10 种 FAnimNodeEditMode 子类
  5. 注册细节面板自定义FAnimBlueprintDetailsFPoseDriverDetailsFAnimBlueprintFunctionPinInfoDetails
  6. 注册图属性:注册 Pose、Curves、Attributes、Sync、RootMotion、InertialBlending 等内置图属性
  7. 注册图自定义FAnimGraphDetails

5 功能使用示例编写

示例1:自定义一个简单的动画蓝图节点

假设要创建一个新的动画蓝图节点 UAnimGraphNode_MyCustomNode,对应运行时节点 FAnimNode_MyCustomNode

// MyCustomAnimGraphNode.h
#pragma once

#include "CoreMinimal.h"
#include "AnimGraphNode_Base.h"
#include "AnimNode_MyCustomNode.h"  // 运行时节点
#include "MyCustomAnimGraphNode.generated.h"

UCLASS()
class MYGAMEEDITOR_API UMyCustomAnimGraphNode : public UAnimGraphNode_Base
{
    GENERATED_BODY()

public:
    // 运行时节点实例(编译器通过它获取类型信息)
    UPROPERTY(EditAnywhere, Category = Settings)
    FAnimNode_MyCustomNode Node;

    // UAnimGraphNode_Base 接口
    virtual FLinearColor GetNodeTitleColor() const override
    {
        return FLinearColor(0.2f, 0.8f, 0.2f);
    }

    virtual FText GetTooltipText() const override
    {
        return NSLOCTEXT("MyGame", "MyCustomNode_Tooltip", "我的自定义动画节点");
    }

    virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override
    {
        return NSLOCTEXT("MyGame", "MyCustomNode_Title", "My Custom Node");
    }

    virtual FText GetMenuCategory() const override
    {
        return NSLOCTEXT("MyGame", "MyCustomNode_Category", "My Game");
    }

    // 创建输出姿势引脚
    virtual void CreateOutputPins() override
    {
        CreatePin(EGPD_Output, UAnimationGraphSchema::MakeLocalSpacePosePin(), TEXT("Pose"));
    }

    // 编译时处理
    virtual void OnProcessDuringCompilation(
        IAnimBlueprintCompilationContext& InCompilationContext,
        IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) override
    {
        // 处理输入姿势链接
        // ...
    }
};

示例2:为骨骼控制节点添加自定义编辑模式

// MyCustomEditMode.h
#pragma once

#include "CoreMinimal.h"
#include "AnimNodeEditMode.h"

class FMyCustomEditMode : public FAnimNodeEditMode
{
public:
    // 返回编辑模式 ID
    virtual FEditorModeID GetEditorModeID() const override
    {
        return TEXT("MyGame.MyCustomEditMode");
    }

    // 视口渲染
    virtual void Render(const FSceneView* View, FViewport* Viewport,
        FPrimitiveDrawInterface* PDI) override
    {
        // 绘制骨骼位置、IK 目标等
        FAnimNodeEditMode::Render(View, Viewport, PDI);
    }

    // 处理用户输入
    virtual bool HandleClick(FEditorViewportClient* InViewportClient,
        HHitProxy* HitProxy, const FViewportClick& Click) override
    {
        // 处理骨骼点击
        return FAnimNodeEditMode::HandleClick(InViewportClient, HitProxy, Click);
    }
};

然后在模块 StartupModule() 中注册:

FEditorModeRegistry::Get().RegisterMode<FMyCustomEditMode>(
    TEXT("MyGame.MyCustomEditMode"),
    NSLOCTEXT("MyGame", "MyCustomEditMode", "My Custom Edit Mode"),
    FSlateIcon(),
    false
);

示例3:创建自定义动画蓝图扩展

// MyAnimBlueprintExtension.h
#pragma once

#include "CoreMinimal.h"
#include "AnimBlueprintExtension.h"
#include "MyAnimBlueprintExtension.generated.h"

UCLASS()
class UMyAnimBlueprintExtension : public UAnimBlueprintExtension
{
    GENERATED_BODY()

public:
    // 在实例数据中添加自定义数据
    UPROPERTY()
    float MyCustomData = 0.0f;

protected:
    // 编译开始时初始化
    virtual void HandleBeginCompilation(
        IAnimBlueprintCompilerCreationContext& InCreationContext) override
    {
        Super::HandleBeginCompilation(InCreationContext);
        MyCustomData = 0.0f;
    }

    // 在所有节点处理完成后执行
    virtual void HandlePostProcessAnimationNodes(
        TArrayView<UAnimGraphNode_Base*> InAnimNodes,
        IAnimBlueprintCompilationContext& InCompilationContext,
        IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) override
    {
        // 遍历所有节点,收集自定义数据
        for (UAnimGraphNode_Base* Node : InAnimNodes)
        {
            // 处理逻辑...
        }
    }
};

示例4:注册自定义图属性

// 在模块的 StartupModule() 中
void FMyGameModule::StartupModule()
{
    UAnimGraphAttributes* AnimGraphAttributes = GetMutableDefault<UAnimGraphAttributes>();
    AnimGraphAttributes->LoadConfig();

    AnimGraphAttributes->Register(
        FAnimGraphAttributeDesc(
            TEXT("MyGame.MyCustomAttribute"),
            EAnimGraphAttributeBlend::Blendable,
            FSlateBrush(),
            NSLOCTEXT("MyGame", "MyAttribute", "My Attribute"),
            NSLOCTEXT("MyGame", "MyAttributeTip", "My custom graph attribute"),
            FLinearColor::Blue,
            EAnimGraphAttributesDisplayMode::Automatic)
    );
}

示例5:通过 Tag 引用动画节点

// 在动画蓝图中,给某个节点设置 Tag(如 "MyPoseNode")
// 然后在另一个节点中通过 AnimNodeReference 引用它:

void UMyAnimInstance::SomeFunction()
{
    // 通过 AnimNodeReference 获取标记的节点
    FAnimNodeReference NodeRef = GetAnimNodeReference(TEXT("MyPoseNode"));
    // 使用 NodeRef...
}

6 总结与最佳实践

核心要点

  1. AnimGraph 是动画蓝图的"编辑器层"

    • 它不参与运行时计算,只在编辑器中工作
    • 每个编辑器节点(UAnimGraphNode_Base)对应一个运行时节点(FAnimNode_Base
    • 编译器(FAnimBlueprintCompilerContext)是两者之间的桥梁
  2. 扩展系统是架构的核心创新

    • UAnimBlueprintExtension 让不同功能模块独立参与编译
    • 每个扩展有自己的实例数据和类数据
    • 新功能应该优先考虑通过扩展系统实现,而不是直接修改编译器
  3. 常量折叠是重要的性能优化

    • 编译时自动识别常量属性
    • 放在类级别数据中,避免每个实例都存一份
    • AlwaysDynamicProperties 可以阻止不需要的折叠
  4. 属性绑定是节点与蓝图通信的关键

    • 支持 Property 绑定和 Function 绑定
    • 通过 PropertyAccess 系统高效实现
    • 绑定信息在编译时处理,运行时零开销
  5. 编辑模式让视口交互成为可能

    • 每种骨骼控制节点可以有自己的编辑模式
    • 通过 FEditorModeRegistry 注册
    • 实现了 IK 拖拽、骨骼选择等可视化操作

最佳实践

  1. 创建自定义节点

    • 继承 UAnimGraphNode_Base(或 UAnimGraphNode_SkeletalControlBase / UAnimGraphNode_AssetPlayerBase
    • 重写 GetNodeTitleColor()GetTooltipText()GetNodeTitle() 提供视觉信息
    • 重写 CreateOutputPins() 创建输出姿势引脚
    • 重写 OnProcessDuringCompilation() 处理编译逻辑
    • 重写 GetMenuCategory() 归入正确的菜单分类
  2. 使用扩展系统

    • 对于新的子系统功能,创建 UAnimBlueprintExtension 子类
    • 利用生命周期钩子在正确的时机执行逻辑
    • 通过 GetInstanceData<T>() / GetClassData<T>() 管理数据
  3. 属性绑定

    • 优先使用 PropertyAccess 绑定而非老式的 PropertyBindings 系统
    • 对于需要频繁更新的属性,标记为 AlwaysDynamicProperties
    • 使用 IsPinExposedAndLinked() 检查引脚状态
  4. 编辑模式

    • 继承 FAnimNodeEditMode 创建自定义编辑模式
    • 通过 FEditorModeRegistry::Get().RegisterMode() 注册
    • 在节点中重写 GetEditorMode() 返回对应的模式 ID
  5. 图属性

    • 使用 UAnimGraphAttributes 注册自定义属性类型
    • 指定 EAnimGraphAttributeBlendEAnimGraphAttributesDisplayMode
    • 在节点中重写 GetInputLinkAttributes() / GetOutputLinkAttributes() 声明属性流
  6. 调试

    • 使用 GetDebuggedAnimNode<T>() 在编辑器中获取运行时节点
    • 重写 GetOnScreenDebugInfo() 输出调试信息
    • 使用 FindDebugAnimNode() 在预览骨骼网格上查找节点

常见问题

问题1:节点在编译后没有出现在生成的类中

  • 检查节点是否被 PruneIsolatedAnimationNodes() 裁剪
  • 确保节点通过 Pose Link 连接到了 Root 节点
  • 检查 IsSinkNode() 的返回值

问题2:属性绑定不生效

  • 检查 IsPinBindable() 是否返回 true
  • 确认绑定信息在编译时被正确处理
  • 检查 GetPinBindingInfo() 的返回值

问题3:自定义编辑模式无法激活

  • 确认在 StartupModule() 中正确注册了编辑模式
  • 检查节点的 GetEditorMode() 返回值与注册的 ID 一致
  • 确认 OnNodeSelected() 被正确调用

问题4:常量折叠导致属性无法运行时修改

  • 将该属性添加到 AlwaysDynamicProperties
  • 或者将该属性暴露为引脚并连线

附录:相关资源

  • AnimGraphRuntime 模块 — 运行时动画节点定义
  • AnimationBlueprintEditor 模块 — 动画蓝图编辑器主体
  • BlueprintGraph 模块 — 蓝图图编辑基础设施
  • KismetCompiler 模块 — 蓝图编译器基础设施
  • UE5 官方文档:Animation Blueprint
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值