查看: 564|回复: 8

[编程指南] 【Details Panel Customization | Unreal Engine】

[复制链接]

1

主题

342

帖子

7万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
75866
发表于 2016-6-27 00:51:29 | 显示全部楼层 |阅读模式



The Details panel is now fully customizable.  You can rearrange properties via a simple system, or you can fully customize them using Slate 用户界面框架.   You can also add other UI to the details using Slate syntax.

本页面的内容:

      
Setup instructions

Customizing
Multibox Style Layout
Property Handles
Slate Layout
Customization Dos and Dont's

Advanced Tips
Accessing complicated properties.
Accessing Arrays
Hiding properties
     Setup instructions
Create a class for customizing properties in.  This must inherit from ILayoutDetails.

You implement one function: void LayoutDetails( IDetailLayoutBuilder ).

The purpose of this class is to encapsulate customization for a classes properties.  One instance of the class will be created for each Details panel that requires it.

Set up a delegate that will be called when the Details panel recognizes properties for a specific class.

The sole purpose of this delegate is to create an instance of your customization class for a specific UObject that has properties.  Remember that there often are multiple details views up at any point and each instance of the details view gets its own customization class instance.  This allows you to store per detail instance data on your layout class.



Example (more examples are located in DetailCustomizations.cpp):

FPropertyEditorModule PropertyModule = FModuleManager::LoadModuleCheckedFPropertyEditorModule("PropertyEditor");PropertyModule.RegisterCustomPropertyLayout( ABrush::StaticClass(), FOnGetDetailLayoutInstance::CreateRaw( FBrushDetails::MakeInstance ) );...static TSharedRefILayoutDetails FBrushDetails::MakeInstance(){    return MakeShareable( new FBrushDetails );}
Implement your customization in the LayoutDetails function of the class you made in step 1.

If this is an engine class, you should add your customization class (if it does not already exist) to the DetailCustomizations module.  This module can be recompiled and reloaded without restarting the editor, making it useful for  fast tweaking of properties.

Bind your delegate in FDetailCustomizationsModule.StartupModule and unbind it in FDetailCustomizationsModule.ShutdownModule.

Game specific classes should use a game specific module.

See examples in this document and the DetailCustomizations module (such as PrimitiveComponentDetails.cpp and StaticMeshComponentDetails.cpp).
Customizing
You handle all customization inside the LayoutDetails function of your customization class.  This function accepts an IDetailLayoutBuilder, which is your interface to properties and how you pass back customization widgets.

The primary function of IDetailLayoutBuilder is to create categories where properties and other details reside.  There are some other minor functions on this class which are self explanatory (most of them you will not need). The documentation for those can be found in DetailLayoutBuilder.h.

The first step in customizing is to edit a category:

