Best Practices for Creating and Using Plugins - jgoffeney/Cesium4Unreal GitHub Wiki

Back

Source

Prerequisites & Creating Code Projects

Goal of source is to create plugins for extending the editor.

Prerequisites

Required

  • Unreal Engine 4
  • Visual Studio or other IDE

Recommended

  • Unreal Engine 4 Source Code
  • Refactoring Tools
  • UnrealVS Extension

Creating a Code Project

The course was designed around the 4.15 version of the engine so creating a project is a little different. The gist is to create a blank C++ project without starter content.

Project Structure

Directories

  • Binaries
    • Compiled game libraries and debug database
  • Config
    • Default configuration files (.ini)
  • Content
  • Content asset packages
  • Intermediate
    • Temporary files
  • Saved
    • Runtime configuration files & log files
  • Source
    • C++ source code

Files

  • {ProjectName}.uproject
    • Project descriptor file
    • JSON formatted file
  • {ProjectName}.sln
    • Visual Studio solution file
  • {ProjectName}.Target.cs
    • Build target configuration for the game
  • {ProjectName}.Editor.Target.cs
    • Build target configuration for the editor
  • {ProjectName}.Build.cs
    • Build rules and configuration
  • {ProjectName}.cpp/.h
    • Main project module implementation

Adding Code Plug-ins to Projects

Create a new blank plugin via the Plugin Wizard. The plugin will show up in the Plugin menu under Project->Other. The code is within the Plugins directory of the project.

The file structure for the plugin and it has a similar structure to the overall project.

Directories

  • Binaries
    • Contains the compiled .dll file for the plugin.
  • Resources
    • Contains the pluging menu icon image

Files

  • {PluginName}.uplugin
    • The equivalent of the {ProjectName}.uproject file to describe the plugin.

Modules & Engine Structure

Files Continued

Under {ProjectName}->Source is another directory called {ProjectName}. That is because the project itself is generated as a module. The module format is repeated throughout Unreal Engine. This structure was added for UE4.

Under {ProjectName}->Plugins->{PluginName}->Source is similarly a directory named {PluginName} which contains Public and Private directories. These are used to store code to expose the API for use by other modules and hide the implementation, respectively for the plugin. It is not necessary to expose any functionality.

Engine Structure

Module Types

Within the engine the modules are divided into the following types:

  • Developer
    • For any application but only used during development
  • Editor
    • For use in the Unreal Editor
  • Runtime
    • For any application at any time
  • ThirdParty
    • Code and libraries from external third parties

The editor modules are prohibited by the EULA to be included in any shipped games or apps.

General bottom line is there is not a plugin API. Instead it is the same structure as the rest of Unreal.

Cloning GitHub Plugins & Text Asset Demo

Clone the example plugin from github into your projects plugins directory. After cloning the plugin then from within the Unreal editor go to File->Refresh Visual Studio Project to update the solution. Basically everything within the project folder will be included in the project if possible.

A TextAsset folder is now within the Plugins folder of your project.

From Unreal compile the project. NOTE: in Unreal 4.6.2 I had an error in which IPluginManager.h was not found within a file. Replace the include with #include "Interfaces/IPluginManager.h" and compile again. Also the Unreal Editor will not automatically detect a plugin loaded this way and instead requires a restart.

After restarting the plugin appears in the Plugin menu under Project->Editor. From within the Content Browser open the Add/Import menu, scroll to Miscellaneous and select Text Asset to create a new TextAsset object which when you open you can save some text into it. So this plugin as added new functionality to the editor.

Creating New Asset Types & Factories

Continuing with the TextAsset example, when looking at the code the TextAsset plugin contains two modules within its Source directory as TextAsset and TextAssetEditor.

Asset Types Overview

Common Tasks

  • Declare the asset type's C++ class
    • This is the asset
  • Implement asset factories
    • This is how users create an instance of the asset
  • Customize asset appearance in the Editor
    • Thumbnails, colors, detail customizations, filtering, categorization, etc.
  • Asset-specific Content Browser
    • Right-click behavior
  • Advanced: Custom asset editor UI
    • For complex asset types

Declaring Asset Type

Looking at TextAsset.h the class is of type UObject. Unreal has a C++ framework for reflection, introspection and garbage collection called the UObject Subsystem.

By naming convention all UObject derived classes start with the letter U. This is required. If it is derived from Actor then it should start with an A and all others start with F.

Things of interest in the code snippet:

  • #include "UObject/Object.h"
    • Allows access to UObject
  • TEXTASSET_API
    • Generated automatically to export the type UTextAsset out of the module so it can be used in other modules
  • UTextAsset
    • The class name with U prepended to indicate it is from UObject
  • UCLASS
    • A macro to tell the Unreal Build Tool (UBT) this is a UObject class that needs to be parsed to generate reflection code
    • The BlueprintType parameter will cause the UBT to generate the code to expose it to the Blueprint system
  • UPROPERTY
    • A macro to tell the UBT this is a variable that needs to be parsed to generate reflection code
    • The BlueprintReadOnly parameter tells the UBT to expose the variable to Blueprint but only for reading (codewise). EditAnywhere indicates the value is exposed for editing anywhere in the editor.
  • #include "TextAsset.generated.h"
    • When the Unreal Build Tool completes parsing the header then when it generates the reflection code it adds it to the header file.
    • This has to be the final included file in the header

