Execute arbitrary blueprint code when loading a plugin
This article will show how to run blueprint code automatically when a plugin is loaded using UGameInstanceSubsystem. Required C++ code is minimal and shouldn't need modifications.
Introduction
The purpose of this article is to show how to run arbitrary blueprint code when a plugin is loaded into a game. This code is execute automatically by the engine, and does not require any callback or event hook.
And, most importantly, it does not need to be C++ code, despite depending on the UGameInstanceSubsystem.
Requirements
This method has been tested on the Unreal Engine 5 Early Access, but should be compatible with later UE4 and the final release of UE5 when it comes.
It also requires Visual Studio Community correctly configured, as indicated in the official docs.
Why is this needed?
Come up with a situation
Let's define a possible use case for this. Imagine that you are creating a game, and decide to separate different patches, expansions or content packs into plugins to clean up your environment. Or maybe the base project is used as a template, with plugins acting as "actual" games that use the common resources in the template.
The base project is the one that contains elements like the inventory system, or the travel map. However, the specific inventory items or map locations are defined in each plugin, using data tables or other similar mechanism.
In order to coordinate this, you create a custom GameInstance (in the base project) that contains maps or arrays as a sort of "global database" of items and locations. Each plugin then has to populate this global database with its game elements, so that the global systems defined in the base project have data to work with.
The problem appears
Now the problem is: How do we tell the plugins to do their thing?
If we create a different game instance in the plugin, it will work, but only for one plugin at a time. We could opt for the hacky solution of adding a dummy actor for each plugin to the main menu scene, and launch the process from there, but this isn't scalable, nor a good practice.
What we need, ideally, is a GameInstanceSubsystem, as explained here, that automatically instances itself when instancing the main game instance.
However, by default that class is only exposed on C++, not to blueprints. And trying to do anything in C++ is a one-way ticket to mental insanity.
Implementation
Step 1: Create Visual Studio projects and a new class
Start by generating the required scaffolding for C++ classes in your blueprint project by using the Tools -> New C++ class... menu item inside the Unreal Editor.
On the dialog that appears, click on All Classes and find GameInstanceSubsystem, then click Next.
Now, choose Public for the Class Type, and write a name for the class, like PluginInitializer. Finally, click Create Class.
Navigate to the directory where your Unreal project is located, and a .sln file should have appeared. Open it with Visual Studio. Among the projects that open, you should be able to find one named after your game, inside of which will be PluginInitializer.h and PluginInitializer.cpp.
Step 2: Write (only a bit) of C++
First, open the PluginInitializer.h file, and locate the class definition, that looks like this:
class YOURPROJECT_API UPluginInitializer : public UGameInstanceSubsystem
{
GENERATED_BODY()
};
YOURPROJECT is automatically populated by Unreal with the name of your game project.
After the GENERATED_BODY() line, paste the following code:
public:
// Begin USubsystem
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
// End USubsystem
UFUNCTION(BlueprintNativeEvent, meta = (DisplayName = "Initialize plugin", Keywords = "Initialize Plugin"), Category = Game)
void InitializePlugin(UGameInstance* GameInstance);
This snippet does two things:
- Declare the Initialize function, that is called by the runtime when creating the Game Instances. You don't have any control over the lifetime of this function.
- Declare an empty function to contain the blueprint code you want to run. The UFUNCTION line is some C++ technobabble that allows blueprints to implement the body of the function themselves.
Then, just above the class definition, add this line:
UCLASS(Blueprintable, BlueprintType)
This again is C++ dark magic that indicates to the Editor that this class is valid as the parent class of a blueprint.
The complete source file should look like this:
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "PluginInitializer.generated.h"
/**
* This class is designed to be used as parent of
* one blueprint per plugin. The InitializePlugin
* function can then be overriden to run arbitrary
* code when the plugin is loaded.
*/
UCLASS(Blueprintable, BlueprintType)
class YOURPROJECT_API UPluginInitializer : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
// Begin USubsystem
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
// End USubsystem
UFUNCTION(BlueprintNativeEvent, meta = (DisplayName = "Initialize plugin", Keywords = "Initialize Plugin"), Category = Game)
void InitializePlugin(UGameInstance* GameInstance);
};
Then, open the PluginInitializer.cpp file, that currently contains only one line of code, and replace its contents with this:
#include "PluginInitializer.h"
void UPluginInitializer::Initialize(FSubsystemCollectionBase& Collection)
{
InitializePlugin(GetGameInstance());
}
void UPluginInitializer::InitializePlugin_Implementation(UGameInstance* GameInstance)
{
}
This code does the following things:
- Implement the runtime-called Initialize function to instead call your blueprint function. It also passes a reference to the base Game Instance, as it seems somehow impossible to acquire it from the blueprint flow.
- Define an empty body for the blueprint function, with a _Implementation suffix in its name. This is probably required by something in the engine.
Step 3: Finally, back to the blueprints!
Go back to the editor, and create a new blueprint class inside one of the plugins. You should be able to select the PluginInitializer class as parent.
Inside the Event Graph of this class, you should see an overridable function, with the name Initialize plugin. Clicking that will add a new node "Event Initialize plugin" to the graph, with the parameter for the Game Instance.
From this point, you can use normal blueprint elements to define the logic that needs to run when initializing the plugin.
Also note that the Game Instance reference can be casted to a custom Game Instance, as long as it is correctly set in the Project Settings.