Editor API Proposal Part 1 (Menu Extensions)

Hi Guys,

Because this is a little controversial, I’m going to preface this by saying:

I have used UE4 since 4.0 and love it to bits. I have seen it grow and mature over the last 8 versions to become an absolute powerhouse of an engine for AAA and indies alike. The community around Unreal is incredible and Epic’s ability to take feedback, and at least consider it, is excellent. I use both BP and C++, both of which I enjoy. I regard myself as a competent programmer, having led small and large teams on small and large game projects as a lead programmer, and also being a senior programmer in small and large teams. This proposal is part 1. I want to do more, as I have more ideas, but I want to get the ideas fully formed in my head before I put them out there for people to tear up :wink:

The following is collated from speaking to developers that are working on titles in Unreal, and collating some of their pain points. Also note that some of these people are not programmers, but are very talented technical artists and designers, for example, that when given a tool like Maya or Cinema4D, use the API provided by those DCC tools, to build some of the most useful and extensive extensions to those products.

I’m posting this to get some discussion going and I know it won’t be popular to some, but I believe it will have some benefits and is worth exploring, discussing and perhaps implementing, so here goes.

Proposal:

Epic (or someone) implements an Editor API. The API would be a simplified subset of the existing editor code base. The existing code base for extending the editor would stand and remain the domain of more advanced editor extension work. The benefits of this would be as such:

  • Simpler way to immediately extend the editor for new comers
  • Existing developers would minimize the amount of code they need to write to perform rudimentary editor extensions
  • Given a well defined EditorAPI, Epic would be free to modify the underlying code, without breaking existing third-party editor plugins

So how would it work? How would you use the EditorAPI? Well here is my proposal for setup, and proposal for the first area of interest, writing editor menu extensions.

NOTE: Writing this in quasi-psuedo-code, I’m not attempting to write good code here.

Setting up a class to use EditorAPI:

Header:



#pragma once
#include “EditorAPI.h”
#include “MyEditorExtension.generated.h”

UCLASS( ClassGroup=(EditorExtension), meta=(EditorTimeNotPIE))
class UMyEditorExtension : public UEditorExtension
{
    GENERATED_BODY()

    public:

    UMyEditorExtension();

    virtual void TickEditorExtension( float deltaTime ) override;
}


CPP:



#include “MyEditorExtension.h”

// Set up some things
UMyEditorExtension::UMyEditorExtension()
{
}

// Receive a tick from the editor, each time it updates
void UMyEditorExtension::TickEditorExtension( float DeltaTime )
{
}



The above would form the minimal version of an editor extension, with this version, you would already be able to run logic within the editor on each tick, allowing you to do some “interesting things” quickly and easily.

So what about adding new menus to the editor’s main menu? Well you would simply add a UFunction with some special markup. Like this:

Header:



#pragma once
#include “EditorAPI.h”
#include “MyEditorExtension.generated.h”

UCLASS( ClassGroup=(EditorExtension), meta=(EditorTimeNotPIE))
class UMyEditorExtension : public UEditorExtension
{
    GENERATED_BODY()

    public:

    UMyEditorExtension();

    virtual void TickEditorExtension( float deltaTime ) override;

    // Add a menu item to the main menu
    UFUNCTION(MainMenuItem=“My Tools|Actor Stuff|Move To Origin”)
    void MoveToOrigin();
}


CPP:



#include “MyEditorExtension.h”

// Set up some things
UMyEditorExtension::UMyEditorExtension()
{
}

// Receive a tick from the editor, each time it updates
void UMyEditorExtension::TickEditorExtension( float DeltaTime )
{
}

// Move selected actors to origin
void UMyEditorExtension::MoveToOrigin()
{
    // Call the EditorAPI to get selected Actors in the editor
    TArray<AActor*> SelectedActors = EditorAPI::GetSelectActors();

    for (auto& CurrentActor : SelectedActors)
    {
        CurrentActor->SetActorLocation(FVector::ZeroVector);
    }
}



What about the context menu, when you right-click on an actor?

Header:



#pragma once
#include “EditorAPI.h”
#include “MyEditorExtension.generated.h”

UCLASS( ClassGroup=(EditorExtension), meta=(EditorTimeNotPIE))
class UMyEditorExtension : public UEditorExtension
{
    GENERATED_BODY()

    public:

    UMyEditorExtension();

    virtual void TickEditorExtension( float deltaTime ) override;

    // Add a menu item to the main menu (with context menu specifier)
    UFUNCTION(MainMenuItem=“My Tools|Actor Stuff|Move To Origin”, ActorContextMenuItem="Actor Stuff|Move To Origin")
    void MoveToOrigin();
}


CPP:



#include “MyEditorExtension.h”

// Set up some things
UMyEditorExtension::UMyEditorExtension()
{
}

// Receive a tick from the editor, each time it updates
void UMyEditorExtension::TickEditorExtension( float DeltaTime )
{
}

// Move selected actors to origin
void UMyEditorExtension::MoveToOrigin()
{
    // Call the EditorAPI to get selected Actors in the editor
    TArray<AActor*> SelectedActors = EditorAPI::GetSelectActors();

    for (auto& CurrentActor : SelectedActors)
    {
        CurrentActor->SetActorLocation(FVector::ZeroVector);
    }
}



So what about menu items with simple controls? Like a checkbox or a slider? Well using a special Type decoration on the UFunction macro, tell the editor to call your function each time an associated menu item control is changed. A corresponding MenuItemValues struct is then retrievable from the EditorAPI, once inside the function body:

Header:



#pragma once
#include “EditorAPI.h”
#include “MyEditorExtension.generated.h”

UCLASS( ClassGroup=(EditorExtension), meta=(EditorTimeNotPIE))
class UMyEditorExtension : public UEditorExtension
{
    GENERATED_BODY()

    public:

    UMyEditorExtension();

    virtual void TickEditorExtension( float deltaTime ) override;

     // Add a menu item to the main menu (with context menu specifier)
    UFUNCTION(MainMenuItem=“My Tools|Actor Stuff|Move To Origin”, ActorContextMenuItem="Actor Stuff|Move To Origin")
    void MoveToOrigin();

    // Add a menu item to the main menu with a checkbox
    UFUNCTION(MainMenuItem=“My Tools|Actor Stuff|Accurate Transforms”, type=(Checkbox))
    void SetAccurateTransforms();
}


CPP:



#include “MyEditorExtension.h”

bool bAccurateTransforms = false;

// Set up some things
UMyEditorExtension::UMyEditorExtension()
{
}

// Receive a tick from the editor, each time it updates
void UMyEditorExtension::TickEditorExtension( float DeltaTime )
{
}

// Move selected actors to origin
void UMyEditorExtension::MoveToOrigin()
{
    // Call the EditorAPI to get selected Actors in the editor
    TArray<AActor*> SelectedActors = EditorAPI::GetSelectActors();

    for (auto& CurrentActor : SelectedActors)
    {
        CurrentActor->SetActorLocation(FVector::ZeroVector);
    }
}

// Set accurate transforms
void UMyEditorExtension::SetAccurateTransforms()
{
    bAccurateTransforms = EditorAPI::GetCurrentMenuItemValues().bIsChecked;

    // ...
}



and sliders?:

Header:



#pragma once
#include “EditorAPI.h”
#include “MyEditorExtension.generated.h”

UCLASS( ClassGroup=(EditorExtension), meta=(EditorTimeNotPIE))
class UMyEditorExtension : public UEditorExtension
{
    GENERATED_BODY()

    public:

    UMyEditorExtension();

    virtual void TickEditorExtension( float deltaTime ) override;

      // Add a menu item to the main menu (with context menu specifier)
    UFUNCTION(MainMenuItem=“My Tools|Actor Stuff|Move To Origin”, ActorContextMenuItem="Actor Stuff|Move To Origin")
    void MoveToOrigin();

    // Add a menu item to the main menu with a checkbox
    UFUNCTION(MainMenuItem=“My Tools|Actor Stuff|Accurate Transforms”, type=(Checkbox))
    void SetAccurateTransforms();

    // Add a menu item to the main menu with a floating point slider
    UFUNCTION(MainMenuItem=“My Tools|Actor Stuff|Tolerance”, type=(FloatSlider), min=0.0, max=1.0)
    void SetTolerance();

    // Add a menu item to the main menu with a integer based slider
    UFUNCTION(MainMenuItem=“My Tools|Actor Stuff|Max Steps”, type=(IntSlider), min=1, max=10)
    void SetMaxSteps();
}


CPP:



#include “MyEditorExtension.h”

bool bAccurateTransforms = false;
float Tolerance = 0.0f;
int MaxSteps = 1;

// Set up some things
UMyEditorExtension::UMyEditorExtension()
{
}

// Receive a tick from the editor, each time it updates
void UMyEditorExtension::TickEditorExtension( float DeltaTime )
{
}

// Move selected actors to origin
void UMyEditorExtension::MoveToOrigin()
{
    // Call the EditorAPI to get selected Actors in the editor
    TArray<AActor*> SelectedActors = EditorAPI::GetSelectActors();

    for (auto& CurrentActor : SelectedActors)
    {
        CurrentActor->SetActorLocation(FVector::ZeroVector);
    }
}

// Set accurate transforms
void UMyEditorExtension::SetAccurateTransforms()
{
    bAccurateTransforms = EditorAPI::GetCurrentMenuItemValues().bIsChecked;

    // ...
}

// Set tolerance
void UMyEditorExtension::SetTolerance()
{
    Tolerance = EditorAPI::GetCurrentMenuItemValues().FloatValue;

    // ...
}

// Set max steps
void UMyEditorExtension::SetMaxSteps()
{
    MaxSteps = EditorAPI::GetCurrentMenuItemValues().IntValue;

    // ...
}


and finally, float, int, string fields and color:

Header:



#pragma once
#include “EditorAPI.h”
#include “MyEditorExtension.generated.h”

UCLASS( ClassGroup=(EditorExtension), meta=(EditorTimeNotPIE))
class UMyEditorExtension : public UEditorExtension
{
    GENERATED_BODY()

    public:

    UMyEditorExtension();

    virtual void TickEditorExtension( float deltaTime ) override;

    // Add a menu item to the main menu
    UFUNCTION(MainMenuItem, path=“My Tools/Actor Stuff/Move To Origin”)
    void MoveToOrigin();

      // Add a menu item to the main menu (with context menu specifier)
    UFUNCTION(MainMenuItem=“My Tools|Actor Stuff|Move To Origin”, ActorContextMenuItem="Actor Stuff|Move To Origin")
    void MoveToOrigin();

    // Add a menu item to the main menu with a checkbox
    UFUNCTION(MainMenuItem=“My Tools|Actor Stuff|Accurate Transforms”, type=(Checkbox))
    void SetAccurateTransforms();

    // Add a menu item to the main menu with a floating point slider
    UFUNCTION(MainMenuItem=“My Tools|Actor Stuff|Tolerance”, type=(FloatSlider), min=0.0, max=1.0)
    void SetTolerance();

    // Add a menu item to the main menu with a integer based slider
    UFUNCTION(MainMenuItem=“My Tools|Actor Stuff|Max Steps”, type=(IntSlider), min=1, max=10)
    void SetMaxSteps();


    // Add a menu item to the main menu with a float based field
    UFUNCTION(MainMenuItem=“My Tools|Actor Stuff|Balance”, type=(FloatField), min=0.0, max=1.0)
    void SetBalance();

    // Add a menu item to the main menu with a int based field
    UFUNCTION(MainMenuItem=“My Tools|Actor Stuff|Actor Count”, type=(IntField), min=1, max=100)
    void SetActorCount();

    // Add a menu item to the main menu with a string based field with max 23 characters and a default of “Prefix”
    UFUNCTION(MainMenuItem=“My Tools|Actor Stuff|Actor Names Prefix”, type=(StringField), max=23, default=“Prefix”)
    void SetActorPrefix();

    // Add a menu item to the main menu with a color based field with a default of 1.0, 1.0, 1.0, 1.0
    UFUNCTION(MainMenuItem=“My Tools|Actor Stuff|Actor Color”, type=(ColorField), default=(1.0, 1.0, 1.0, 1.0))
    void SetActorColor();
}


CPP:



#include “MyEditorExtension.h”

bool bAccurateTransforms = false;
float Tolerance = 0.0f;
int MaxSteps = 1;
float Balance = 0.0f;
int ActorCount = 1;
FString ActorPrefix = “Prefix”;
FLinearColor ActorColor = FLinearColor::White;

// Set up some things
UMyEditorExtension::UMyEditorExtension()
{
}

// Receive a tick from the editor, each time it updates
void UMyEditorExtension::TickEditorExtension( float DeltaTime )
{
}

// Move selected actors to origin
void UMyEditorExtension::MoveToOrigin()
{
    // Call the EditorAPI to get selected Actors in the editor
    TArray<AActor*> SelectedActors = EditorAPI::GetSelectActors();

    for (auto& CurrentActor : SelectedActors)
    {
        CurrentActor->SetActorLocation(FVector::ZeroVector);
    }
}

// Set accurate transforms
void UMyEditorExtension::SetAccurateTransforms()
{
    bAccurateTransforms = EditorAPI::GetCurrentMenuItemValues().bIsChecked;

    // ...
}

// Set tolerance
void UMyEditorExtension::SetTolerance()
{
    Tolerance = EditorAPI::GetCurrentMenuItemValues().FloatValue;

    // ...
}

// Set max steps
void UMyEditorExtension::SetMaxSteps()
{
    MaxSteps = EditorAPI::GetCurrentMenuItemValues().IntValue;

    // ...
}

// Set actor count
void UMyEditorExtension::SetActorCount()
{
    ActorCount = EditorAPI::GetCurrentMenuItemValues().IntValue;

    // ...
}

// Set tolerance
void UMyEditorExtension::SetBalance()
{
    Balance = EditorAPI::GetCurrentMenuItemValues().FloatValue;

    // ...
}

// Set prefix
void UMyEditorExtension::SetActorPrefix()
{
    ActorPrefix = EditorAPI::GetCurrentMenuItemValues().StringValue;

    // ...
}

// Set actor color
void UMyEditorExtension::SetActorColor()
{
    ActorColor = EditorAPI::GetCurrentMenuItemValues().ColorValue;

    // ...
}



That’s it from me. I have a ton of ideas based around this very concept for extending the entire editor. I will continue dropping these if this stuff is welcome and discussed.

Fingers crossed.

Thanks all!

Tommy.

EDIT: Incorporated 's feedback

Would this be better implemented in, say, python as the user facing component? Those artist you mentioned would be used to it from many of the packages, and Unreal has just gotten support for external scripting languages, with Lua as a test, so that would be a possibility as well. This could eliminate some of the overhead of verbosity that comes with writing it to C++.

Personally I think it is a good idea and I don’t see it as controversial … I do however agree with BillDStrong … this would be better if it leveraged scripting languages like LUA or Python. A lot of these types of extensions are written in scripting languages like that.

Sure, I understand your point, but there are other factors. These things also affect me and I have no desire or intention of using python, C# or LUA in UE4. It has C++ already and a totally adequate scripting system with BPs. As I said:

This includes programmers, including myself.

Tommy.

I actually think it is controversial for the editor, especially extensions. For other things not at all. For example, for runtime classes like AActors and AComponents, we already have an API like this and it works well. It’s simplified, let’s people do the most common things with actors and components. For the editor extensions however, not so much. I think leveraging scripting languages for this type of stuff is a bad idea, given we already have C++ and it would also be possible to make these things blueprint callable. Not to mention LUA/Python interfaces will never be part of mainline at this rate and can’t be relied on?

Tommy.

Okay … you make a fair point. 8-}

Look, I don’t know right, I want to shake this tree and hear what people think… Epic too hopefully, this has been on my mind quite a bit. Thanks for the discussion so far though, scripting hadn’t crossed my mind in terms of LUA or Python, so that is interesting.

Tommy.

Support bump for this idea, it would be really great to have the ability to easily extend the editor, especially for creating extensions that can be used to improve your personal game’s workflow, sort of like the slot that blutility at one point seemed to be trying to fill, not sure why that engine feature got pushed to the back burner.

I love idea of using UFUNCTIONS to create menues and toolbars, i would propose little correction to keep it more integrated

insted of seperate option specifier and path specfier use only single specifier same as Category. Also specifiers already use | as a separator insted if /, so i would see it like this

UFUNCTION(ActorContextMenuItem=“Actor Stuff|Move To Origin”)

Oh that’s really nice actually. I guess this would also allow placement in main menu at the same time and go to one UFUNCTION… nice!

Tommy.

Incorporated 's suggestions to the original proposal.

Tommy.