UE4 Editor Extended Trampling History

Keywords: C++ Programming Attribute Unity Windows

As the saying goes, first-class programming architecture, three-process programming UI. But in the process of game development, especially in the development of engine and tool chain, UI is a pit that can not be bypassed. UE4 is becoming more and more popular in major factories. Various tools emerge in endlessly. But compared with unity, Slate UI is not a big level when it is used as editor extension and plug-in. The most important thing is that UE4's editor buries numerous pits and only writes. When I experience it, I record the pit daddy problem.

First of all, Slate framework has been analyzed by God. Basically, Slate is a self-created UI framework written from DX or OpenGL. Like using UMG as game UI in UE4, Slate defines its own grammar besides the underlying rendering function. The purpose is to define the hierarchical structure and layout in UI, that is Slot. In theory, any of our editor extensions can be written in Slate. But if you look at a little bit of UE4 code, you know it's a huge and tedious project, especially because VS doesn't support Slate's weird grammar. So UE4 has built a lot of wheels to encapsulate a lot of UI work, such as adding a button, adding editor attributes and so on.

Then the question arises, how can we learn these UE4 wheels quickly and use them for me? In fact, it is a word: "copy". In the process of development, various official plug-ins and the Unreal Ed module itself are our best reference. With the WidgetReflector tool in UE4, we can quickly locate the entries of various UI components, so that we can easily "copy" the code for our use.

Of course, the above methodologies are not the focus of this article. Next, we will talk about the actual content of the editor expansion. I will assume that you are already familiar with the basic plug-in production and compilation of UE4.

1.FExtender

The most common function of editor extension is to add a button. In the layout of UE4 editor, it is convenient for us to add buttons and entries to drop-down menus and toolbars. Just call Extender directly.

The menu bars, toolbars and menus in the UE4 editor all have corresponding Extender classes, such as FMenu Extender, add buttons or menu entries. We need to specify the following four things:
Extension Point is generally defined by the UE4 editor. Settings, for example, is added to the set menu. More commonly, there is Windows LayOut, EditMain.

HookPosition In fact, that isEExtensionHookthis enum

UICommandList Commandlist It's yours. UI To execute the function, the following code:

FXXCommands::Register();
PluginCommands = MakeShareable(new FUICommandList);
PluginCommands->MapAction(FXXCommands::Get().PluginAction2,
FExecuteAction::CreateRaw(this, &UIDelegateFunctionName),
FCanExecuteAction());

Delegate is a delegate defined by UE4 itself, which can be called by CreateRaw CreateSP method according to the type of function pointer.

By specifying these elements, we can call extender to modify UE4 Editor directly, such as the following code:

TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender());
MenuExtender->AddMenuExtension("EditMain", EExtensionHook::After, PluginCommands, FMenuExtensionDelegate::CreateRaw(this, &AddMenuCommands));

Add a clickable entry to the Edit menu.

2.DetailCustomization
As long as you have read the UE4 C++ document, you will know the UPROPERTY macro in C++, which can display the properties of custom classes, modify and so on at any time and conveniently. In fact, the UI of each custom attribute is implemented in UE4. The following figure clearly shows that for each UPROPERTY type UE4, a UI is implemented:

All of the PROPERYTY macros in UE4 can work. In fact, they all come from a class called IDetailView. The concrete principle is very simple, that is, parse is the type of UPROPERTY in UObject, which generates corresponding slate objects in turn. IDetailView can be used to do many things, especially for numerical display modification and so on. When we insert IDetailView objects into any slate node, UEEditor will automatically generate the corresponding numerical panel interface:

TSharedPtr<IDetailsView> myDetailView;
myDetailView = EditModule.CreateDetailView(DetailsViewArgs);
myDetailView->SetObject(myUObject);

Then in slate:

+ SVerticalBox::Slot()
    .AutoHeight()
    [
        WallDetailView->AsShared()
    ]

It can be said that it is a very useful function, in addition to the application of DetailView, in the actual extension, sometimes you need to do DetailCustomization, one is customization of detailview, such as modifying the interface of an actor, adding buttons, etc., the other is Property Type Customization.

Both of these Customization methods are implemented through class inheritance, namely IDetailCustomization and IPropertyTypeCustomization.
For example, we need to define an actor's information display panel. We need a:

class FXActorDetail :public IDetailCustomization

Then we write our own slate code in the CustomDetail function, such as adding a button to the actor

void FXActorDetail::CustomizeDetails(IDetailLayoutBuilder& DetailLayout)
{
    DetailLayout.EditCategory((CategoryName)) 
            .AddCustomRow((NewRowFilterString)) 
            .NameContent() 
            [ 
                SNew(STextBlock) 
                .Font(IDetailLayoutBuilder::GetDetailFont()) 
                .Text((TextLeftToButton)) 
            ] 
            .ValueContent() 
            .MaxDesiredWidth(125.f) 
            .MinDesiredWidth(125.f) 
            [ 
                SNew(SButton) 
                .ContentPadding(2) 
                .VAlign(VAlign_Center) 
                .HAlign(HAlign_Center) 
                .OnClicked((ObjectPtr), (FunctionPtr)) 
                [ 
                    SNew(STextBlock) 
                    .Font(IDetailLayoutBuilder::GetDetailFont()) 
                    .Text((ButtonText)) 
                ] 
            ]; 
}    

The customization of PropertyType is slightly different. PropertyType relies on two classes: IDetailPropertyRow and FDetailWidgetRow. What we need to do is to create our own widgetrow class to represent our own attributes. At the same time, we use slate code to customize their style and UE4 to represent component mobile attributes:

IDetailPropertyRow& MobilityRow = Category.AddProperty(MobilityHandle);
    MobilityRow.CustomWidget()
    .NameContent()
    [
        SNew(STextBlock)
        .Text(LOCTEXT("Mobility", "Mobility"))
        .ToolTipText(this, &FMobilityCustomization::GetMobilityToolTip)
        .Font(IDetailLayoutBuilder::GetDetailFont())
    ]
    .ValueContent()
    .MaxDesiredWidth(0)
    [
        SAssignNew(ButtonOptionsPanel, SUniformGridPanel)
    ];

3.EditMode extension
In addition to simple buttons, attribute display, UE4 editor also has a powerful function is the EdMode extension, which allows you to customize the mode of the editor, so as to achieve a variety of functions besides the standard game editor, such as terrain editing, brushes, etc., Edmode allows you to customize objects rendering hidden display brushes and so on.

Add an EdMode to Unreal Editor, which is usually written in the StartUpModule function of your plug-in:

FMyEdMode:Public FEdMode
FEditorModeRegistry::Get().RegisterMode<FMyEdMode:Public>(FMyEdMode:Public::EM_MyEdModeId, LOCTEXT("EdModeName", ""), FSlateIcon(FMyEdModeStyle::Get()->GetStyleSetName(), "Plugins.Tab"), true);

Each FEdMode l has an EdModel Toolkit. When we define our own EdModel, we also customize the customtoolkit. The toolkit hold s all your EdModel Tools. For example, your EdModel Tool is a slate class, where you can implement the UI of your special mode of operation and so on, and then use the gettookkit in EdModel.

The FEdMode class implement s a variety of editing-related functions. Pictured

Enter/Exit
The act of exiting and entering your editing mode is usually to initialize the Toolkit and hide the display objects.
For example:

FEdMode::Enter();
ToggleVisibility(true);
if (!Toolkit.IsValid())
{
    Toolkit = MakeShareable(new FLAEEdModeToolkit);
    Toolkit->Init(Owner->GetToolkitHost());
}

Selection
A common feature in EdMode is overloading selection s. UE4 allows you to customize selected objects as long as you overload EdMode's IsSelection Allowed.
You can, for example, only allow StaticMesh to be selected:

bool FMyEdMode::IsSelectionAllowed(AActor* InActor, bool bInSelection) const
{

    if (InActor->IsA(AStaticMeshActor::StaticClass()))
    {
        return true;
    }
    else
    {
        return false;
    }
}

But the pit that actually exists here is that the selection and deselection functions of UE4 will be judged by the return value of this function, that is, if your actor is selected in the editing process under one EdMode and you switch to another EdMode that is not allowed to be selected, you can no longer cancel the selection of this object.

The solution here is that you can write your own DeSelect method (copy Unreal Ed) and call it when you enter.

