Create your own editing mode in Unreal Engine

  • Tutorial


Hello my name is Dmitry. I make 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 predefined sections of the level. But the question arises how to set the areas where the level will be generated and where not. To do this, I created my own editing mode, under the cut, I will describe its creation, the sources 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 GetActiveTool() const { return ActiveTool; }
	void SetActiveTool(TSharedPtr ActiveTool);
	void ApplyBrush(FEditorViewportClient* ViewportClient);
	static FEditorModeID EM_EdModeCustom;
private:
	UMaterial* OverlayMaterial;
	void UpdateGridCursorPosition(const FSceneView* View, FViewport* Viewport);
	ATestAct* FindTestActorOnScen();
	TSharedPtr ActiveTool;
	FIntVector GridCursorPosition; 
	bool bToolActive;
	class SCustomEdit* EditPanel;
};

Enter and Exit - are called when entering and leaving editing mode. When we enter the editing mode, we create mode interface elements that will be displayed in its tab. These elements are described in the SCustomEdit.h file, I will not list it here.

InputKey - is called when events are received from input devices, that is, they clicked the button, received the IE_Pressed event, released, received IE_Released.

Render - draws a scene.

IsSelectionAllowed - Determines whether objects can be selected or not.

ActorSelectionChangeNotify - called if the selection still happened

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

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

For added convenience, I created two tools. The first tool is Paint. Using it, we select cells on which we don’t need to generate a level (Shift inverts the tool). The second tool is Select. With it, you can arrange items on selected cells. Each tool has its own Render () and InputKey () methods, which we call from the corresponding editing mode methods.

But just like that, the new editing mode will not appear. For it to appear, you need to register it in the StartupModule () method.
void FLevelDrawEditorModule::StartupModule()
{
	FCustomCommands::Register();
	FEditorModeRegistry::Get().RegisterMode
		(
			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 such a menu here:
image

Now the most important thing is where to store the received information. Information is stored in the TestAct object. It needs to be placed on the stage if several TestActs are placed 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 that will be displayed.

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

You may ask, why did I attach 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 only I would attach this object to edit mode. That settings were reset every time there was a switch to another mode.

In addition, you may notice that the pointer to the UModeSettings object is indicated as UPROPERTY (Transient). The Transient parameter prevents the editor from saving the UModeSettings object when the save button is clicked. But what is UPROPERTY for?

Important: Unreal Egine has a garbage collector that deletes all objects if there is not a single pointer defined as UPROPERTY left to point to this object. Also, the object can be protected using the RF_RootSet flag.
YourObjectInstance->SetFlags(RF_RootSet);

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

LevelMap - Actually in this object information about the cells is saved. This object consists of four two-dimensional arrays, one for each quarter of the plane. Here, the UPROPERTY parameter is already needed so that when you click on the button to save the map, 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 actually all for further study I recommend looking at the source.

The source code project is here .

Also popular now: