Do not confuse Modular Features with Modular Gameplay Features .

Introduction

In Unreal Engine, Modular Features is a pretty straightforward system that makes it possible create and manage features that can be enabled or disabled at runtime. Features share the same base interface (derived from IModularFeatures), and they can be registered/unregistered at runtime from any part of the code and any module. This system is widely used in UE, for example, in audio capture, where each platform may have its audio capture implementation registered/unregistered based on the target platform, all using the base interface of IAudioCaptureFactory, which is derived from IModularFeatures.

Example of Modular Features

A good example illustrating its utility is in the RuntimeAudioImporter plugin. The plugin’s primary purpose is to encode/decode audio data at runtime, and it supports multiple audio formats within codecs such as OGG, MP3, WAV, etc. Each codec (e.g., FWAV_RuntimeCodec) is a separate feature within Modular Features, all based on the same FBaseRuntimeCodec interface. These codecs can be registered/unregistered at runtime from any part of the code or module. This enables easy extension of the plugin with new codecs like AAC without modifying the plugin’s code at all. So, essentially, the Modular Features system provides a convenient way to create a plugin/module open for extension but closed for modification.

Here’s a simplified example demonstrating how to use Modular Features in Unreal Engine, based on the RuntimeAudioImporter plugin and its codecs as features.

First, let’s define the base interface for the codec features:

class RUNTIMEAUDIOIMPORTER_API FBaseRuntimeCodec : public IModularFeature
{
public:
	FBaseRuntimeCodec() = default;
	virtual ~FBaseRuntimeCodec() = default;

	/**
	 * Encode uncompressed PCM data into a compressed format
	 */
	virtual bool Encode(FDecodedAudioStruct DecodedData, FEncodedAudioStruct& EncodedData, uint8 Quality) PURE_VIRTUAL(FBaseRuntimeCodec::Encode, return false;)
	/**
	 * Decode compressed audio data into PCM format
	 */
	virtual bool Decode(FEncodedAudioStruct EncodedData, FDecodedAudioStruct& DecodedData) PURE_VIRTUAL(FBaseRuntimeCodec::Decode, return false;)
};

Then, let’s implement a codec feature, such as the WAV codec. This can be done in any module, not necessarily in the same module as the plugin:

class RUNTIMEAUDIOIMPORTER_API FWAV_RuntimeCodec : public FBaseRuntimeCodec
{
public:
	//~ Begin FBaseRuntimeCodec Interface
	virtual bool Encode(FDecodedAudioStruct DecodedData, FEncodedAudioStruct& EncodedData, uint8 Quality) override
	{
		// Encode WAV data here
	}
	virtual bool Decode(FEncodedAudioStruct EncodedData, FDecodedAudioStruct& DecodedData) override
	{
		// Decode WAV data here
	}
	//~ End FBaseRuntimeCodec Interface
};

Then, let’s register/unregister the codec feature, for example, in the plugin’s module startup/shutdown (this can be done in any module and at any time):

class FRuntimeAudioImporterModule : public IModuleInterface
{
public:
	virtual void StartupModule() override
	{
		WAV_Codec = MakeShared<FWAV_RuntimeCodec>();

		// Register the WAV codec feature
		IModularFeatures::Get().RegisterModularFeature(FName(TEXT("RuntimeAudioImporterCodec")), WAV_Codec.Get());
	}
	virtual void ShutdownModule() override
	{
		// Unregister the WAV codec feature
		IModularFeatures::Get().UnregisterModularFeature(FName(TEXT("RuntimeAudioImporterCodec")), WAV_Codec.Get());

		WAV_Codec.Reset();
	}

protected:
	TSharedPtr<FWAV_RuntimeCodec> WAV_Codec;
};

Finally, to get the list of registered codecs, you can use the following code:

IModularFeatures::FScopedLockModularFeatureList ScopedLockModularFeatureList;
TArray<FBaseRuntimeCodec*> AvailableCodecs = IModularFeatures::Get().GetModularFeatureImplementations<FBaseRuntimeCodec>(FName(TEXT("RuntimeAudioImporterCodec")));

That’s it! You can implement as many codecs as needed and register/unregister them at runtime, from any module and at any time. This makes it possible to easily extend the plugin with new codecs without any changes to the plugin’s code. Instead of codecs, it could be anything else, depending on your needs of extension.