前言 随着虚幻引擎5的推出,资产管理系统迎来了重大更新——引入了全新的AssetDefinition
系统,取代了旧版中的FAssetTypeActions_Base
。这一变革不仅改进了编辑器性能,还带来了更灵活、更模块化的资产处理方式。本文将介绍这一系统的基本概念、设计理念、使用方法,以及如何通过合理实践来有效利用它。
基本概念 什么是 AssetDefinition? AssetDefinition
是虚幻引擎5中用于定义资产类型特性和行为的核心类。每种资产类型(如材质、纹理、蓝图等)都有一个对应的AssetDefinition
类,它描述了:
资产在编辑器中的表现方式(名称、颜色、图标)
资产的打开和编辑行为
资产的比较和合并方式
资产的分类和过滤方式
资产支持的操作和上下文菜单
简而言之,AssetDefinition
定义了”一种资产是什么”以及”如何与之交互”。
核心类层次结构与交互模式
UAssetDefinition : 抽象基类,定义所有资产共有的基本接口
UAssetDefinitionDefault : 提供常见功能的默认实现
具体资产定义类 : 为特定资产类型实现定制行为
UAssetDefinitionRegistry : 全局注册表,管理所有资产定义实例并提供查询接口
资产定义注册与查找机制 资产定义系统通过UAssetDefinitionRegistry
单例进行集中管理,这个类是系统的关键枢纽:
自动注册 :所有继承自UAssetDefinition
的类在引擎启动时会被自动发现并注册到注册表中
查找资产定义 :引擎和插件代码通过注册表来查找特定资产类型的定义:
缓存映射 :注册表内部维护类型到定义的映射关系,确保高效查询
系统交互流程 当对资产执行操作时,系统的交互流程如下:
请求资产操作 :编辑器代码(如内容浏览器)需要执行资产操作(如打开、比较)
查找资产定义 :通过UAssetDefinitionRegistry
获取对应的资产定义
执行定义的行为 :调用资产定义上的相应方法执行操作
处理结果 :资产定义返回处理结果(如EAssetCommandResult::Handled
表示操作已处理)
关键类和接口 每个UAssetDefinition
提供的核心方法包括:
基本属性 :
1 2 3 virtual FText GetAssetDisplayName () const ;virtual FLinearColor GetAssetColor () const ;virtual TSoftClassPtr<UObject> GetAssetClass () const ;
资产操作 :
1 2 3 virtual EAssetCommandResult OpenAssets (const FAssetOpenArgs& OpenArgs) const ;virtual EAssetCommandResult PerformAssetDiff (const FAssetDiffArgs& DiffArgs) const ;virtual EAssetCommandResult Merge (const FAssetAutomaticMergeArgs& MergeArgs) const ;
资产分类和过滤 :
1 2 virtual TConstArrayView<FAssetCategoryPath> GetAssetCategories () const ;virtual EAssetSupportResponse CanLocalize (const FAssetData& InAsset) const ;
注册表的核心功能 UAssetDefinitionRegistry
类提供以下关键功能:
查询功能 :
1 2 3 4 const UAssetDefinition* GetAssetDefinitionForClass (const UClass* InClass) const ;const UAssetDefinition* GetAssetDefinitionForAsset (const UObject* InAsset) const ;const UAssetDefinition* GetAssetDefinitionForAssetData (const FAssetData& InAssetData) const ;const UAssetDefinition* GetAssetDefinitionForClassNames (FStringView InClassName, FStringView InObjectPathName) const ;
多实例处理 :允许查询多个资产的共同定义
1 const UAssetDefinition* GetCommonAssetDefinitionForAssets (TArrayView<const UObject*> InAssets) const ;
迭代所有定义 :
1 TArray<const UAssetDefinition*> GetAllAssetDefinitions () const ;
实际应用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 UClass* CommonClass = nullptr ; for (int32 ObjIdx = 0 ; ObjIdx < SelectedAssets.Num (); ++ObjIdx){ if (CommonClass == nullptr ) { CommonClass = UAssetRegistryHelpers::FindAssetNativeClass (SelectedAssets[ObjIdx]); continue ; } UClass* Class = UAssetRegistryHelpers::FindAssetNativeClass (SelectedAssets[ObjIdx]); while (Class && !Class->IsChildOf (CommonClass)) { CommonClass = CommonClass->GetSuperClass (); } } ContextObject->CommonClass = CommonClass; ContextObject->CommonAssetDefinition = UAssetDefinitionRegistry::Get ()->GetAssetDefinitionForClass (ContextObject->CommonClass); if (Context->CommonAssetDefinition){ Section.Label = FText::Format (NSLOCTEXT ("AssetTools" , "AssetSpecificOptionsMenuHeading" , "{0} Actions" ), Context->CommonAssetDefinition->GetAssetDisplayName ()); }
设计理念 虚幻引擎5引入AssetDefinition
系统主要基于以下设计理念:
1. 关注点分离 旧系统将资产的定义、操作和UI展示混合在一起,新系统将这些关注点明确分离:
资产本身的特性(UAssetDefinition
)
资产的编辑行为(工具集)
资产的UI交互(菜单扩展)
2. 性能优化 旧系统的一个主要问题是在右键点击资产时会加载资产,即使只是查看菜单。新系统避免了这种无谓的加载,显著提高了编辑器性能。
3. 可扩展性增强 新系统采用了更现代的扩展机制,使得任何模块都能为任何资产类型添加功能,而无需修改原始代码。
4. 一致性与可维护性 统一的资产定义接口使得引擎能够以一致的方式处理所有资产类型,简化了维护工作。
新旧系统的对比 旧系统 filePath 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class ASSETTOOLS_API FAssetTypeActions_DataAsset : public FAssetTypeActions_Base{ public : virtual FText GetName () const override { return NSLOCTEXT ("AssetTypeActions" , "AssetTypeActions_DataAsset" , "Data Asset" ); } virtual FColor GetTypeColor () const override { return FColor (201 , 29 , 85 ); } virtual UClass* GetSupportedClass () const override { return UDataAsset::StaticClass (); } virtual uint32 GetCategories () override { return EAssetTypeCategories::Misc; } virtual FText GetDisplayNameFromAssetData (const FAssetData& AssetData) const override ; virtual void GetActions (const TArray<UObject*>& InObjects, FToolMenuSection& Section) override { Section.AddMenuEntry ( "DataAsset_ChangeClass" , LOCTEXT ("DataAsset_ChangeClass" , "Convert to Different DataAsset Type" ), LOCTEXT ("DataAsset_ChangeClassTip" , "Change the class these Data Assets are subclassed from." ), FSlateIcon (), FUIAction ( FExecuteAction::CreateSP (this , &FAssetTypeActions_DataAsset::ExecuteChangeDataAssetClass, GetTypedWeakObjectPtrs <UDataAsset>(InObjects)) ) ); } virtual void PerformAssetDiff (UObject* OldAsset, UObject* NewAsset, const FRevisionInfo& OldRevision, const FRevisionInfo& NewRevision) const override ; private : void ExecuteChangeDataAssetClass (TArray<TWeakObjectPtr<UDataAsset>> InDataAssets) ; };
新系统 filePath 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 UCLASS ()class UAssetDefinition_DataAsset : public UAssetDefinitionDefault{ GENERATED_BODY () public : UAssetDefinition_DataAsset () { IncludeClassInFilter = EIncludeClassInFilter::Always; } virtual FText GetAssetDisplayName () const override { return NSLOCTEXT ("AssetDefinition" , "DataAsset" , "Data Asset" ); } virtual FText GetAssetDisplayName (const FAssetData& AssetData) const override ; virtual FLinearColor GetAssetColor () const override { return FLinearColor (FColor (201 , 29 , 85 )); } virtual TSoftClassPtr<UObject> GetAssetClass () const override { return UDataAsset::StaticClass (); } virtual EAssetCommandResult PerformAssetDiff (const FAssetDiffArgs& DiffArgs) const override ; virtual bool CanMerge () const override ; virtual EAssetCommandResult Merge (const FAssetAutomaticMergeArgs& MergeArgs) const override ; virtual EAssetCommandResult Merge (const FAssetManualMergeArgs& MergeArgs) const override ; }; namespace MenuExtension_DataAsset{ static void ExecuteChangeDataAssetClass (const FToolMenuContext& InContext) { const UContentBrowserAssetContextMenuContext* Context = UContentBrowserAssetContextMenuContext::FindContextWithAssets (InContext); if (Context) { TArray<UDataAsset*> DataAssets = Context->LoadSelectedObjects <UDataAsset>(); if (DataAssets.Num () > 0 ) { } } } static FDelayedAutoRegisterHelper DelayedAutoRegister (EDelayedRegisterRunPhase::EndOfEngineInit, []{ UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateLambda([](/2025/05/09/UE_AssetDefinition/) { FToolMenuOwnerScoped OwnerScoped(UE_MODULE_NAME); UToolMenu* Menu = UE::ContentBrowser::ExtendToolMenu_AssetContextMenu(UDataAsset::StaticClass()); FToolMenuSection& Section = Menu->FindOrAddSection("GetAssetActions" ); Section.AddDynamicEntry(NAME_None, FNewToolMenuSectionDelegate::CreateLambda([](FToolMenuSection& InSection) { const TAttribute<FText> Label = LOCTEXT("DataAsset_ChangeClass" , "Convert to Different DataAsset Type" ); const TAttribute<FText> Tooltip = LOCTEXT("DataAsset_ChangeClassTip" , "Change the class these Data Assets are subclassed from." ); FToolUIAction UIAction; UIAction.ExecuteAction = FToolMenuExecuteAction::CreateStatic(&ExecuteChangeDataAssetClass); InSection.AddMenuEntry("DataAsset_ChangeClass" , Label, Tooltip, FSlateIcon(), UIAction); })); })); }) ;}
主要区别分析
注册机制的简化
旧系统需要在模块启动时手动注册:
1 2 IAssetTools::Get ().RegisterAssetTypeActions (MakeShared <FAssetTypeActions_DataAsset>());
新系统自动发现并注册UObject子类:
菜单处理分离
旧系统在类内处理菜单:
1 virtual void GetActions (const TArray<UObject*>& InObjects, FToolMenuSection& Section) override ;
新系统通过独立的菜单扩展系统:
1 2 3 4 namespace MenuExtension_DataAsset{ }
这种分离使得菜单功能可以独立于基本资产定义。
延迟加载优化
旧系统的菜单构建要求预先加载资产:
1 virtual void GetActions (const TArray<UObject*>& InObjects, FToolMenuSection& Section) override ;
新系统仅在操作执行时加载:
1 2 3 4 5 static void ExecuteChangeDataAssetClass (const FToolMenuContext& InContext) { TArray<UDataAsset*> DataAssets = Context->LoadSelectedObjects <UDataAsset>(); }
性能优化的关键点 新系统的最大改进在于避免在浏览资产时加载它们,特别是在构建上下文菜单时。这解决了UE5之前的一个主要性能瓶颈,在旧系统中简单地右键点击资产就会触发资产加载,有时还会引发级联加载其他相关资产。
通过将资产定义与操作分离,并使用FAssetData
而不是加载的UObject*
,新系统能够在不加载资产的情况下显示资产信息和构建上下文菜单,仅在确实需要资产数据时才加载它们。
基本使用方式 创建资产定义类 要为自定义资产类型创建资产定义,请按照以下步骤操作:
创建继承自UAssetDefinitionDefault
的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 UCLASS ()class UAssetDefinition_MyAsset : public UAssetDefinitionDefault{ GENERATED_BODY () public : UAssetDefinition_MyAsset () { IncludeClassInFilter = EIncludeClassInFilter::Always; } virtual FText GetAssetDisplayName () const override { return LOCTEXT ("MyAsset" , "My Asset" ); } virtual FLinearColor GetAssetColor () const override { return FLinearColor (0.2f , 0.8f , 0.4f ); } virtual TSoftClassPtr<UObject> GetAssetClass () const override { return UMyAsset::StaticClass (); } virtual TConstArrayView<FAssetCategoryPath> GetAssetCategories () const override { static const TArray<FAssetCategoryPath> Categories = { EAssetCategoryPaths::Basic, EAssetCategoryPaths::Gameplay }; return Categories; } };
资产定义类会在引擎启动时自动注册,无需额外步骤。
添加自定义菜单项 要为资产类型添加自定义上下文菜单项:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 namespace MenuExtension_MyAsset{ void ExecuteCustomAction (const FToolMenuContext& MenuContext) { if (const UContentBrowserAssetContextMenuContext* Context = UContentBrowserAssetContextMenuContext::FindContextWithAssets (MenuContext)) { TArray<UMyAsset*> SelectedAssets = Context->LoadSelectedObjectsIf <UMyAsset>(); for (UMyAsset* Asset : SelectedAssets) { } } } static FDelayedAutoRegisterHelper DelayedAutoRegister (EDelayedRegisterRunPhase::EndOfEngineInit, []{ UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateLambda([](/2025/05/09/UE_AssetDefinition/) { FToolMenuOwnerScoped OwnerScoped(UE_MODULE_NAME); UToolMenu* Menu = UE::ContentBrowser::ExtendToolMenu_AssetContextMenu(UMyAsset::StaticClass()); FToolMenuSection& Section = Menu->FindOrAddSection("AssetActions" ); Section.AddDynamicEntry(NAME_None, FNewToolMenuSectionDelegate::CreateStatic([](FToolMenuSection& InSection) { InSection.AddMenuEntry( "MyAsset_CustomAction" , NSLOCTEXT("MyAssetActions" , "CustomAction" , "Custom Action" ), NSLOCTEXT("MyAssetActions" , "CustomActionTooltip" , "Performs a custom action on this asset" ), FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.MyAsset" ), FToolUIAction(FToolMenuExecuteAction::CreateStatic(&ExecuteCustomAction)) ); })); })); }) ;}
自定义资产打开行为 如果默认的资产编辑器不适合您的资产类型,可以重写OpenAssets
方法:
1 2 3 4 5 6 7 8 9 10 11 12 virtual EAssetCommandResult OpenAssets (const FAssetOpenArgs& OpenArgs) const override { if (OpenArgs.OpenMethod == EAssetOpenMethod::Edit) { FMyCustomAssetEditor::CreateEditor (OpenArgs.LoadObjects <UMyAsset>()); return EAssetCommandResult::Handled; } return Super::OpenAssets (OpenArgs); }
自定义差异比较 要为资产提供更专业的差异比较功能,可以重写PerformAssetDiff
:
1 2 3 4 5 6 7 8 9 10 11 12 virtual EAssetCommandResult PerformAssetDiff (const FAssetDiffArgs& DiffArgs) const override { if (DiffArgs.OldAsset == nullptr && DiffArgs.NewAsset == nullptr ) { return EAssetCommandResult::Unhandled; } FMyAssetDiffWindow::CreateWindow (DiffArgs.OldAsset, DiffArgs.NewAsset); return EAssetCommandResult::Handled; }
后记 最近遇到了很多令人疲惫的事,导致这段时间基本没做啥东西了。偶然在电脑上发现了之前看资产系统拿AI整理的一点笔记,发上来冒个泡水一篇。
内容粗浅,博君一笑。
常用类和方法参考 UAssetDefinition 关键方法
方法名
描述
GetAssetDisplayName()
获取资产的显示名称
GetAssetColor()
获取资产的颜色
GetAssetClass()
获取与此定义关联的资产类
GetAssetCategories()
获取资产的类别
OpenAssets()
处理资产的打开操作
PerformAssetDiff()
执行资产对比操作
UAssetDefinitionRegistry 重要方法
方法名
描述
Get()
获取全局资产定义注册表
GetAssetDefinitionForAsset()
根据资产获取对应的资产定义
GetAssetDefinitionForClass()
根据类获取对应的资产定义
GetAllAssetDefinitions()
获取所有已注册的资产定义