void FLAEEdMode::DeselectAll()
{
    // Make a list of selected actors . . .
    TArray<AActor*> ActorsToDeselect;
    for (FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It)
    {
        AActor* Actor = static_cast<AActor*>(*It);
        checkSlow(Actor->IsA(AActor::StaticClass()));

        ActorsToDeselect.Add(Actor);
    }
    for (int32 ActorIndex = 0; ActorIndex < ActorsToDeselect.Num(); ++ActorIndex)
    {
        AActor* Actor = ActorsToDeselect[ActorIndex];
        if (UActorGroupingUtils::IsGroupingActive())
        {
            // if this actor is a group, do a group select/deselect
            AGroupActor* SelectedGroupActor = Cast<AGroupActor>(Actor);
            if (SelectedGroupActor)
            {
                GEditor->SelectGroup(SelectedGroupActor, true, false, false);
            }
            else
            {
                // Select/Deselect this actor's entire group, starting from the top locked group.
                // If none is found, just use the actor.
                AGroupActor* ActorLockedRootGroup = AGroupActor::GetRootForActor(Actor, true);
                if (ActorLockedRootGroup)
                {
                    GEditor->SelectGroup(ActorLockedRootGroup, false, false, false);
                }
            }
        }

        // Don't do any work if the actor's selection state is already the selected state.
        const bool bActorSelected = Actor->IsSelected();
        if (bActorSelected)
        {
            GEditor->GetSelectedActors()->Select(Actor, false);
            {
                if (GEditor->GetSelectedComponentCount() > 0)
                {
                    GEditor->GetSelectedComponents()->Modify();
                }

                GEditor->GetSelectedComponents()->BeginBatchSelectOperation();
                for (UActorComponent* Component : Actor->GetComponents())
                {
                    if (Component)
                    {
                        GEditor->GetSelectedComponents()->Deselect(Component);

                        // Remove the selection override delegates from the deselected components
                        if (USceneComponent* SceneComponent = Cast<USceneComponent>(Component))
                        {
                            FComponentEditorUtils::BindComponentSelectionOverride(SceneComponent, false);
                        }
                    }
                }
                GEditor->GetSelectedComponents()->EndBatchSelectOperation(false);
            }

        }
        SetActorSelectionFlags(Actor);
    }
}

Custom EdMode Panel
The EdMode panel is not as convenient as Toolbar and DetailView. It is usually written in slate code. First is the icon defining EdMode:
Build a class of FMyEdModeStyle. The main purpose of this class is to define style data such as landmarks, fonts and so on. In Slate, it is called SlateImage Brush:

#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( FMyEdModeStyle::InContent( RelativePath, ".png" ), __VA_ARGS__ )

We need a StyleSet in this class:

TSharedPtr< FSlateStyleSet > FLAEEdModeStyle::StyleSet = NULL;

void FLAEEdModeStyle::Initialize()
{
    // Const icon sizes
    const FVector2D Icon8x8(8.0f, 8.0f);
    const FVector2D Icon267x140(170.0f, 50.0f);
    // Only register once
    if (StyleSet.IsValid())
    {
        return;
    }
    StyleSet = MakeShareable(new FSlateStyleSet("FMyEdMode"));
    StyleSet->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate"));
    const FTextBlockStyle NormalText = FEditorStyle::GetWidgetStyle<FTextBlockStyle>("NormalText");
    StyleSet->Set("Plugins.Tab", new IMAGE_BRUSH("icon_40x", Icon40x40));
    StyleSet->Set("Plugins.Mode.Edit", new IMAGE_BRUSH("mode_edit", Icon40x40));
    FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get());
}

Then initialize the StyleSet. Notice that for the EdMode icon, just register Icon in Plugins.Mode.Edit. Finally, Initialize is called in the StartUpModule of the plug-in.

Next is the buttons, icons, etc. on the specific panel. The general approach is to create a new Slate class in the Tookkit members: a subclass of SCompoundWidget:

class SLAEEdModeTools :public SCompoundWidget

We can write all the ui code in the Construct function of this class. In EdMode, we can get our UI Slate class as follows:

auto tools = Toolkit->GetInlineContent().Get();

When building UI, if we need to get the current EdMode data:

auto MyMode = GLevelEditorModeTools().GetActiveMode(FMyEdMode::EM_MyEdModeId);

Specific UI construction can be achieved according to requirements, such as UE4 default layout mode code:

for (const FPlacementCategoryInfo& Category : Categories)
    {
        Tabs->AddSlot()
            .AutoHeight()
            [
                CreatePlacementGroupTab(Category)
            ];
    }

It is to build a tab based on all actor s that can be placed at present.

Unfinished: custom asset, automatic LD

Posted by jeancharles on Sat, 18 May 2019 02:54:52 -0700