virtual void LayoutDetails( IDetailLayoutBuilder DetailBuilder ) override{    // Edit the lighting category    IDetailCategory LightingCategory = DetailBuilder.EditCategory("Lighting", TEXT("OptionalLocalizedDisplayName") );}
The EditCategory function takes an FName for the category where properties will reside and an optional localized display name.  If the display name is specified, it will override any existing display name.  The category name does not have to be the same category name specified in the UPROPERTY macro, although it will reuse the UPROPERTY category if the names are the same.  The macro category name is used as the default category if the property is not customized and in the tree view.

EditCategory returns an IDetailCategoryBuilder which is what you use to add properties to a category.  There are a two ways to do this:

Using simple multibox style layout, which is quick for rearranging properties.

Using Slate syntax which offers the most robust customization options.

Multibox Style Layout
An easy example using the LightingCategory created above:

// Add a property to the category.  The first param is the name of the property and the second is an optional display name override.LightingCategory.AddProperty("bCastStaticShadow", TEXT("Static") );LightingCategory.AddProperty("bCastDynamicShadow", TEXT("Dynamic") );LightingCategory.AddProperty("bCastVolumetricTranslucentShadow", TEXT("Volumetric") );
This is the most basic example.  It adds 3 properties stacked vertically and overrides their display names. (The text in the examples is not localized to save space, but should always be localized in general practice.)

Note that customized properties and categories always appear above non-customized ones.  You can use this simple syntax to reorganize important properties which may otherwise be buried.

The result:

【虚幻4翻译文档-Details Panel Customization | Unreal Engine】[虚幻4中文文档]



A slightly more advanced example (located in PrimitiveComponentDetails.cpp):

// Create a non-collapsible group with the display name "Shadows" which is only visible if the CastShadow property is enabled. All properties below this call will appear in the same group until EndGroup or another BeginGroup is calledLightingCategory.BeginGroup( TEXT("Shadows"), GroupImageName, "CastShadow" );    // Begin a new line.  All properties below this call will be added to the same line until EndLine() or another BeginLine() is called    LightingCategory.BeginLine();
     // Add properties using their default look
     LightingCategory.AddProperty("bCastStaticShadow", TEXT("Static") );
     LightingCategory.AddProperty("bCastDynamicShadow", TEXT("Dynamic") );
     LightingCategory.AddProperty("bCastVolumetricTranslucentShadow", TEXT("Volumetric") );    LightingCategory.BeginLine();
     LightingCategory.AddProperty("bCastInsetShadow", TEXT("Inset") );
     LightingCategory.AddProperty("bCastHiddenShadow", TEXT("Hidden") );
     LightingCategory.AddProperty("bCastShadowAsTwoSided", TEXT("Two Sided") );LightingCategory.EndGroup();
BeginGroup is used to create  a new group of properties.  It  takes a name to display for the group, an optional image name (Slate brush name) to display next to the name, and an optional edit condition property which if false will hide the entire group from view so its properties cannot be changed.  These edit conditions are the same as the UPROPERTY macro style edit conditions except they operate on a group of properties instead of just one.  More things like this could be added in the future!

AddProperty adds a property using its default look.  It usually only needs one parameter which is the property name.  More complicated properties such as properties inside structs need additional information.  See the Advanced Tips section or the documentation in DetailCategoryBuilder.h if you need this.

BeginLine creates a new line of properties.  By default, all properties added via AddProperty are created on a new line.  BeginLine ensures all properties added until the next BeginLine or EndLine are on the same line.

The result:

【虚幻4翻译文档-Details Panel Customization | Unreal Engine】[虚幻4中文文档]



Notes About Multibox Style Layout
It is not very powerful, but more features will be added as needed.  It is currently designed to be for quick reorganization.

The slate layout will require more advanced access to properties, specifically property handles, especially if you need to customize their look.

Property Handles
The two main functions of property handles are to read and write the value of a property and to identify the property to Slate customization widgets.  How properties are accessed by the details view/property tree is somewhat complicated, so property handles hide all that away and handles undo/redo, pre/post edit change, package dirtying, world switching etc., for you.

To get a property handle, you must ask the IDetailCategory where you want to customize it for one.  You do this by calling IDetailCategory::GetProperty.  Usually you simply pass in the name of the property as follows:

IDetailCategoryBuilder LightingCategory = DetailBuilder.EditCategory( "Lighting" );// Get a handle to the "bOverrideLightmapRes" propertyTSharedPtrIPropertyHandle OverrideLightmapRes = LightingCategory.GetProperty( "bOverrideLightmapRes" );
Now you have a handle to the bool property bOverrideLightmapRes.

From here, you can read and write the value of that property and/or pass it to a slate widget for customization.



Useful property handle functions (For the complete documented list see PropertyHandle.h):


Function
Description

IPropertyHandle::SetValue(const ValueType InValue) and IPropertyHandle::GetValue(ValueType OutValue)Writes and reads property values. These are overloaded for many built in types (including vectors and rotators).  For complicated types like user structs, will need to get a child handle.  See the advanced section at the end of this document.ResetToDefault()Resets a property to its default.IsValidHandle()Returns whether or not you have a valid property handle.AsArray()Array property values are special.  See the advanced section at the end of this document.
Other notes:

The handle returned from GetProperty may be invalid if the property could not be found or is not going to appear in the details view.  Check IsValidHandle() to be sure.  Calling functions on invalid handles will not crash.

You should not store property handles outside of your layout class unless they are weak pointers.  Internally the data they access is a weak pointer so it will not crash if you try to set or get a value on an invalid property but you have a reference to a useless object if you store it and it is not cleaned up.

If you try to read / write access a value type for an unsupported property (e.g., a float for a String property ), it will fail but no data will be corrupted.

Handling failure cases when accessing values.

Remember that detail views can view multiple objects at once and it is not uncommon for users to select hundreds of Actors at once.  In cases like these, you will undoubtedly have multiple values for one property.  GetValue and SetValue return an FPropertyAccess::Result to help you determine whether or not accessing a value was successful. FPropertyAccess::MultipleValues will be a common return value.

/*** Potential results from accessing the values of properties


  */namespace FPropertyAccess{
enum Result
{
     /** Multiple values were found so the value could not be read */
     MultipleValues,
     /** Failed to set or get the value (Property is no longer available, is not a compatible type, or is edit const are the likely cases) */
     Fail,
     /** Successfully set or got the value */
     Success,
};}
If you are customizing a low level typed property like an int or float, you must handle the multiple values case somehow.

    INT MyInteger;    // Get the value of the property    FPropertyAccess::Result MyResult = MyIntHandle-GetValue(MyInteger);
If MyResult is FPropertyAccess::MultipleValues, MyInteger will not be set.  Sending that to a widget that displays it will show garbage value and initializing it before hand is not much better because it is still not the correct value.  How this is handled is up to the customizer.  For numeric types, using SNumericEntryBox is recommended which allows you to optionally return no value in its value attribute.  It will then display a label you provide instead.  See SNumericEntryBox.h.

Slate Layout
Slate layout allows you to completely customize the look and arrangement of properties.  You pass your layout back to a category via IDetailCategoryBuilder::AddWidget which takes an arbitrary widget from Slate.  To assist you in this, there are some customization widgets available to you:

SProperty
This is the customization widget.  You use this widget to customize a property and/or embed the property in other slate declarative syntax.  You create an SProperty using SNew but you also provide a handle to the property so it knows what to build.  The handle is a non-optional parameter to SNew:  SNew( SProperty, HandleToTheProperty )

Example:

// Edit the lighting categoryIDetailCategoryBuilder LightingCategory = DetailBuilder.EditCategory( "Lighting" );// Get a handle to the bOverrideLightmapRes propertyTSharedPtrIPropertyHandle OverrideLightmapRes = LightingCategory.GetProperty( "bOverrideLightmapRes" );LightingCategory.AddWidget()[    SNew( SHorizontalBox )    + SHorizontalBox::Slot()    [
// Make a new SProperty
SNew( SProperty, EnableOverrideLightmapRes )    ]    + SHorizontalBox::Slot()    .Padding( 4.0f, 0.0f )    .MaxWidth( 50 )    [
SNew( SProperty, LightingCategory.GetProperty("OverriddenLightMapRes") )
.NamePlacement( EPropertyNamePlacement::Hidden ) // Hide the name    ]];
The result:

【虚幻4翻译文档-Details Panel Customization | Unreal Engine】[虚幻4中文文档]



SProperty by default will generate a widget for the property.  There are some basic customization attributes on SProperty for customizing the default look (such as the name).  If you want to customize a property, you use the CustomWidget slot.  Once you use the CustomWidget slot, SProperty no longer knows anything about how to set and get the value since you have made a custom widget.  You need to use your property handle to get and set the value.

Example:

  // Customizes the OverridenLightMapRes property to display some text and a spinbox   TSharedPtrIPropertyHandle LightMapResValue = LightingCategory.GetProperty("OverriddenLightMapRes")  SNew( SProperty, LightMapResValue )  .CustomWidget()  [
SNew( SHorizontalBox )
+ SHorizontalBox::Slot()
.VAlign( VAlign_Center )
.Padding( 2.0f )
[

SNew( STextBlock )

.Text( TEXT("Lightmap Res") )
]
+ SHorizontalBox::Slot()
[

SNew( SSpinBox )

.MinSliderValue( 0 )

.MaxSliderValue( 1024 )

.OnValueCommitted( SetValueOnProperty )

  .Value( GetValueFromProperty
]  ]  ...  FLOAT GetValueFromProperty()  {
// Using the property handle created above, get its value and send it to the spinbox
INT Value; // note lightmap res is an integer so it must be accessed as such.
LightMapResValue.GetValue( Value );
// Note HANDLE FAILURE CASES
return Value;  }  void SetValueOnProperty( FLOAT NewValue )  {
// Using the property handle, set its value
LightMapResValue.SetValue( NewValue )  }<h5 id="spropertynotesSProperty Notes
SProperty always displays reset to default even if you make a custom widget.  There is an argument on the SProperty which toggles this behavior.  For example, if you have a row of properties, you may want to tell it not to display a reset to default for each one but make a big menu at the end.  See SResetToDefaultMenu below.

If you pass an invalid handle to SProperty, it will simply not show up.

In addition to SProperty there are some other customization widgets that can be used.

SAssetProperty
SAssetProperty is an SProperty that displays a thumbnail of the asset as well as an entry box for changing the asset.  You can change the size of the thumbnail as well.  You can use this on UObject properties that have renderable thumbnails.  If you use this on other types, it will not display anything.

【虚幻4翻译文档-Details Panel Customization | Unreal Engine】[虚幻4中文文档]



SFilterableDetail
SFilterableDetail is a widget that does not draw anything but filters everything in its content slot when a user types in the search box of the details view.  This widget is useful for non-property based details. SProperty is already filtered so you do not need to set up an SFilterableDetail for those unless you want to group their filtering.

// Create a widget which will filter out everything in the content slot when "Create Blocking Volume" is not matched with a user's search term// Note: The second parameter is the localized search term that matches the filter and the third parameter is the category where this filter should resideSNew( SFilterableDetail, NSLOCTEXT("StaticMeshDetails", "BlockingVolumeMenu", "Create Blocking Volume"), StaticMeshCategory ).Content()[      // Create blocking volume menu      SNew( SComboButton )      .ButtonContent()      [
     SNew( STextBlock )
     .Text( NSLOCTEXT("StaticMeshDetails", "BlockingVolumeMenu", "Create Blocking Volume") )
      .Font( IDetailLayoutBuilder::GetDetailFont() )      ]      .MenuContent()      [
     BlockingVolumeBuilder.MakeWidget()      ]]