NOTE: the macros themselves don't do anything in C++ but are just keywords for UBT to look for.

#include "Internationalization/Text.h"
#include "UObject/Object.h"
#include "UObject/ObjectMacros.h"

#include "TextAsset.generated.h"

/**
 * Implements an asset that can be used to store arbitrary text, such as notes
 * or documentation.
 */
UCLASS(BlueprintType, hidecategories=(Object))
class TEXTASSET_API UTextAsset
	: public UObject
{
	GENERATED_BODY()

public:

	/** Holds the stored text. */
	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="TextAsset")
	FText Text;
};

Asset Factories

UFactory

UFactory is the base class for all asset factories. It has all the core logic for integration with the editor. When the editor needs to perform an action it calls into your factory which has the code to respond to the action (you don't write drag and drop but just override a function that responds to a drag and drop).

Factory Types

The factory type indicates how the user should make a new instance in the editor.

  • Content Browser Context Menu
    • Generate new instance as from editor right-click menu
    • Name: {TypeName}FactoryNew
  • Content Browser Drag and Drop
    • Files on disk dragged into the editor
    • Name: {TypeName}Factory
  • AutomaticReimport
    • Recreate assets when files on disk changed
    • Name: Reimport{TypeName}Factory

For the example within Source/TextAssetEditor/Private/Factories/TextAssetFactoryNew.h is the code for the right-click access and override the functions FactoryCreateNew and ShouldShowInNewMenu.

  • FactoryCreateNew
    • Function called by the editor to create and return a new instance.
    • It uses a templated function NewObject to create the new instance where T is the asset class like UTextAsset.
  • ShouldShowInNewMenu
    • Function to indicate if the asset class should appear in the Add/Import menu.
    • Just returns either true or false.
  • Constructor
    • Within the constructor there is a little initialization. It needs to indicate what class the factory supports, whether it makes a new instance vs. drag and drop, and whether the name of the asset is editable.
    • A certain amount of this is due to the base UFactory class being legacy code (15+ years old). It is also why you can't have a single factory to handle context menu, drag/drop and reimport but instead need a factory for each.
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.

#include "TextAssetFactoryNew.h"

#include "TextAsset.h"


/* UTextAssetFactoryNew structors
 *****************************************************************************/

UTextAssetFactoryNew::UTextAssetFactoryNew(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	SupportedClass = UTextAsset::StaticClass();
	bCreateNew = true;
	bEditAfterNew = true;
}


/* UFactory overrides
 *****************************************************************************/

UObject* UTextAssetFactoryNew::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
	return NewObject<UTextAsset>(InParent, InClass, InName, Flags);
}


bool UTextAssetFactoryNew::ShouldShowInNewMenu() const
{
	return true;
}

The code for drag and drop support is within TextAssetFactory.h. The Formats variable is part of UFactory an indicates which file formats are supported. The function FactoryCreateFile() loads a text file and then generates a new TextAsset object.

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.

#include "TextAssetFactory.h"

#include "Containers/UnrealString.h"
#include "TextAsset.h"
#include "Misc/FileHelper.h"


/* UTextAssetFactory structors
 *****************************************************************************/

UTextAssetFactory::UTextAssetFactory( const FObjectInitializer& ObjectInitializer )
	: Super(ObjectInitializer)
{
	Formats.Add(FString(TEXT("txt;")) + NSLOCTEXT("UTextAssetFactory", "FormatTxt", "Text File").ToString());
	SupportedClass = UTextAsset::StaticClass();
	bCreateNew = false;
	bEditorImport = true;
}


/* UFactory overrides
 *****************************************************************************/

UObject* UTextAssetFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled)
{
	UTextAsset* TextAsset = nullptr;
	FString TextString;

	if (FFileHelper::LoadFileToString(TextString, *Filename))
	{
		TextAsset = NewObject<UTextAsset>(InParent, InClass, InName, Flags);
		TextAsset->Text = FText::FromString(TextString);
	}

	bOutOperationCanceled = false;

	return TextAsset;
}

Customizing Assets with Asset Actions

Anatomy and Life Cycle of Modules

Anatomy of Modules

{ModuleName}.Build.cs

When a project is started it searches all the project directories for *.Build.cs files and creates a list of all the available modules. Then it compiles the build files and executes them. C# build files are used to maintain platform independence. It operates like CMake or QMake in which a platform independent file provides the project definitions for including file and libraries which is then converted into a platform dependent makefile.

  • ModuleRules
    • The required base class
  • PublicDependencyModuleNames
    • A collection of other module names this module depends upon
    • It gives the compiler an order as the dependent modules have to be built first
  • PrivateIncludePaths
    • A collection of directories
    • Any subfolders for a specified folder will not automatically be used for the private context. They would be for public.

{ModuleName}Module.cpp

  • IModuleInterface
    • The base interface class
  • StartupModule
    • Called by the engine each time the module is loaded into memory
    • This is a good place to do any initialization that needs to happen prior to use such as registering actions
  • ShutdownModule
    • Called by the engine each time the module is unloaded from memory. This would be when the engine shuts down or if the module is hot reloadable.
    • Use to unregister actions.
  • IMPLEMENT_MODULE

Within the example the StartupModule function is responsible for adding the module to the list of modules available to the editor. The ShutdownModule removes it.

Slate UI Framework & Widget Reflector

Custom Asset Editors Code Exploration

⚠️ **GitHub.com Fallback** ⚠️