📜 ⬆️ ⬇️

Creating your own editing mode in the Unreal Engine



Hello, my name is Dmitry. I create computer games on the Unreal Engine as a hobby. My game has a procedurally generated world, but in order to create a more interesting level, I decided to use pre-defined parts of the level. But the question arises how to specify the plots where the level will be generated, and where not. For this, I created my own editing mode, under the cut I will describe its creation, the source code at the end of the article.


So let's start by creating a new editing mode:
class FEdModeCustom : public FEdMode { public: FEdModeCustom(); ~FEdModeCustom(); virtual void Tick(FEditorViewportClient* ViewportClient,float DeltaTime) override; // FEdMode interface virtual bool UsesToolkits() const override; void Enter() override; void Exit() override; virtual bool InputKey( FEditorViewportClient* InViewportClient, FViewport* InViewport, FKey InKey, EInputEvent InEvent ) override; virtual bool HandleClick(FEditorViewportClient* InViewportClient, HHitProxy *HitProxy, const FViewportClick &Click ) override; virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) override; //FEdMode: Render elements for the Draw tool virtual bool IsSelectionAllowed(AActor* InActor, bool bInSelection) const override; //Check to see if an actor can be selected in this mode - no side effects virtual void ActorSelectionChangeNotify() override; //Called when the currently selected actor has changed virtual bool ShouldDrawWidget() const override; virtual bool InputDelta(FEditorViewportClient* InViewportClient, FViewport* InViewport, FVector& InDrag, FRotator& InRot, FVector& InScale) override; // End of FEdMode interface //Render void DrawPrevewGird(FPrimitiveDrawInterface* PDI); void DrawBlankCells(FPrimitiveDrawInterface* PDI); class ATestAct* EditorModel; void ForceRealTimeViewports(const bool bEnable, const bool bStoreCurrentState); TSharedPtr<class FCustomToolBase> GetActiveTool() const { return ActiveTool; } void SetActiveTool(TSharedPtr<FCustomToolBase> ActiveTool); void ApplyBrush(FEditorViewportClient* ViewportClient); static FEditorModeID EM_EdModeCustom; private: UMaterial* OverlayMaterial; void UpdateGridCursorPosition(const FSceneView* View, FViewport* Viewport); ATestAct* FindTestActorOnScen(); TSharedPtr<class FCustomToolBase> ActiveTool; FIntVector GridCursorPosition; bool bToolActive; class SCustomEdit* EditPanel; }; 

Enter and Exit are called when entering and exiting edit mode. When entering the edit mode, we create interface elements for the mode that will be displayed in its tab. These elements are described in the SCustomEdit.h file, I will not give it here.
')
InputKey - called when receiving events from input devices, that is, pressing the button received the event IE_Pressed released, received IE_Released.

Render - draws the scene.

IsSelectionAllowed - Determines whether objects can be selected or not.

ActorSelectionChangeNotify - called if the selection still occurred

ShouldDrawWidget - Determines whether the widget should be drawn on the object or not (A widget is such a thing with three colored arrows that appears on the selected object if you move it.)

InputDelta - Prevents the camera from moving if one of the mouse buttons is held down.

For more convenience, I created two tools. The first tool is Paint with the help of it, we select cells on which it is not necessary to generate a level (Shift inverts the tool). The second tool is Select with the help of it you can arrange elements on selected cells. Each tool has its own methods Render () and InputKey (), which we call from the corresponding editing mode methods.

But just like that the new editing mode will not appear. In order for it to appear, it must be registered in the StartupModule () method.
 void FLevelDrawEditorModule::StartupModule() { FCustomCommands::Register(); FEditorModeRegistry::Get().RegisterMode<FEdModeCustom> ( FEdModeCustom::EM_EdModeCustom, FText::FromString("Level Draw"), FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.ViewOptions", "LevelEditor.ViewOptions.Small"), true, 400 ); //Registrate detail pannel costamization for TestActor FTestActDetailCustomization::RegestrateCostumization(); } 


After that, we will have this menu:
image

Now the most important thing is where to store the information received. Information is stored in the TestAct object. It needs to be placed on the stage. If you place several TestAct on the stage, you can switch between them by clicking on their icons.
 UCLASS() class LEVELDRAW_API ATestAct : public AActor { GENERATED_BODY() public: ATestAct(); UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gird) FVector GridCellSize; UPROPERTY(Transient) UModeSettings* ModeSettings; UPROPERTY() FLevelMap LevelMap; private_subobject: #if WITH_EDITORONLY_DATA UPROPERTY() UBillboardComponent* SpriteComponent; #endif //WITH_EDITORONLY_DATA }; 

So what we have:

GridCellSize - This parameter determines the size of the cells to be displayed.

ModeSettings - The parameters of this object are displayed on the properties panel of the edit mode. It sets two points with which you can highlight the grid itself, and also displays the name of a specific TestAct and a button for erasing data.

You may ask why I attached this object to TestAct and not to the editing mode itself? The fact is that the editing mode “lives” only when you switch to it. And if I had attached this object to the edit mode. That setting was reset every time there was a switch to another mode.

In addition, you can see that the pointer to the UModeSettings object is designated as UPROPERTY (Transient). The Transient parameter does not allow the editor to save the UModeSettings object when you click save. But what is UPROPERTY for?

Important: in Unreal Egine there is a garbage collector that removes all objects if there is no pointer defined as UPROPERTY pointing to this object. You can also protect an object using the RF_RootSet flag.
 YourObjectInstance->SetFlags(RF_RootSet); 

Of course, all of the above applies only to objects inheriting from a UObject. If the object is not the heir of UObject, the garbage collector does not threaten it.

LevelMap - Actually it is in this object that information about cells is stored. This object consists of four two-dimensional arrays, one for each quarter of the plane. This is where the UPROPERTY parameter is needed so that when you click on the save map button, the FLevelMap object is saved.

SpriteComponent - Just a link to the icon that will be displayed on the editor screen if the ATestAct object is placed on the map.

That's all for further study, I recommend watching the source.

Source project here .

Source: https://habr.com/ru/post/280318/


All Articles