SResetToDefaultMenu
SResetToDefaultMenu is a menu which displays the yellow reset to default arrow.  By default, SProperty adds a reset to default menu, but sometimes it makes sense to group more than one property into the same menu. (e.g Vector properties).  You can add SProperty widgets to an SResetToDefaultMenu to handle this for you.  Simply call AddProperty on SResetToDefaultMenu and then place the menu in any declarative syntax!

SArrayProperty
This widget allows you to customize an array of properties.  You  create one just like SProperty and also hook up a delegate which is called each time a widget for an array element is needed.

Example:

void FMeshComponentDetails::LayoutDetails( IDetailLayoutBuilder DetailLayout ){      IDetailCategoryBuilder DetailCategory = DetailLayout.EditCategory("Rendering");      TSharedRefIPropertyHandle MaterialProperty = DetailCategory.GetProperty( "Materials" );      DetailCategory.AddWidget()      [
     SNew( SArrayProperty, MaterialProperty )
     // This delegate is called for each array element to generate a widget for it
     .OnGenerateArrayElementWidget( this, FMeshComponentDetails::OnGenerateElementForMaterials )      ];}.*** Generates a widget for a materials element*  * @param ElementProperty     A handle to the array element we need to generate* @param ElementIndex
The index of the element we are generating*/TSharedRefSWidget FMeshComponentDetails::OnGenerateElementForMaterials( TSharedRefIPropertyHandle ElementProperty, INT ElementIndex ){      return
      SNew( SAssetProperty, ElementProperty )
     .ThumbnailSize( FIntPoint(32,32) );}
The result:

【虚幻4翻译文档-Details Panel Customization | Unreal Engine】[虚幻4中文文档]



Customization Dos and Dont's
Do check error cases when customizing properties and reading/writing values.  Remember that details views can often be viewing multiple objects at once where each object has different values.  Customized properties should be able to handle the common case of multiple values.

Do store any data about the selection on your customization class.  Some non-property details will need the selected Actors for a customization.  You can get the selected Actors from IDetailLayoutBuilder.  You can store this selection set or anything selection sensitive on your customization class.  It is guaranteed to be around while the selection remains the same in its details view.

Do not use FActorIterator, FSelectedActorIterator, or GEditor-GetSelectedActorIterator.  Remember that the Details panel can be locked and these things operate on global selection sets or lists of Actors which is not the same as the selected Actors in a Details panel if it is locked!  Using these will access different data.  You can get the list of selected Actors valid for you from IDetailLayoutBuilder.

Do not hold a strong reference to your layout class or property handles (you should not need to anyway).  Remember that details views (especially level editor ones) can change at any time based on user selection so any references you have to layout classes can easily become invalid.  The shared pointers to layout classes are checked for uniqueness when customizing details to prevent this from happening.
Advanced Tips
Accessing complicated properties.
A complicated property is defined as anything that cannot be resolved with just a property name.  Usually this involves properties inside structs.

There are two ways to access complicated properties:

Functions that return a property handle or add a property to a category take optional parameters for resolving properties.

Example:

TSharedPtrIPropertyHandle IDetailCategoryBuilder::GetProperty(  FName PropertyPath, UClass* ClassOutermost , FName InstanceName)

Parameter
Description

PathThe path to the property.  Can be just a name of the property or a path in the format outer.outer.value[optional_index_for_static_arrays].ClassOutermostOptional outer class if accessing a property outside of the current class being customized.InstanceNameOptional instance name if multiple UProperty's of the same type exist (Such as two identical structs, the instance name is one of the struct variable names).
Examples:

struct MyStruct{     INT StaticArray[3];    FLOAT FloatVar;}class MyActor{     MyStruct Struct1;    MyStruct Struct2;    FLOAT MyFloat}
To access StaticArray at index 2 from Struct2 in MyActor, your path would be "MyStruct.StaticArray[2]" and your instance name is "Struct2".

To access the same StaticArray outside of MyActor customization functions you would do the same as above but ClassOutermost would be MyActor::StaticClass().

To access MyFloat in MyActor you can just pass in "MyFloat" because the name of the property is unambiguous.

If you have a property handle, you can get a child property handle by name from it:

TSharedPtrIPropertyHandle IPropertyHandle::GetChildHandle( FName ChildName )

Parameter
Description

ChildNameThe property name of the child.  This will recurse until found.  Paths are not supported and children of arrays cannot be accessed in this way.
Accessing Arrays
You can access arrays via IPropertyHandle::AsArray.  If the property handle is an array, this will return an IPropertyHandleArray which has functions for adding, removing, inserting, duplicating, and getting the number of elements of an array.

Hiding properties
You can hide properties altogether by calling IDetailLayoutBuilder::HideProperty.  It takes either a name/path or a property handle.
回复

使用道具 举报

0

主题

872

帖子

2846

积分

vip会员

Rank: 1

积分
2846
发表于 2016-7-2 19:33:57 来自手机 | 显示全部楼层
挺不错的!就是不知道价格
回复 支持 反对

使用道具 举报

0

主题

901

帖子

2954

积分

vip会员

Rank: 1

积分
2954
发表于 2016-7-3 23:35:00 来自手机 | 显示全部楼层
看看这个好不好啊
回复 支持 反对

使用道具 举报

0

主题

851

帖子

2820

积分

vip会员

Rank: 1

积分
2820
发表于 2016-7-4 11:30:57 来自手机 | 显示全部楼层
这个多少金币啊。
回复 支持 反对

使用道具 举报

0

主题

858

帖子

2837

积分

vip会员

Rank: 1

积分
2837
发表于 2016-7-8 16:54:51 来自手机 | 显示全部楼层
谢谢分享,顶一个
回复 支持 反对

使用道具 举报

0

主题

897

帖子

2962

积分

vip会员

Rank: 1

积分
2962
发表于 2016-7-22 01:43:27 | 显示全部楼层
嗯,这个资源特别的好啊,赶紧下来收藏起来。
回复 支持 反对

使用道具 举报

0

主题

901

帖子

2974

积分

vip会员

Rank: 1

积分
2974
发表于 2016-7-22 04:51:52 来自手机 | 显示全部楼层
什么游戏看看
回复 支持 反对

使用道具 举报

0

主题

876

帖子

2886

积分

vip会员

Rank: 1

积分
2886
发表于 2016-7-23 06:51:10 来自手机 | 显示全部楼层
这个不错这个不错这个不错
回复 支持 反对

使用道具 举报

0

主题

857

帖子

2858

积分

vip会员

Rank: 1

积分
2858
发表于 2016-7-24 12:13:33 | 显示全部楼层
这样的人是业界良心 有时在人人下个东西学比取经还难
回复 支持 反对

使用道具 举报

*滑块验证:
您需要登录后才可以回帖 登录 | enginedx注册

本版积分规则

 
 



邮件留言:


 
返回顶部