AnimationSettings 模块全面系统分析
源文档:https://gitee.com/chouchouxia/share-ue5
目录
1 模块概述
1.1 基本信息
| 属性 | 值 |
|---|---|
| 模块名称 | AnimationSettings |
| 类型 | Editor(编辑器模块) |
| 位置 | Engine/Source/Editor/AnimationSettings/ |
| 描述 | 为动画系统提供编辑器侧的配置监听与自动维护能力 |
这个模块的体量非常小------仅有一个 .h 文件、一个 .cpp 文件和一个 .Build.cs 文件。代码量不足 100 行,但它在 UE 动画编辑器工作流中扮演了关键的"守护者"角色。
在 AnimationSettingsModule.h 中只定义了一个类:FAnimationSettingsModule。该模块依赖了 Core、CoreUObject、AssetRegistry、Engine、SharedSettingsWidgets 共 5 个模块。核心依赖是:IAssetRegistry(资产注册表)、UAnimationSettings(动画全局配置)、UUserDefinedStruct(用户自定义结构体)。
1.2 这个模块到底做了什么?
很多人在刚接触 AnimationSettings 模块时可能会感到困惑------它不像 Persona 那样提供编辑界面,也不像 AnimGraph 那样负责动画蓝图节点,那它存在的意义到底是什么?
答案可以概括为一句话:它负责监听编辑器中资产的重命名操作,并自动把 UAnimationSettings 里的 UserDefinedStructAttributes 引用同步更新到最新名称。
具体来说:
- 当美术或策划在内容浏览器中重命名 一个
UUserDefinedStruct(用户自定义结构体)时,如果该结构体被配置在"项目设置 → 动画 → 用户自定义结构体动画属性"列表中,FAnimationSettingsModule会自动感知到这次重命名,并刷新UAnimationSettings中对应的软引用,然后把变更写回DefaultEngine.ini配置文件。
这就避免了一个非常尴尬的场景:重命名了一个自定义结构体资产后,项目设置里的引用变成了悬空指针,导致动画属性注册静默失败,排查起来费时费力。
2 模块整体架构解析
虽然这个模块非常精简,但它的架构设计仍然值得拆解。我们可以从几个维度来理解它。
2.1 架构图
┌─────────────────────────────────────────────────────────────────────┐
│ 编辑器运行时 (Editor Runtime) │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 内容浏览器 (Content Browser) │ │
│ │ 用户右击资产 → 重命名 → 触发 FAssetData 变更 │ │
│ └──────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ IAssetRegistry (资产注册表) │ │
│ │ OnAssetRenamed 委托广播 │ │
│ └──────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ FAnimationSettingsModule (核心模块) │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ FAnimationSettingsModule::OnAssetRenamed() │ │ │
│ │ │ - 判断资产是否为 UUserDefinedStruct │ │ │
│ │ │ - 遍历 UAnimationSettings::UserDefinedStructAttributes │ │ │
│ │ │ - 更新匹配的软引用 │ │ │
│ │ │ - 处理源控签出 │ │ │
│ │ │ - 调用 TryUpdateDefaultConfigFile() 写回 ini 文件 │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ UAnimationSettings (DeveloperSettings) │ │
│ │ - UserDefinedStructAttributes: TArray<TSoftObjectPtr<...>> │ │
│ │ - 持久化到 DefaultEngine.ini │ │
│ │ - 运行时用于注册非可混合动画属性类型 │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
2.2 模块间依赖关系
如果上面的架构图看起来还有些抽象,可以参考下面这张依赖关系图:
FAnimationSettingsModule (监听器)
│
├──→ IAssetRegistry (事件源)
│ └── OnAssetRenamed 多播委托
│
├──→ UAnimationSettings (配置目标)
│ └── UserDefinedStructAttributes
│
├──→ UUserDefinedStruct (资产类型判断)
│
└──→ SettingsHelpers (源控与文件操作)
├── CheckOutOrAddFile()
└── MakeWritable()
可以看到,FAnimationSettingsModule 本身不包含任何业务逻辑------它是一个纯粹的"桥接"模块,把三个独立系统串在了一起:
- IAssetRegistry:告诉我们"哪个资产被重命名了"
- UAnimationSettings:告诉我们"哪些资产需要跟踪引用"
- SettingsHelpers:告诉我们"怎么安全地把配置写回磁盘"
这种设计的好处是:监听逻辑和业务配置完全解耦。如果以后需要在 UAnimationSettings 中新增更多需要自动维护引用的属性,只需要在 OnAssetRenamed 里追加处理分支即可,框架本身不需要任何改动。
2.3 模块划分
虽然只有一个类,但可以从职责角度进行划分:
1. 事件监听层(StartupModule / ShutdownModule)
- 职责:在模块启动时向
AssetRegistry注册回调,在模块关闭时解注册 - 特点:使用
AddStatic注册静态回调,避免了对象生命周期管理问题
2. 事件处理层(OnAssetRenamed)
- 职责:判断重命名的资产类型、查找匹配项、更新引用
- 特点:纯静态函数,无状态依赖
3. 持久化层(内嵌逻辑)
- 职责:处理源控签出、使文件可写、写回配置文件
- 特点:考虑了源控环境、新文件创建等边界情况
2.4 核心技术栈
| 技术/类 | 用途 |
|---|---|
IModuleInterface |
模块生命周期管理 |
IAssetRegistry |
资产注册表,提供资产重命名事件 |
FAssetData |
资产元数据包装 |
UAnimationSettings |
继承自 UDeveloperSettings 的全局动画配置 |
UUserDefinedStruct |
用户自定义结构体资产 |
TSoftObjectPtr |
软对象指针,避免强引用导致的内存驻留 |
FPlatformFileManager |
平台文件系统访问 |
SettingsHelpers |
引擎设置辅助工具(源控签出、文件权限) |
FDelegateHandle |
委托句柄,用于取消注册 |
3 核心类代码详解
3.1 FAnimationSettingsModule 类
3.1.1 概述
FAnimationSettingsModule 是整个模块唯一的功能类,实现了 IModuleInterface 接口。它的核心职责是当内容浏览器中的 UUserDefinedStruct 资产被重命名时,自动同步 UAnimationSettings 中的引用。
核心设计理念:
- 轻量级:仅两个方法 + 一个静态回调 + 一个成员变量
- 按需触发:不轮询、不 Tick,完全由事件驱动
- 无副作用的旁路逻辑:不修改资产本身,只更新配置引用
3.1.2 StartupModule / ShutdownModule
cpp
void FAnimationSettingsModule::StartupModule()
{
const FAssetRegistryModule& AssetRegistryModule =
FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
OnAssetRenamedHandle = AssetRegistryModule.Get().OnAssetRenamed()
.AddStatic(&FAnimationSettingsModule::OnAssetRenamed);
}
void FAnimationSettingsModule::ShutdownModule()
{
if (const FAssetRegistryModule* AssetRegistryModule =
FModuleManager::GetModulePtr<FAssetRegistryModule>(TEXT("AssetRegistry")))
{
if (IAssetRegistry* AssetRegistry = AssetRegistryModule->TryGet())
{
AssetRegistry->OnAssetRenamed().Remove(OnAssetRenamedHandle);
}
}
}
功能分析:
-
StartupModule()在编辑器启动时被调用,通过FModuleManager加载AssetRegistry模块,然后使用AddStatic将自己注册为资产重命名事件的一个监听者。这里用AddStatic而非AddRaw或AddSP,是因为OnAssetRenamed被声明为static成员函数------它不依赖模块实例的状态,解注册时也不需要担心this指针失效的问题。 -
ShutdownModule()是注册的逆操作。注意这里的防御性写法 :先用GetModulePtr检查模块指针是否还活着,再用TryGet检查IAssetRegistry接口是否还可用。之所以这么做,是因为编辑器关闭时模块卸载顺序是不确定的------AssetRegistry可能比AnimationSettings先卸载,如果直接解引用才会崩溃。 -
OnAssetRenamedHandle(FDelegateHandle)是模块唯一的成员变量,用于在关闭时精确移除之前绑定的委托。
3.1.3 OnAssetRenamed(核心逻辑)
这是整个模块的灵魂所在。让我们分段来读:
cpp
void FAnimationSettingsModule::OnAssetRenamed(const FAssetData& InAssetData, const FString& InOldName)
{
if (UUserDefinedStruct* UserDefinedStruct = Cast<UUserDefinedStruct>(InAssetData.GetAsset()))
{
第一步:类型过滤。 通过 Cast 判断被重命名的资产是不是 UUserDefinedStruct。如果不是(比如重命名的是一张贴图、一个蓝图),直接跳过,零开销返回。这里使用 GetAsset() 会触发一次同步加载,但由于用户重命名操作本身就是低频操作,这个开销完全可以接受。
cpp
if (UAnimationSettings* Settings = UAnimationSettings::Get())
{
TArray<int32> Indices;
for (int32 Index = 0; Index < Settings->UserDefinedStructAttributes.Num(); Index++)
{
const TSoftObjectPtr<UUserDefinedStruct>& StructPtr =
Settings->UserDefinedStructAttributes[Index];
UUserDefinedStruct* Struct = StructPtr.Get();
if (Struct == UserDefinedStruct)
{
Indices.Add(Index);
}
}
第二步:收集匹配项。 遍历 UAnimationSettings::UserDefinedStructAttributes 数组,找出所有指向"旧身份对象"的索引。这里有一个值得注意的细节------TSoftObjectPtr::Get() 在对象已加载时返回裸指针,而 InAssetData.GetAsset() 已经在前面加载了这个对象,所以这里的 StructPtr.Get() 和 UserDefinedStruct 做指针比较是安全和有效的。
另外注意 Indices 是一个数组而非单个索引------这意味着同一个 UUserDefinedStruct 可能被配置了多次(虽然实际场景中很少见),代码对此做了容错处理。
cpp
if (Indices.Num() > 0)
{
for (int32 Index : Indices)
{
Settings->UserDefinedStructAttributes[Index] = UserDefinedStruct;
}
第三步:更新引用。 直接把旧的 TSoftObjectPtr 替换为指向"新身份对象"的 TSoftObjectPtr。这看起来像是一个无操作------前后对象指针相同,为什么还需要替换?答案在于 TSoftObjectPtr 内部存储的是路径字符串 。资产重命名后,旧路径失效了,需要把 TSoftObjectPtr 的内部路径更新为新路径。直接把新对象赋值给 TSoftObjectPtr,UE 的赋值运算符会自动从对象身上获取最新的路径。
cpp
FString RelativePath;
bool bIsSourceControlled = false;
bool bIsNewFile = false;
check(Settings->GetClass()->HasAnyClassFlags(CLASS_DefaultConfig));
// Attempt to checkout the file automatically
RelativePath = Settings->GetDefaultConfigFilename();
bIsSourceControlled = true;
FString FullPath = FPaths::ConvertRelativePathToFull(RelativePath);
if (!FPlatformFileManager::Get().GetPlatformFile().FileExists(*FullPath))
{
bIsNewFile = true;
}
if (!bIsSourceControlled || !SettingsHelpers::CheckOutOrAddFile(FullPath))
{
SettingsHelpers::MakeWritable(FullPath);
}
Settings->TryUpdateDefaultConfigFile();
if (bIsNewFile && bIsSourceControlled)
{
SettingsHelpers::CheckOutOrAddFile(FullPath);
}
第四步:持久化配置。 这是最复杂的部分,涉及到配置文件的源控管理:
-
定位配置文件 :通过
GetDefaultConfigFilename()获取DefaultEngine.ini的路径(UAnimationSettings被标记为config=Engine,所以配置存在引擎的默认配置文件中)。 -
检查文件是否存在 :如果是新创建的配置文件(第一次保存),标记为
bIsNewFile。 -
源控签出 :调用
SettingsHelpers::CheckOutOrAddFile()尝试从 Perforce/SVN 签出文件。如果签出失败(比如不在源控环境下),则退化为MakeWritable()去掉只读属性。 -
写回配置 :调用
TryUpdateDefaultConfigFile()把UAnimationSettings的当前内存状态序列化回.ini文件。 -
二次源控处理 :如果文件是新创建的且在源控环境下,在文件写入后再做一次
CheckOutOrAddFile(add操作需要在文件存在后才能执行)。
这个流程虽然看似冗长,但它保证了在多人协作的源控环境中,配置文件变更不会被静默丢弃。
3.1.4 设计亮点
- 事件驱动,零轮询开销:不在 Tick 里做任何检查,只在真正发生重命名时才触发逻辑
- 防御性关闭 :
ShutdownModule里用了GetModulePtr+TryGet两层保护,避免卸载顺序问题 - 静态回调 :
OnAssetRenamed是静态函数,不依赖this,避免了模块实例已被销毁但回调仍被触发的悬空指针风险 - 源控友好:自动处理签出、权限、新文件创建等边界情况
3.2 UAnimationSettings 配置类
虽然 UAnimationSettings 本身定义在 Engine 运行时模块而非 AnimationSettings 编辑器模块中,但它是本模块的"被服务对象",有必要了解其结构。
3.2.1 类层次
UObject
└── UDeveloperSettings
└── UAnimationSettings
UAnimationSettings 继承自 UDeveloperSettings,这意味着它会自动出现在 UE 编辑器的 Project Settings(项目设置) 面板中。UDeveloperSettings 提供了一个 SectionName 字段用于分组,UAnimationSettings 在构造函数中将其设置为 "Animation",所以配置项在项目设置中归类于 Engine → Animation 分组。
3.2.2 配置属性分类
| 分类 | 属性 | 作用 |
|---|---|---|
| Compression | CompressCommandletVersion |
压缩版本号,递增可触发全量重压缩 |
KeyEndEffectorsMatchNameArray |
需要高精度压缩的骨骼名称模式(如 IK、手、武器) | |
ForceRecompression |
是否强制重压缩所有动画 | |
bForceBelowThreshold |
是否在误差超过阈值时允许使用更大但更精确的压缩方式 | |
bFirstRecompressUsingCurrentOrDefault |
是否优先使用当前压缩器重压缩 | |
| Performance | bEnablePerformanceLog |
是否输出压缩性能日志 |
bStripAnimationDataOnDedicatedServer |
是否在 DS 上剥离动画轨道数据 | |
bTickAnimationOnSkeletalMeshInit |
是否在骨骼网格初始化时执行动画 Tick | |
| Animation Attributes | BoneTimecodeCustomAttributeNameSettings |
时间码动画属性名称配置 |
BoneCustomAttributesNames |
直接导入到骨骼的自定义属性名称列表 | |
BoneNamesWithCustomAttributes |
全部动画属性都直接导入的骨骼名称列表 | |
AttributeBlendModes |
按名称指定的动画属性混合模式 | |
DefaultAttributeBlendMode |
默认动画属性混合模式 | |
TransformAttributeNames |
FBX 节点变换曲线导入为属性的名称匹配规则 | |
UserDefinedStructAttributes |
用户自定义结构体动画属性列表(本模块监听的核心目标) | |
| Mirroring | MirrorFindReplaceExpressions |
镜像骨骼的查找替换表达式(如 l_ ↔ r_) |
| Animation Data | DefaultFrameRate |
项目默认帧率(默认 30fps) |
bEnforceSupportedFrameRates |
是否强制动画资产只能使用受支持的帧率 |
3.2.3 UserDefinedStructAttributes 的运行时意义
cpp
UPROPERTY(config, EditAnywhere, Category = AnimationAttributes,
meta=(AllowedClasses="/Script/Engine.UserDefinedStruct"))
TArray<TSoftObjectPtr<UUserDefinedStruct>> UserDefinedStructAttributes;
这个属性允许在项目设置中注册一组 UUserDefinedStruct,使其成为动画系统的非可混合(Non-blendable)属性类型。
当 PostEditChangeProperty 检测到这个数组发生变化时,会调用:
cpp
UE::Anim::AttributeTypes::RegisterNonBlendableType(UserDefinedStruct.Get());
将其注册到 FAttributeTypes 全局注册表中。动画蓝图、动画节点等在计算混合时会查询这个注册表------被标记为 Non-blendable 的类型不会参与混合计算,而是取最高权重的值。
这就是为什么 FAnimationSettingsModule 需要监听重命名------如果这个引用断掉了,结构体类型就不会被正确注册,运行时行为会静默出错。
4 数据流与运行时行为分析
4.1 正常流程:资产重命名
1. 用户在内容浏览器中右击一个 UUserDefinedStruct 资产,选择"重命名"
↓
2. AssetRegistry 处理重命名,更新内部索引
↓
3. AssetRegistry 广播 OnAssetRenamed 多播委托
↓
4. FAnimationSettingsModule::OnAssetRenamed 被调用
├── 4a. Cast<UUserDefinedStruct> ------ 不是目标类型 → 直接返回
└── 4b. 是目标类型 → 继续
↓
5. 获取 UAnimationSettings 单例
↓
6. 遍历 UserDefinedStructAttributes 数组,收集匹配索引
↓
7. 更新所有匹配的 TSoftObjectPtr 引用
↓
8. 定位 DefaultEngine.ini 文件路径
↓
9. 源控签出 / 去掉只读属性
↓
10. TryUpdateDefaultConfigFile() 写回配置
↓
11. 完成 ------ 配置文件中的引用已同步为新名称
4.2 模块加载/卸载流程
编辑器启动
│
▼
FAnimationSettingsModule::StartupModule()
│
├─→ 加载 AssetRegistry 模块
│
└─→ 注册 OnAssetRenamed 静态回调
│
│ ... 编辑器运行中 ...
│
▼
编辑器关闭 / 模块热重载
│
▼
FAnimationSettingsModule::ShutdownModule()
│
├─→ 获取 AssetRegistryModule 指针(防御性检查)
│
├─→ 获取 IAssetRegistry 接口(防御性检查)
│
└─→ 移除 OnAssetRenamedHandle 委托
4.3 边界情况处理
| 场景 | 处理方式 |
|---|---|
重命名的资产不是 UUserDefinedStruct |
Cast 失败,直接返回,零额外开销 |
UAnimationSettings 获取失败 |
if 保护,不做任何操作 |
资产不在 UserDefinedStructAttributes 中 |
Indices.Num() == 0,外层 if 拦截 |
| 配置文件在源控下 | CheckOutOrAddFile 自动签出 |
| 配置文件是只读的 | MakeWritable 去掉只读属性 |
| 配置文件是新创建的 | 先写入再补做 add 到源控 |
| AssetRegistry 模块先于本模块卸载 | GetModulePtr 返回空,ShutdownModule 安全跳过 |
5 功能使用示例编写
示例1:在项目中使用用户自定义结构体作为动画属性
首先,在内容浏览器中创建一个 UserDefinedStruct(比如叫 FMyAnimAttribute),给它添加几个成员变量:
内容浏览器 → 右键 → Miscellaneous → User Defined Struct
命名为: FAttackAttribute
成员: float DamageMultiplier, FVector HitDirection, bool bIsCharged
然后在项目设置中注册这个结构体:
Edit → Project Settings → Engine → Animation →
User Defined Struct Animation Attributes → 添加 FAttackAttribute
此时,FAnimationSettingsModule 已经开始工作了------如果后续在内容浏览器中把 FAttackAttribute 重命名为 FCombatAttribute,模块会自动更新项目设置中的引用。
示例2:在动画蓝图中读取自定义属性
cpp
// 在 AnimInstance 子类中
void UMyAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
Super::NativeUpdateAnimation(DeltaSeconds);
// 从动画曲线/属性系统中读取自定义属性
if (const FAttackAttribute* Attr = GetAttackingAnimAttribute())
{
CurrentDamageMultiplier = Attr->DamageMultiplier;
CurrentHitDirection = Attr->HitDirection;
bIsChargedAttack = Attr->bIsCharged;
}
}
示例3:在 C++ 中访问动画设置
cpp
// 在编辑器中获取动画全局设置
#if WITH_EDITOR
void UMyAnimationTool::CheckCompressionSettings()
{
UAnimationSettings* AnimSettings = UAnimationSettings::Get();
if (AnimSettings)
{
UE_LOG(LogTemp, Log, TEXT("当前压缩版本: %d"), AnimSettings->CompressCommandletVersion);
UE_LOG(LogTemp, Log, TEXT("默认帧率: %.2f"), AnimSettings->GetDefaultFrameRate().AsDecimal());
UE_LOG(LogTemp, Log, TEXT("高精度骨骼模式数量: %d"), AnimSettings->KeyEndEffectorsMatchNameArray.Num());
}
}
#endif
示例4:监听动画设置变更(编辑器工具开发)
cpp
// 在编辑器工具中监听 UAnimationSettings 的变更
#if WITH_EDITOR
void UMyEditorTool::Initialize()
{
UAnimationSettings* Settings = UAnimationSettings::Get();
if (Settings)
{
Settings->OnSettingChanged().AddUObject(this, &UMyEditorTool::OnAnimationSettingsChanged);
}
}
void UMyEditorTool::OnAnimationSettingsChanged(UObject* Settings, const FPropertyChangedEvent& Event)
{
const FName PropertyName = Event.GetPropertyName();
if (PropertyName == GET_MEMBER_NAME_CHECKED(UAnimationSettings, DefaultFrameRate))
{
// 默认帧率变更,刷新相关 UI 或重新计算
RefreshFrameRateDependentData();
}
}
#endif
6 总结与最佳实践
6.1 核心要点
-
定位清晰,职责单一
FAnimationSettingsModule不做动画计算、不提供 UI,只做一件事:保持UAnimationSettings::UserDefinedStructAttributes的引用有效性- 这种"小而精"的模块设计是 UE 编辑器的惯用模式------每个模块解决一个明确的痛点
-
事件驱动优于轮询
- 通过
IAssetRegistry::OnAssetRenamed委托获取精确的变更通知 - 不需要在 Tick 中遍历所有资产做 diff 对比
- 零 CPU 开销,只在真正发生重命名时才执行
- 通过
-
源控友好是编辑器模块的基本素养
- 自动处理 Perforce/SVN 签出
- 降级处理只读文件
- 正确处理新文件创建场景
-
防御性编程贯穿始终
StartupModule使用LoadModuleChecked(假设 AssetRegistry 一定存在)ShutdownModule使用GetModulePtr+TryGet(假设它可能已经卸载)- 所有指针访问都有有效性检查
6.2 最佳实践
-
如果需要在项目设置中新增类似"资产引用"配置项
- 可以参考
FAnimationSettingsModule的模式:写一个轻量编辑器模块,监听AssetRegistry的OnAssetRenamed事件 - 在回调中遍历需要维护的引用数组,更新匹配的
TSoftObjectPtr - 务必处理配置文件写回和源控签出
- 可以参考
-
开发编辑器工具时的模块加载/卸载规范
StartupModule中注册的委托,必须在ShutdownModule中显式解注册- 解注册时考虑模块卸载顺序,用防御式写法
- 静态回调比成员函数回调更安全(不依赖
this生命周期)
-
UDeveloperSettings 的使用建议
- 全局配置类继承
UDeveloperSettings,可以自动在项目设置面板中展示 - 在构造函数中设置
SectionName来决定分类 - 需要持久化引用的属性使用
TSoftObjectPtr而非硬引用,避免强依赖
- 全局配置类继承
-
理解 UAnimationSettings 各属性的影响范围
CompressCommandletVersion:递增后会触发命令行重压缩所有动画bStripAnimationDataOnDedicatedServer:打开后 DS 不包含动画数据,可显著减少 DS 内存和包体大小DefaultFrameRate:影响所有新建动画资产的默认帧率
6.3 常见问题
问题1:重命名了结构体后,动画属性注册失效
- 确认
AnimationSettings模块是否正常加载(检查日志是否有相关报错) - 手动检查
DefaultEngine.ini中[/Script/Engine.AnimationSettings]段落的UserDefinedStructAttributes路径是否正确 - 如果配置文件处于源控锁定状态且签出失败,引用不会更新
问题2:哪些操作会触发 OnAssetRenamed?
- 内容浏览器中直接重命名
- 通过资产工具批量重命名
- 蓝图编译触发的重定向器处理也可能间接触发
- 注意:移动资产(Move/Rename 同时发生)也会触发,且行为相同
问题3:模块似乎没有对重定向器(Redirector)做特殊处理?
- 实际上,
InAssetData.GetAsset()会自动遵循重定向器加载目标资产,所以不需要显式处理
附录:文件结构参考
AnimationSettings/
├── AnimationSettings.Build.cs # 模块构建配置
├── Public/
│ └── AnimationSettingsModule.h # 模块接口声明
└── Private/
└── AnimationSettingsModule.cpp # 模块实现
相关文件
| 文件 | 路径 |
|---|---|
UAnimationSettings 声明 |
Engine/Source/Runtime/Engine/Classes/Animation/AnimationSettings.h |
UAnimationSettings 实现 |
Engine/Source/Runtime/Engine/Private/Animation/AnimationSettings.cpp |
CustomAttributes |
Engine/Source/Runtime/Engine/Classes/Animation/CustomAttributes.h |
AttributeTypes |
Engine/Source/Runtime/Engine/Public/Animation/AttributeTypes.h |