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
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