AnimationEditor 模块全面系统分析
目录
### 文章目录
- [AnimationEditor 模块全面系统分析](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [目录](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [@toc](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [1 模块概述](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [1.1 基本信息](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [1.2 为什么需要 AnimationEditor 模块?](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [2 模块整体架构解析](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [2.1 架构图](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [2.2 模块间依赖关系](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [2.3 模块划分](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [2.4 数据流走向](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [2.5 核心技术栈](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [2.6 架构设计优势](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [潜在改进点](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [3 类级代码注释详解](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [3.1 IAnimationEditorModule 接口](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [3.2 IAnimationEditor 接口](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [3.3 FAnimationEditor 类](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [3.3.1 初始化与生命周期](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [3.3.2 Tick 机制](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [3.3.3 命令绑定](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [3.3.4 动画操作](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [3.3.5 编辑器刷新](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [3.4 FAnimationEditorMode 类](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [3.5 FAnimationEditorCommands 类](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [3.6 UAnimationToolMenuContext 类](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [3.7 FAnimationEditorModule 类](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [4 功能使用示例编写](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [示例1:自定义动画编辑器工具栏扩展](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [示例2:通过资产工具打开动画编辑器](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [示例3:监听动画编辑器事件](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [5 如何扩展 AnimationEditor](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [5.1 扩展工具栏](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [5.2 扩展菜单](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [5.3 注册自定义面板](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [5.4 扩展布局](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [6 总结与最佳实践](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [核心要点](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [最佳实践](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [常见问题解决](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [附录](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [A. 相关资源](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
- [B. 文件结构参考](#文章目录 AnimationEditor 模块全面系统分析 目录 @[toc] 1 模块概述 1.1 基本信息 1.2 为什么需要 AnimationEditor 模块? 2 模块整体架构解析 2.1 架构图 2.2 模块间依赖关系 2.3 模块划分 2.4 数据流走向 2.5 核心技术栈 2.6 架构设计优势 潜在改进点 3 类级代码注释详解 3.1 IAnimationEditorModule 接口 3.2 IAnimationEditor 接口 3.3 FAnimationEditor 类 3.3.1 初始化与生命周期 3.3.2 Tick 机制 3.3.3 命令绑定 3.3.4 动画操作 3.3.5 编辑器刷新 3.4 FAnimationEditorMode 类 3.5 FAnimationEditorCommands 类 3.6 UAnimationToolMenuContext 类 3.7 FAnimationEditorModule 类 4 功能使用示例编写 示例1:自定义动画编辑器工具栏扩展 示例2:通过资产工具打开动画编辑器 示例3:监听动画编辑器事件 5 如何扩展 AnimationEditor 5.1 扩展工具栏 5.2 扩展菜单 5.3 注册自定义面板 5.4 扩展布局 6 总结与最佳实践 核心要点 最佳实践 常见问题解决 附录 A. 相关资源 B. 文件结构参考)
1 模块概述
1.1 基本信息
| 属性 | 值 |
|---|---|
| 模块名称 | AnimationEditor |
| 类型 | Editor(编辑器模块) |
| 位置 | Engine/Source/Editor/AnimationEditor |
| 描述 | UE5 动画资产编辑器,提供动画序列(AnimSequence)、动画蒙太奇(AnimMontage)等动画资产的编辑功能 |
整个模块共包含 8 个源文件(4 个公开头文件 + 4 个私有实现文件),代码量大约 900 行。这个模块是 UE5 编辑器工具链中 Persona 动画系统的一个核心组成部分,负责为 UAnimationAsset 及其子类(UAnimSequence、UAnimMontage、UAnimCompositeBase 等)提供完整的可视化编辑环境。
在 AnimationEditor 模块中,主要定义了 6 个关键类型:IAnimationEditor(编辑器接口)、IAnimationEditorModule(模块接口)、FAnimationEditor(编辑器实现)、FAnimationEditorMode(应用模式)、FAnimationEditorCommands(命令定义)、UAnimationToolMenuContext(菜单上下文)。
这个模块主要依赖了引擎的 Core、CoreUObject、Engine、Slate、Persona、SkeletonEditor、AnimGraph、ToolMenus 等模块。使用到的核心依赖是:Persona(动画工具集框架)、SkeletonEditor(骨骼树编辑)、AnimGraph(动画蓝图图表)、WorkflowOrientedApp(工作流应用框架)。
1.2 为什么需要 AnimationEditor 模块?
很多朋友可能觉得奇怪------UE 本身不是已经有 Persona 编辑器了吗?为什么要单独抽一个 AnimationEditor 模块出来?
其实,Persona 是一个编辑器框架 ,它提供了一套通用的骨骼网格、动画、物理资产的编辑基础设施。而 AnimationEditor 是 Persona 框架下的一个具体应用,专门负责动画序列和动画蒙太奇的编辑。如果没有这个模块:
- 每次打开一个
UAnimSequence资产,Persona 框架不知道应该展示哪些面板、布局长什么样 - 动画资产的压缩、重导入、FBX 导出等操作没有统一的入口
- 曲线编辑、动画曲线查看器、蒙太奇分段编辑等动画专属功能无法集成到编辑器里
- 外部模块无法扩展动画编辑器的工具栏和菜单
AnimationEditor 模块帮我们解决了"在 Persona 框架下,如何为动画资产构建一个完整、可用、可扩展的编辑环境"这个问题。
2 模块整体架构解析
2.1 架构图
┌─────────────────────────────────────────────────────────────────────┐
│ 外部调用者 (External Callers) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │
│ │ Asset Tools │ │ Other Editors│ │ External Extensions │ │
│ │ (双击资产) │ │ (Skeleton等) │ │ (工具栏/菜单扩展) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────────┬───────────────┘ │
└─────────┼──────────────────┼──────────────────────┼──────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────┐
│ IAnimationEditorModule (模块接口) │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ - CreateAnimationEditor() 创建编辑器实例 │ │
│ │ - GetMenuExtensibilityManager() 获取菜单扩展管理器 │ │
│ │ - GetToolBarExtensibilityManager()获取工具栏扩展管理器 │ │
│ │ - GetAllAnimationEditorToolbarExtenders() 获取所有工具栏扩展器 │ │
│ └────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
│ 创建
▼
┌─────────────────────────────────────────────────────────────────────┐
│ IAnimationEditor (编辑器接口) │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 继承: FPersonaAssetEditorToolkit + IHasPersonaToolkit │ │
│ │ 核心API: │ │
│ │ - SetAnimationAsset() 设置当前编辑的动画资产 │ │
│ │ - GetAssetBrowser() 获取资产浏览器 │ │
│ │ - EditCurves() 编辑曲线 │ │
│ │ - StopEditingCurves() 停止编辑曲线 │ │
│ └────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
│ 实现
▼
┌─────────────────────────────────────────────────────────────────────┐
│ FAnimationEditor (编辑器实现) │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 继承: IAnimationEditor, FGCObject, FTickableEditorObject │ │
│ │ 核心成员: │ │
│ │ - AnimationAsset 当前编辑的动画资产 │ │
│ │ - PersonaToolkit Persona 工具集 │ │
│ │ - SkeletonTree 骨骼树 │ │
│ │ - DetailsView 细节面板 │ │
│ │ - SequenceBrowser 序列浏览器 │ │
│ │ - CurveEditor 曲线编辑器 │ │
│ │ 核心功能: │ │
│ │ - InitAnimationEditor() 初始化编辑器 │ │
│ │ - ExtendMenu()/ExtendToolbar() 扩展菜单/工具栏 │ │
│ │ - BindCommands() 绑定UI命令 │ │
│ │ - OpenNewAnimationDocumentTab() 打开文档标签页 │ │
│ └────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│ │
│ 使用 │ 使用
▼ ▼
┌─────────────────────┐ ┌─────────────────────────────┐
│ FAnimationEditorMode│ │ FAnimationEditorCommands │
│ (应用模式) │ │ (命令定义) │
├─────────────────────┤ ├─────────────────────────────┤
│ - TabFactories │ │ - ImportAnimation │
│ - TabLayout │ │ - ReimportAnimation │
│ - RegisterTab- │ │ - ApplyCompression │
│ Factories() │ │ - SetKey │
│ - 定义面板布局 │ │ - ExportToFBX_* │
└─────────────────────┘ │ - AddLoopingInterpolation │
│ - RemoveBoneTracks │
└─────────────────────────────┘
2.2 模块间依赖关系
如果上述架构图看起来还有点抽象,可以参考下面的依赖关系进行理解。
IAnimationEditorModule (模块入口)
↓ 创建
IAnimationEditor (接口)
↓ 实现
FAnimationEditor (编辑器主体)
├─→ FAnimationEditorMode (定义应用模式、面板布局)
│ ├─→ ISkeletonEditorModule (创建骨骼树Tab)
│ └─→ FPersonaModule (创建Details、Viewport、AssetBrowser等Tab)
├─→ FAnimationEditorCommands (注册UI命令)
├─→ IPersonaToolkit (Persona工具集)
│ ├─→ IPersonaPreviewScene (预览场景)
│ └─→ USkeleton (骨骼)
├─→ ISkeletonTree (骨骼树)
└─→ UAnimationToolMenuContext (菜单上下文)
可能大家看到这里会感到有些疑惑------为什么 AnimationEditor 模块自己不实现所有的面板,而是大量依赖 Persona 模块来创建?
其实,这正是 UE5 编辑器框架设计的精妙之处。AnimationEditor 的职责是组装 ,而不是实现。Persona 模块提供了一套通用的、可复用的面板工厂(TabFactory),比如细节面板、视口面板、资产浏览器面板等。AnimationEditor 模块只需要:
- 决定使用哪些面板(通过
FAnimationEditorMode注册) - 决定面板怎么排列(通过
TabLayout定义布局) - 决定面板之间如何交互(通过回调委托连接)
这种设计让 AnimationEditor 的代码量保持在一个很小的规模(约 900 行),同时可以复用 Persona 模块中经过充分测试的复杂面板逻辑。如果将来需要为 SkeletonEditor、PhysicsAssetEditor 等编辑器添加动画编辑能力,它们也可以用同样的方式复用这些面板。
2.3 模块划分
对于每个模块的功能总结如下:
1. IAnimationEditorModule - 模块接口层
职责:
- 提供模块的入口点
- 创建编辑器实例
- 管理菜单和工具栏的扩展性
特点:
- 继承
IModuleInterface、IHasMenuExtensibility、IHasToolBarExtensibility - 通过
FExtensibilityManager提供扩展点,允许外部模块注入菜单项和工具栏按钮 - 额外提供
FAnimationEditorToolbarExtender委托,让外部模块可以添加自定义工具栏扩展
2. IAnimationEditor - 编辑器接口层
职责:
- 定义动画编辑器的公共 API
- 提供曲线编辑的支持结构
特点:
- 继承
FPersonaAssetEditorToolkit(Persona 资产编辑器工具集基类)和IHasPersonaToolkit - 定义了
FCurveEditInfo结构体,用于描述曲线编辑信息 - 接口设计简洁,只暴露必要的方法
3. FAnimationEditor - 编辑器实现层
职责:
- 实现
IAnimationEditor接口 - 管理编辑器的生命周期
- 处理用户交互(压缩、重导入、导出等)
- 协调各个面板之间的数据流动
特点:
- 同时继承
FGCObject(垃圾回收)和FTickableEditorObject(每帧 Tick) - 管理 Persona 工具集、骨骼树、序列浏览器、曲线编辑器等核心组件
- 提供丰富的动画操作:压缩、导出 FBX、添加循环插值、移除骨骼轨道、复制曲线到 SoundWave 等
4. FAnimationEditorMode - 应用模式层
职责:
- 定义编辑器的面板布局(TabLayout)
- 注册所有面板工厂(TabFactory)
特点:
- 继承
FApplicationMode - 定义了完整的编辑器布局:左侧骨骼树/资产详情 → 中间视口/文档 → 右侧细节面板/资产浏览器
- 通过
PersonaModule.OnRegisterTabs()和PersonaModule.OnRegisterLayoutExtensions()广播支持外部扩展
5. FAnimationEditorCommands - 命令定义层
职责:
- 定义编辑器中所有可用的 UI 命令
特点:
- 继承
TCommands<FAnimationEditorCommands> - 使用
UI_COMMAND宏定义命令,包括名称、描述、快捷键 - 涵盖导入、重导入、压缩、FBX 导出、循环插值、关键帧、移除骨骼轨道等 9 个命令
6. UAnimationToolMenuContext - 菜单上下文层
职责:
- 在工具菜单中传递编辑器实例的引用
特点:
- 继承
UObject - 持有
TWeakPtr<IAnimationEditor>弱引用 - 通过
FToolMenuContext在菜单回调中获取编辑器实例
2.4 数据流走向
编辑器初始化流程:
1. 用户双击动画资产(UAnimSequence/UAnimMontage 等)
↓
2. AssetTools 调用 IAnimationEditorModule::CreateAnimationEditor()
↓
3. FAnimationEditorModule 创建 FAnimationEditor 实例
↓
4. FAnimationEditor::InitAnimationEditor() 被调用
├─→ 注册重导入/导入回调(FReimportManager + UImportSubsystem)
├─→ 创建 IPersonaToolkit(通过 FPersonaModule)
├─→ 创建 ISkeletonTree(通过 ISkeletonEditorModule)
├─→ 调用 FAssetEditorToolkit::InitAssetEditor() 初始化编辑器框架
├─→ BindCommands() 绑定 UI 命令
├─→ 创建 FAnimationEditorMode 并设为当前模式
├─→ ExtendMenu() / ExtendToolbar() 扩展菜单和工具栏
└─→ OpenNewAnimationDocumentTab() 打开动画文档标签页
切换动画资产流程:
1. 用户在资产浏览器中选中另一个动画资产
↓
2. HandleOpenNewAsset() 被调用
↓
3. OpenNewAnimationDocumentTab(NewAsset) 被调用
├─→ 通过 PersonaModule 创建新的文档 Widget
├─→ RemoveEditingObject(旧资产) + AddEditingObject(新资产)
├─→ 更新 PersonaToolkit 的预览动画资产
├─→ 关闭旧的曲线编辑标签页(如果存在)
├─→ 如果是 AnimMontage,激活蒙太奇分段标签页
└─→ 通知 AssetFamily 记录资产打开
动画操作流程(以压缩为例):
1. 用户点击工具栏 "Apply Compression" 按钮
↓
2. FAnimationEditorCommands::ApplyCompression 命令触发
↓
3. FAnimationEditor::OnApplyCompression() 被调用
├─→ 检查当前资产是否为 UAnimSequence
├─→ 收集 AnimSequences 数组
└─→ 调用 FPersonaModule::ApplyCompression() 执行压缩
2.5 核心技术栈
| 技术/类 | 用途 |
|---|---|
FPersonaAssetEditorToolkit |
Persona 资产编辑器工具集基类 |
IPersonaToolkit |
Persona 工具集接口,管理预览场景、骨骼等 |
ISkeletonTree |
骨骼树接口,显示骨骼层级结构 |
IAnimationSequenceBrowser |
动画序列浏览器,浏览和管理动画资产 |
IAnimSequenceCurveEditor |
动画序列曲线编辑器 |
FWorkflowCentricApplication |
工作流中心应用,管理应用模式和 Tab |
FApplicationMode |
应用模式,定义布局和 Tab 工厂 |
FExtensibilityManager |
扩展性管理器,支持菜单/工具栏扩展 |
UToolMenus |
工具菜单系统(UE5 新菜单框架) |
FReimportManager |
重导入管理器 |
FAssetEditorToolkit |
资产编辑器工具集基类 |
FTickableEditorObject |
可 Tick 的编辑器对象 |
FGCObject |
垃圾回收对象接口 |
2.6 架构设计优势
- 高复用性:AnimationEditor 本身不实现面板,而是通过 Persona 模块的工厂方法创建,最大化代码复用
- 可扩展性强 :通过
FExtensibilityManager和FAnimationEditorToolbarExtender委托,外部模块可以自由扩展菜单和工具栏 - 职责清晰:模块接口、编辑器接口、编辑器实现、应用模式、命令定义各司其职,符合单一职责原则
- 布局可配置 :通过
FAnimationEditorMode::TabLayout定义面板布局,修改布局不需要改动核心逻辑 - 与引擎深度集成:支持重导入回调、导入回调、垃圾回收、编辑器 Tick 等引擎基础设施
潜在改进点
- 曲线编辑状态管理 :当前
EditCurves和StopEditingCurves之间没有显式的状态机,如果外部调用顺序不当可能导致状态不一致 - 错误处理 :部分操作(如
OnApplyCompression、OnRemoveBoneTrack)缺少失败时的回滚机制 - 文档注释:部分公开接口的方法缺少详细的参数说明和使用示例
- 测试覆盖:模块中没有包含自动化测试代码
3 类级代码注释详解
3.1 IAnimationEditorModule 接口
概述
IAnimationEditorModule 是 AnimationEditor 模块的公共接口,继承自 IModuleInterface、IHasMenuExtensibility、IHasToolBarExtensibility。它为外部模块提供了创建动画编辑器实例、扩展菜单和工具栏的能力。
核心设计理念:
- 工厂模式 :通过
CreateAnimationEditor()创建编辑器实例,外部无需关心具体实现 - 扩展性 :通过
FExtensibilityManager和FAnimationEditorToolbarExtender委托提供两层扩展机制 - 模块化 :只暴露必要的接口,具体实现在
FAnimationEditorModule中
CreateAnimationEditor()
cpp
virtual TSharedRef<IAnimationEditor> CreateAnimationEditor(
const EToolkitMode::Type Mode,
const TSharedPtr<IToolkitHost>& InitToolkitHost,
class UAnimationAsset* InAnimationAsset) = 0;
功能分析:
- 工厂方法,创建一个新的动画编辑器实例
Mode:工具集模式(独立窗口 / 嵌入到其他编辑器)InitToolkitHost:工具集宿主,如果嵌入到其他编辑器则传入宿主引用InAnimationAsset:要编辑的动画资产- 返回
TSharedRef<IAnimationEditor>,确保编辑器实例在调用者持有的引用释放前不会被销毁
实际实现(FAnimationEditorModule):
cpp
virtual TSharedRef<IAnimationEditor> CreateAnimationEditor(...) override
{
TSharedRef<FAnimationEditor> AnimationEditor(new FAnimationEditor());
AnimationEditor->InitAnimationEditor(Mode, InitToolkitHost, InAnimationAsset);
return AnimationEditor;
}
GetAllAnimationEditorToolbarExtenders()
cpp
DECLARE_DELEGATE_RetVal_TwoParams(
TSharedRef<FExtender>,
FAnimationEditorToolbarExtender,
const TSharedRef<FUICommandList> /*InCommandList*/,
TSharedRef<IAnimationEditor> /*InAnimationEditor*/
);
virtual TArray<FAnimationEditorToolbarExtender>& GetAllAnimationEditorToolbarExtenders() = 0;
功能分析:
- 这是 AnimationEditor 独有的扩展机制,区别于通用的
IHasToolBarExtensibility FAnimationEditorToolbarExtender委托接收命令列表和编辑器实例,返回一个FExtender- 外部模块可以注册委托,在编辑器工具栏创建时动态添加扩展
- 这个设计比通用的
FExtensibilityManager更灵活,因为扩展器可以访问编辑器实例的完整状态
使用场景:
外部模块(如游戏项目模块)可以在 StartupModule() 中注册扩展器:
cpp
IAnimationEditorModule& AnimEditorModule = FModuleManager::GetModuleChecked<IAnimationEditorModule>("AnimationEditor");
AnimEditorModule.GetAllAnimationEditorToolbarExtenders().Add(
IAnimationEditorModule::FAnimationEditorToolbarExtender::CreateRaw(this, &FMyModule::ExtendAnimationEditorToolbar)
);
3.2 IAnimationEditor 接口
概述
IAnimationEditor 是动画编辑器的核心接口,继承自 FPersonaAssetEditorToolkit 和 IHasPersonaToolkit。它定义了动画编辑器的公共 API,包括资产设置、资产浏览器访问和曲线编辑能力。
FCurveEditInfo 结构体
cpp
struct FCurveEditInfo
{
FText CurveDisplayName; // 曲线的显示名称
FLinearColor CurveColor; // 曲线颜色
FName CurveName; // 曲线名称
ERawCurveTrackTypes Type; // 曲线类型
int32 CurveIndex; // 曲线索引
FSimpleDelegate OnCurveModified; // 曲线修改回调
};
功能分析:
- 用于描述一条需要编辑的曲线的完整信息
- 支持
operator==比较,通过CurveName+Type+CurveIndex三元组判断相等 - 注意:5.3 版本中
FSmartName Name已被标记为废弃,改用FName CurveName
EditCurves()
cpp
virtual void EditCurves(
UAnimSequenceBase* InAnimSequence,
const TArray<FCurveEditInfo>& InCurveInfo,
const TSharedPtr<ITimeSliderController>& InExternalTimeSliderController) = 0;
功能分析:
- 在动画编辑器中打开曲线编辑视图
InAnimSequence:要编辑曲线的动画序列InCurveInfo:要编辑的曲线列表InExternalTimeSliderController:外部时间滑块控制器,用于同步时间轴
实现逻辑(FAnimationEditor):
- 如果曲线编辑标签页不存在,通过
FPersonaModule创建新的曲线编辑器 Widget - 将曲线标签页插入到 TabManager 中
- 遍历
InCurveInfo,逐条添加曲线到曲线编辑器 - 调用
ZoomToFit()自适应曲线范围
3.3 FAnimationEditor 类
概述
FAnimationEditor 是动画编辑器的核心实现类,继承自 IAnimationEditor、FGCObject、FTickableEditorObject。它负责整个编辑器的生命周期管理、用户交互处理和面板协调。
多继承的设计意图:
IAnimationEditor:提供编辑器接口FGCObject:需要手动管理AnimationAsset的 GC 引用(AddReferencedObjects)FTickableEditorObject:需要每帧 Tick 来刷新预览视口
3.3.1 初始化与生命周期
InitAnimationEditor()
cpp
void FAnimationEditor::InitAnimationEditor(
const EToolkitMode::Type Mode,
const TSharedPtr<IToolkitHost>& InitToolkitHost,
UAnimationAsset* InAnimationAsset)
功能分析(编辑器初始化全流程):
- 保存资产引用 :
AnimationAsset = InAnimationAsset - 注册导入/重导入回调 :监听
FReimportManager::OnPostReimport()和UImportSubsystem::OnAssetPostImport,当资产被重新导入时自动刷新编辑器 - 创建 PersonaToolkit :通过
FPersonaModule::CreatePersonaToolkit()创建 Persona 工具集,这是整个编辑器的核心基础设施 - 设置预览模式 :
SetDefaultAnimationMode(EPreviewSceneDefaultAnimationMode::Animation)将预览场景默认模式设为动画模式 - 创建 SkeletonTree :通过
ISkeletonEditorModule::CreateSkeletonTree()创建骨骼树 - 初始化编辑器框架 :调用
FAssetEditorToolkit::InitAssetEditor()初始化工具集基础框架 - 绑定命令 :
BindCommands()注册 UI 命令 - 创建应用模式 :
AddApplicationMode()添加FAnimationEditorMode,并设为当前模式 - 扩展菜单和工具栏 :
ExtendMenu()+ExtendToolbar() - 打开文档标签页 :
OpenNewAnimationDocumentTab(AnimationAsset)打开初始动画文档
析构函数
cpp
FAnimationEditor::~FAnimationEditor()
{
GEditor->GetEditorSubsystem<UImportSubsystem>()->OnAssetPostImport.RemoveAll(this);
FReimportManager::Instance()->OnPostReimport().RemoveAll(this);
if (PersonaToolkit.IsValid())
{
constexpr bool bSetPreviewMeshInAsset = false;
PersonaToolkit->SetPreviewMesh(nullptr, bSetPreviewMeshInAsset);
}
}
功能分析:
- 清理导入/重导入回调,防止悬空指针
- 将预览网格设为
nullptr,确保 PersonaToolkit 释放对预览网格的引用 - 注意:
bSetPreviewMeshInAsset = false表示不将预览网格写入资产,只是清理运行时的引用
3.3.2 Tick 机制
cpp
void FAnimationEditor::Tick(float DeltaTime)
{
if (GetPersonaToolkit()->GetMesh() && GetPersonaToolkit()->GetMesh()->IsCompiling())
{
return; // 如果骨骼网格正在编译,跳过本帧 Tick
}
GetPersonaToolkit()->GetPreviewScene()->InvalidateViews();
}
功能分析:
- 每帧调用
InvalidateViews()刷新预览视口,确保动画预览实时更新 - 聪明的优化:当骨骼网格正在编译时(
IsCompiling()),跳过视口刷新,避免编译期间的不必要计算 - 通过
FTickableEditorObject的GetTickableTickType()返回ETickableTickType::Always,确保即使在编辑器未激活时也保持 Tick
3.3.3 命令绑定
BindCommands()
cpp
void FAnimationEditor::BindCommands()
{
FAnimationEditorCommands::Register();
ToolkitCommands->MapAction(FAnimationEditorCommands::Get().ApplyCompression,
FExecuteAction::CreateSP(this, &FAnimationEditor::OnApplyCompression),
FCanExecuteAction::CreateSP(this, &FAnimationEditor::HasValidAnimationSequence));
ToolkitCommands->MapAction(FAnimationEditorCommands::Get().SetKey,
FExecuteAction::CreateSP(this, &FAnimationEditor::OnSetKey),
FCanExecuteAction::CreateSP(this, &FAnimationEditor::CanSetKey));
// ... 更多命令绑定
}
功能分析:
- 每个命令都绑定了一个
FExecuteAction(执行逻辑)和一个FCanExecuteAction(可用性检查) - 这种设计模式确保了按钮的启用/禁用状态始终与当前编辑的资产类型保持一致
- 例如,
ApplyCompression和ReimportAnimation只在编辑UAnimSequence时可用(通过HasValidAnimationSequence()检查) SetKey还需要额外检查预览网格组件是否有选中的骨骼(BonesOfInterest.Num() > 0)
3.3.4 动画操作
OnApplyCompression()
cpp
void FAnimationEditor::OnApplyCompression()
{
UAnimSequence* AnimSequence = Cast<UAnimSequence>(AnimationAsset);
if (AnimSequence)
{
TArray<TWeakObjectPtr<UAnimSequence>> AnimSequences;
AnimSequences.Add(AnimSequence);
FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked<FPersonaModule>("Persona");
PersonaModule.ApplyCompression(AnimSequences, false);
}
}
功能分析:
- 将压缩逻辑委托给
FPersonaModule::ApplyCompression() - 使用
TWeakObjectPtr传递动画序列,防止在压缩过程中资产被意外销毁 - 第二个参数
false表示不显示压缩对话框(直接使用默认设置)
OnReimportAnimation()
cpp
void FAnimationEditor::OnReimportAnimation(bool bWithDialog)
{
UAnimSequence* AnimSequence = Cast<UAnimSequence>(AnimationAsset);
if (AnimSequence)
{
FReimportManager::Instance()->ReimportAsync(AnimSequence, ...);
}
}
功能分析:
- 支持两种重导入模式:
bWithDialog=false(静默重导入)和bWithDialog=true(显示重导入对话框) - 使用
ReimportAsync异步重导入,不会阻塞编辑器 UI - 重导入完成后,通过
HandlePostReimport回调自动刷新编辑器
OnRemoveBoneTrack()
cpp
void FAnimationEditor::OnRemoveBoneTrack()
{
if (FMessageDialog::Open(EAppMsgType::YesNo, LOCTEXT(...)) == EAppReturnType::Yes)
{
UAnimSequence* AnimSequence = Cast<UAnimSequence>(AnimationAsset);
if (AnimSequence)
{
IAnimationDataController& Controller = AnimSequence->GetController();
IAnimationDataController::FScopedBracket ScopedBracket(Controller, ...);
Controller.RemoveAllBoneTracks();
Controller.RemoveAllCurvesOfType(ERawCurveTrackTypes::RCT_Transform);
}
}
}
功能分析:
- 这是一个破坏性操作,因此在执行前弹出确认对话框
- 使用
IAnimationDataController::FScopedBracket将多个操作包装为一个撤销单元 - 先移除所有骨骼轨道,再移除所有 Transform 类型的曲线
- 注意:不会移除通知(Notifies)和普通曲线
CopyCurveToSoundWave()
cpp
void FAnimationEditor::CopyCurveToSoundWave(const FAssetData& SoundWaveAssetData) const
{
USoundWave* SoundWave = Cast<USoundWave>(SoundWaveAssetData.GetAsset());
UAnimSequence* Sequence = Cast<UAnimSequence>(AnimationAsset);
// 遍历动画曲线,复制到 SoundWave 的内部曲线表
for (const FFloatCurve& FloatCurve : Sequence->GetDataModel()->GetFloatCurves())
{
FRichCurve* Curve = FindOrAddCurve(CurveTable, FloatCurve.GetName());
*Curve = FloatCurve.FloatCurve;
}
// 添加 Audio 曲线标记播放起始时间
FRichCurve* AudioCurve = FindOrAddCurve(CurveTable, "Audio");
AudioCurve->AddKey(0.f, 1.0f);
}
功能分析:
- 这是一个高级功能,将动画的浮点曲线数据复制到 SoundWave 的内部曲线表
- 常用于音频驱动的动画系统------SoundWave 中的曲线可以驱动游戏中的动画参数
- 如果 SoundWave 没有内部曲线表,会自动创建一个
- 会自动添加一个
Audio曲线,标记音频开始播放的时间点
3.3.5 编辑器刷新
ConditionalRefreshEditor()
cpp
void FAnimationEditor::ConditionalRefreshEditor(UObject* InObject)
{
bool bInterestingAsset = true;
// 检查是否与当前编辑的资产相关
if (InObject != GetPersonaToolkit()->GetSkeleton() &&
InObject != GetPersonaToolkit()->GetSkeleton()->GetPreviewMesh() &&
Cast<UAnimationAsset>(InObject) != AnimationAsset)
{
bInterestingAsset = false;
}
// 检查是否是被编辑的 Montage 的引用动画
if (UAnimMontage* Montage = Cast<UAnimMontage>(AnimationAsset))
{
for (FSlotAnimationTrack& Slot : Montage->SlotAnimTracks)
{
for (FAnimSegment& Segment : Slot.AnimTrack.AnimSegments)
{
if (Segment.GetAnimReference() == InObject)
{
bInterestingAsset = true;
break;
}
}
}
}
if (bInterestingAsset)
{
GetPersonaToolkit()->GetPreviewScene()->InvalidateViews();
OpenNewAnimationDocumentTab(Cast<UAnimationAsset>(InObject));
}
}
功能分析:
- 当资产被重新导入时,判断是否需要刷新编辑器
- "感兴趣"的资产包括:当前编辑的资产、关联的骨骼、预览网格、以及被编辑 Montage 引用的动画序列
- 如果判断为相关资产,刷新视口并重新打开文档标签页
3.4 FAnimationEditorMode 类
概述
FAnimationEditorMode 继承自 FApplicationMode,是 AnimationEditor 唯一的应用模式。它定义了编辑器的面板布局和所有可用的面板工厂。
TabLayout 布局详解:
┌──────────────────────────────────────────────────────────────┐
│ Standalone_AnimationEditor_Layout_v1.5 │
├──────────────┬───────────────────────┬───────────────────────┤
│ 左侧 (20%) │ 中间 (60%) │ 右侧 (20%) │
├──────────────┼───────────────────────┼───────────────────────┤
│ SkeletonTree │ Viewport (60%) │ Details │
│ (骨骼树) │ (视口) │ (细节面板) │
│ │ │ │
│ AssetDetails │ │ AdvancedPreview │
│ (资产详情) │ │ (高级预览设置) │
│ ├───────────────────────┼───────────────────────┤
│ │ Document / Curve / │ AssetBrowser │
│ │ FindReplace (40%) │ (资产浏览器) │
│ │ (文档/曲线/查找替换)│ │
│ │ │ AnimMontageSections │
│ │ │ (蒙太奇分段) │
│ │ │ │
│ │ │ CurveNames │
│ │ │ (曲线名称) │
│ │ │ │
│ │ │ SlotNames │
│ │ │ (插槽名称) │
└──────────────┴───────────────────────┴───────────────────────┘
注册的面板工厂:
FAnimationEditorMode 构造函数中注册了以下面板:
| 面板 | 来源模块 | 用途 |
|---|---|---|
| SkeletonTreeTab | SkeletonEditor | 骨骼层级结构树 |
| DetailsTab | Persona | 选中对象的属性详情 |
| ViewportTab | Persona | 3D 预览视口 |
| AdvancedPreviewTab | Persona | 高级预览场景设置 |
| AssetBrowserTab | Persona | 动画序列浏览器 |
| AssetDetailsTab | Persona | 资产属性详情 |
| CurveViewerTab | Persona | 动画曲线查看器 |
| SlotNamesTab | Persona | 骨骼插槽名称管理 |
| AnimNotifiesTab | Persona | 动画通知编辑 |
| AnimMontageSectionsTab | Persona | 蒙太奇分段编辑 |
| AnimAssetFindReplaceTab | Persona | 动画资产查找替换 |
可以看到,AnimationEditor 自身没有实现任何面板,全部通过 Persona 和 SkeletonEditor 模块的工厂方法创建。
3.5 FAnimationEditorCommands 类
概述
FAnimationEditorCommands 继承自 TCommands<FAnimationEditorCommands>,定义了 AnimationEditor 中所有可用的 UI 命令。
命令清单:
| 命令 | 描述 | 快捷键 | 可用条件 |
|---|---|---|---|
ImportAnimation |
导入新动画 | 无 | - |
ReimportAnimation |
重新导入当前动画 | 无 | 当前资产为 AnimSequence |
ReimportAnimationWithDialog |
带对话框重新导入 | 无 | 当前资产为 AnimSequence |
ApplyCompression |
应用压缩 | 无 | 当前资产为 AnimSequence |
ExportToFBX_AnimData |
导出动画数据到 FBX | 无 | 当前资产为 AnimSequence |
ExportToFBX_PreviewMesh |
通过预览网格导出 FBX | 无 | 当前资产为 AnimSequence |
AddLoopingInterpolation |
添加循环插值 | 无 | 当前资产为 AnimSequence |
SetKey |
设置关键帧 | S | 当前资产为 AnimSequence 且有选中骨骼 |
RemoveBoneTracks |
移除所有骨骼轨道 | 无 | 当前资产为 AnimSequence |
每个命令都通过 UI_COMMAND 宏定义,包含名称、描述和默认快捷键。CanExecute 条件在 FAnimationEditor::BindCommands() 中绑定。
3.6 UAnimationToolMenuContext 类
概述
UAnimationToolMenuContext 是一个轻量级的 UObject,用于在 UE5 的 UToolMenus 菜单系统中传递编辑器实例的引用。
cpp
UCLASS()
class ANIMATIONEDITOR_API UAnimationToolMenuContext : public UObject
{
GENERATED_BODY()
public:
TWeakPtr<IAnimationEditor> AnimationEditor;
};
功能分析:
- 持有
TWeakPtr<IAnimationEditor>,避免循环引用 - 在
FAnimationEditor::InitToolMenuContext()中被创建并添加到FToolMenuContext - 在
FAnimationEditor::GetAnimationEditor()静态方法中从FToolMenuContext中取出,用于在菜单回调中获取编辑器实例
这种设计模式在 UE5 编辑器模块中非常常见------由于 UToolMenus 的菜单回调是静态的或基于 UObject 的,需要一个上下文对象来传递编辑器实例。
3.7 FAnimationEditorModule 类
概述
FAnimationEditorModule 是 IAnimationEditorModule 的私有实现,负责模块的启动、关闭和编辑器实例的创建。
cpp
class FAnimationEditorModule : public IAnimationEditorModule
{
virtual void StartupModule() override
{
MenuExtensibilityManager = MakeShareable(new FExtensibilityManager);
ToolBarExtensibilityManager = MakeShareable(new FExtensibilityManager);
}
virtual void ShutdownModule() override
{
MenuExtensibilityManager.Reset();
ToolBarExtensibilityManager.Reset();
}
// ...
};
功能分析:
StartupModule():创建菜单和工具栏的扩展性管理器ShutdownModule():清理扩展性管理器,释放资源CreateAnimationEditor():创建FAnimationEditor实例并调用InitAnimationEditor()- 最后通过
IMPLEMENT_MODULE(FAnimationEditorModule, AnimationEditor)宏注册模块
4 功能使用示例编写
示例1:自定义动画编辑器工具栏扩展
假设我们的项目需要在动画编辑器的工具栏中添加一个"导出动画统计信息"的按钮。
cpp
// MyAnimationEditorExtensions.h
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
class FMyAnimationEditorExtensions : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
private:
// 工具栏扩展器
TSharedRef<FExtender> ExtendAnimationEditorToolbar(
const TSharedRef<FUICommandList> InCommandList,
TSharedRef<IAnimationEditor> InAnimationEditor);
// 导出统计信息的处理函数
void OnExportAnimationStats(TSharedRef<IAnimationEditor> AnimationEditor);
// 自定义命令列表
TSharedPtr<FUICommandList> CommandList;
};
// MyAnimationEditorCommands.h
#pragma once
#include "Framework/Commands/Commands.h"
class FMyAnimationEditorCommands : public TCommands<FMyAnimationEditorCommands>
{
public:
FMyAnimationEditorCommands()
: TCommands<FMyAnimationEditorCommands>(
TEXT("MyAnimationEditor"),
NSLOCTEXT("Contexts", "MyAnimationEditor", "My Animation Editor"),
NAME_None,
FAppStyle::GetAppStyleSetName())
{}
virtual void RegisterCommands() override;
TSharedPtr<FUICommandInfo> ExportAnimationStats;
};
cpp
// MyAnimationEditorExtensions.cpp
#include "MyAnimationEditorExtensions.h"
#include "IAnimationEditorModule.h"
#include "Animation/AnimSequence.h"
#include "ToolMenus.h"
void FMyAnimationEditorCommands::RegisterCommands()
{
UI_COMMAND(ExportAnimationStats, "导出动画统计", "导出当前动画的统计信息到CSV",
EUserInterfaceActionType::Button, FInputChord());
}
void FMyAnimationEditorExtensions::StartupModule()
{
FMyAnimationEditorCommands::Register();
CommandList = MakeShareable(new FUICommandList);
// 注册工具栏扩展器
IAnimationEditorModule& AnimEditorModule =
FModuleManager::GetModuleChecked<IAnimationEditorModule>("AnimationEditor");
AnimEditorModule.GetAllAnimationEditorToolbarExtenders().Add(
IAnimationEditorModule::FAnimationEditorToolbarExtender::CreateRaw(
this, &FMyAnimationEditorExtensions::ExtendAnimationEditorToolbar));
}
TSharedRef<FExtender> FMyAnimationEditorExtensions::ExtendAnimationEditorToolbar(
const TSharedRef<FUICommandList> InCommandList,
TSharedRef<IAnimationEditor> InAnimationEditor)
{
TSharedRef<FExtender> Extender = MakeShareable(new FExtender);
// 绑定命令
CommandList->MapAction(
FMyAnimationEditorCommands::Get().ExportAnimationStats,
FExecuteAction::CreateStatic(&FMyAnimationEditorExtensions::OnExportAnimationStats, InAnimationEditor)
);
// 在 Animation 段之后添加按钮
Extender->AddToolBarExtension(
"Animation",
EExtensionHook::After,
CommandList,
FToolBarExtensionDelegate::CreateLambda([this](FToolBarBuilder& ToolbarBuilder)
{
ToolbarBuilder.AddToolBarButton(FMyAnimationEditorCommands::Get().ExportAnimationStats);
})
);
return Extender;
}
void FMyAnimationEditorExtensions::OnExportAnimationStats(TSharedRef<IAnimationEditor> AnimationEditor)
{
// 通过 PersonaToolkit 获取当前编辑的动画资产
// 具体实现取决于项目需求
}
示例2:通过资产工具打开动画编辑器
cpp
// 在自定义的 AssetTools 模块中
void FMyAssetTools::OpenAnimationEditor(UAnimationAsset* AnimationAsset)
{
if (AnimationAsset)
{
IAnimationEditorModule& AnimEditorModule =
FModuleManager::GetModuleChecked<IAnimationEditorModule>("AnimationEditor");
TSharedRef<IAnimationEditor> Editor = AnimEditorModule.CreateAnimationEditor(
EToolkitMode::Standalone,
nullptr, // 独立窗口
AnimationAsset
);
}
}
示例3:监听动画编辑器事件
cpp
// 在项目模块中监听动画编辑器的创建
void FMyGameEditorModule::StartupModule()
{
// 注册 Persona 模块的委托
FPersonaModule& PersonaModule =
FModuleManager::GetModuleChecked<FPersonaModule>("Persona");
PersonaModule.OnRegisterTabs().AddRaw(this, &FMyGameEditorModule::OnRegisterAnimationEditorTabs);
PersonaModule.OnRegisterLayoutExtensions().AddRaw(
this, &FMyGameEditorModule::OnRegisterAnimationEditorLayoutExtensions);
}
void FMyGameEditorModule::OnRegisterAnimationEditorTabs(
FWorkflowAllowedTabSet& TabFactories,
TSharedRef<FWorkflowCentricApplication> InApplication)
{
// 检查是否是动画编辑器
if (InApplication->GetToolkitFName() == FName("AnimationEditor"))
{
// 注册自定义面板
// TabFactories.RegisterFactory(...);
}
}
5 如何扩展 AnimationEditor
5.1 扩展工具栏
AnimationEditor 提供了两种工具栏扩展方式:
方式一:通用扩展管理器(IHasToolBarExtensibility)
cpp
IAnimationEditorModule& Module = FModuleManager::GetModuleChecked<IAnimationEditorModule>("AnimationEditor");
TSharedPtr<FExtensibilityManager> ExtMgr = Module.GetToolBarExtensibilityManager();
ExtMgr->AddExtender(MyExtender);
方式二:专用工具栏扩展器委托(推荐)
cpp
Module.GetAllAnimationEditorToolbarExtenders().Add(
IAnimationEditorModule::FAnimationEditorToolbarExtender::CreateLambda(
[](const TSharedRef<FUICommandList>& Commands, TSharedRef<IAnimationEditor> Editor) -> TSharedRef<FExtender>
{
TSharedRef<FExtender> Extender = MakeShareable(new FExtender);
// 添加自定义扩展...
return Extender;
}
)
);
方式二的优点是可以直接访问编辑器实例,获取当前编辑的资产、骨骼等信息。
5.2 扩展菜单
通过 GetMenuExtensibilityManager() 获取菜单扩展管理器:
cpp
IAnimationEditorModule& Module = FModuleManager::GetModuleChecked<IAnimationEditorModule>("AnimationEditor");
Module.GetMenuExtensibilityManager()->AddExtender(MyMenuExtender);
5.3 注册自定义面板
通过 PersonaModule.OnRegisterTabs() 委托,在动画编辑器创建时注册自定义面板:
cpp
FPersonaModule& PersonaModule = FModuleManager::GetModuleChecked<FPersonaModule>("Persona");
PersonaModule.OnRegisterTabs().AddLambda(
[](FWorkflowAllowedTabSet& TabFactories, TSharedRef<FWorkflowCentricApplication> InApplication)
{
if (InApplication->GetToolkitFName() == FName("AnimationEditor"))
{
// 注册自定义面板工厂
TabFactories.RegisterFactory(MyCustomTabFactory);
}
}
);
5.4 扩展布局
通过 PersonaModule.OnRegisterLayoutExtensions() 委托,在动画编辑器布局中插入自定义面板位置:
cpp
PersonaModule.OnRegisterLayoutExtensions().AddLambda(
[](FLayoutExtender& Extender)
{
// 在 AssetBrowser 堆栈中插入自定义面板
Extender.ExtendLayout(
AnimationEditorTabs::AssetBrowserTab,
ELayoutExtensionPosition::After,
FTabManager::FTab(MyCustomTabName, ETabState::ClosedTab)
);
}
);
6 总结与最佳实践
核心要点
-
AnimationEditor 是组装者,不是实现者
- 它不实现任何面板,而是通过 Persona 模块的工厂方法创建面板
- 它的核心职责是:定义布局、协调面板、处理动画操作
-
理解 Persona 框架是理解 AnimationEditor 的前提
IPersonaToolkit是整个编辑器的核心基础设施FPersonaModule提供了几乎所有面板的工厂方法FWorkflowCentricApplication提供了应用模式和 Tab 管理
-
扩展机制是设计亮点
- 两层工具栏扩展:通用
FExtensibilityManager+ 专用FAnimationEditorToolbarExtender - 运行时面板注册:
OnRegisterTabs()+OnRegisterLayoutExtensions() - 外部模块可以完全自定义动画编辑器的工具栏、菜单和面板布局
- 两层工具栏扩展:通用
-
命令绑定的可用性检查
- 每个命令都绑定了
CanExecute条件,根据当前编辑的资产类型动态启用/禁用 - 这是一个好的实践,避免了用户在不合适的上下文中执行操作
- 每个命令都绑定了
-
生命周期管理
- 通过
FGCObject手动管理AnimationAsset的 GC 引用 - 通过
FTickableEditorObject每帧刷新预览视口 - 在析构函数中清理所有回调,防止悬空指针
- 通过
最佳实践
-
扩展 AnimationEditor 时优先使用专用扩展器委托
FAnimationEditorToolbarExtender比通用FExtensibilityManager更灵活- 可以直接访问编辑器实例和命令列表
-
利用 Persona 模块的委托进行深度扩展
OnRegisterTabs()注册自定义面板OnRegisterLayoutExtensions()调整布局- 不需要修改 AnimationEditor 的一行代码
-
在重导入回调中正确处理编辑器刷新
- 参考
ConditionalRefreshEditor()的逻辑 - 判断资产是否与当前编辑器相关,避免不必要的刷新
- 参考
-
使用
IAnimationDataController进行动画数据修改- 不要直接修改动画数据,而是通过
Controller接口 - 使用
FScopedBracket将多个操作包装为一个撤销单元
- 不要直接修改动画数据,而是通过
-
为自定义扩展正确管理命令的生命周期
- 在模块
StartupModule()中注册命令 - 在模块
ShutdownModule()中清理
- 在模块
常见问题解决
问题1:扩展的工具栏按钮不显示
- 检查命令是否已注册(
RegisterCommands()) - 检查
CanExecute条件是否返回true - 确认扩展钩子位置(
EExtensionHook::After等)是否正确
问题2:自定义面板在动画编辑器中不出现
- 确认
OnRegisterTabs()回调中正确过滤了ToolkitFName - 检查
TabLayout中是否为自定义面板预留了位置(通过OnRegisterLayoutExtensions()扩展)
问题3:重导入后编辑器没有自动刷新
- 确认
HandlePostReimport回调已正确绑定 - 检查
ConditionalRefreshEditor中的资产关联判断逻辑
附录
A. 相关资源
- Persona 模块源码:
Engine/Source/Editor/Persona - SkeletonEditor 模块源码:
Engine/Source/Editor/SkeletonEditor - AnimGraph 模块源码:
Engine/Source/Editor/AnimGraph - UE5 官方文档:Animation Editor
- UE5 官方文档:Workflow-Centric Applications
B. 文件结构参考
AnimationEditor/
├── AnimationEditor.Build.cs
├── Public/
│ ├── IAnimationEditor.h // 编辑器接口
│ ├── IAnimationEditorModule.h // 模块接口
│ └── AnimationToolMenuContext.h // 菜单上下文
└── Private/
├── AnimationEditor.h // 编辑器实现(头文件)
├── AnimationEditor.cpp // 编辑器实现(源文件)
├── AnimationEditorMode.h // 应用模式
├── AnimationEditorMode.cpp
├── AnimationEditorCommands.h // 命令定义
├── AnimationEditorCommands.cpp
└── AnimationEditorModule.cpp // 模块实现