Unreal Engine: Mod Support using Mutators
Introduction
We wanted to give Developers a way to provide a nice and easy solution for Modders, especially new ones, to hit the ground running. We picked Mutators and, while exploring how we wanted to approach them, we settled on experimenting with a feature of Unreal which is quite powerful but doesn’t have much documentation or examples: UHT Extensions.
The code examples given in this post are written for UE 5.7. While there may be some changes between engine versions the concepts discussed should remain consistent
What even is a Mutator?
A Mutator is a concept taken from Unreal Tournament. They are objects with a number of overridable functions which are called by game code to allow them to modify existing behaviours or even inject new behaviours. Some classic examples of Mutators are LowGrav (lowering the gravity), Vampire (dealing damage heals you), and Big Head (player model head scales with your score). Players could also choose to layer these, creating even more complicated and unique play experiences. We’ve extended the concept further by including support for replacing assets but we will just be talking about the behavioural aspect in this post.
If you look at the implementation in UT4 you can see functions like AddDefaultInventory(), ScoreObject(), and OverridePickupQuery(). Overriding these will lets the Mutator change a player inventory, how scoring works, or even what happens when you collect a pick up. This approach works well. You can just identify the functions you want to support, add equivalent operations to the Mutator, and away you go.
We couldn’t do that for you though. We have no idea what events are present in your game. We could try and anticipate that and add functions for every conceivable event that any game could include but that would be both messy and ineffectual. Instead we wanted to give developers the tools to easily create their own events.
One thing we really wanted to avoid was excessive boilerplate. We could have directed developers to write their own functions using the framework we created but that would lead to tedium or copy/paste/edit, both of which can be error prone.
Templates are usually a good way to help reduce boilerplate code but unfortunately templates don’t work with Blueprint. Ok, if Templates wont work maybe we could go old school and use preprocessor macros. Well we run into timing issue here. When you build a project Unreal will first run some custom tools, such as Unreal Build Tool (UBT) and Unreal Header Tool (UHT), before invoking the compiler toolchain. UHT is responsible for parsing and generating code for a number of things including UMACROs like UFUNCTION(), UPROPERTY(), etc. If we wanted to have a preprocessor macro expand to declare a Blueprint callable function it would have to contain UFUNCTION(BlueprintCallable) but UHT has already finished at that point so no code would be generated for it. What would be really nice would be if we could somehow sneak into UHT and have it generate our code for us, like it does for the UMACROs.
Thankfully we can do just that using UHT Extensions. UHT allows us to create C# plugins in our project that get invoked when UHT runs and gives us access to many of it’s features.
We interrupt this regularly scheduled transmission
Before we dive into how everything works it’s worth talking about our solution so we know exactly what we need to generate.
We have two main components: A Mutator Subsystem and the Mutators themselves. The subsystem is responsible for instantiating, storing, and otherwise managing Mutators. It will also serve as a central location for gameplay code to query, invoke, or otherwise interact with Mutators. The Mutators are objects that have a set of functions that can be overridden by modders and called by the subsystem.
The functions on the Mutators need to have a corresponding function on the subsystem. As we said earlier we wanted to avoid boilerplate as much as possible and minimise subsequent errors. With that in mind we decided to pack the parameters of these functions into a struct. This lets us reuse the same type across both functions without needing to redefine them and, while normally using a type like this would add to the boilerplate, because we are already generating code for the function we can generate the struct at the same time.
After taking stock of everything here is what we need:
- A Blueprint callable function for invoking a mutator event for our subsystem
- A Blueprint native function with a default implementation for our Mutator
- A Blueprint struct for our parameters to these functions
While we define a number of macros to cover these steps not all of them require code generation and can be achieved by just using macros. The definition of the subsystem function doesn’t need any code generation at all and, while we will need to generate code that handles the BlueprintNativeEvent property of the Mutator function, we can create the declaration and definition of this function with macros.
From here we made three macros:
DEFINE_MUTATOR(Name, …)
- Declares a new event in the subsystem with no return value called Name
- The variadic arguments should be type + name pairs (like in delegate macros)
- This doesn’t expand to any C++ code itself but instead is used by the UHT Extension to generate code. This includes:
- A native declaration of a function
- A Blueprint callable thunk for the event which calls the function above
- A struct with members corresponding to the type + name pairs from the arguments to be used as a parameter to the new event
IMPLEMENT_MUTATOR_EVENT(Name)
- Provides a definition of the native event defined by the above macro
- This expands to C++ code that iterates over each mutator and calls a function of the same name on it and doesn’t generate any code
DECLARE_MUTATOR_EVENT_BLUEPRINT(Name)
- Provides an inline definition of a virtual function called Name in the Mutator class
- This expands to C++ code but will also generate code for the
BlueprintNativeEventthunk
There are more pieces to this (we also have macros to create events that return values for instance) but for our purposes they are essentially identical or irrelevant so we are just going to keep it simple and focus on these.
Lets take some time to figure out what that will look like in practice.
//MutatorSubsystem.h
class MODIOUGC_API UMutatorSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
...
DEFINE_MUTATOR(EnemyWaveEnded, int32, WaveNumber)
...
};Here we are defining an event that want to trigger when a wave of enemies is finished and we want to pass it an integer for which wave it was. This is roughly the same as the following
USTRUCT(BlueprintType)
struct FEnemyWaveEnded_Params
{
GENERATED_BODY()
public:
int32 WaveNumber;
};
UFUNCTION(BlueprintCallable)
void EnemyWaveEnded(FEnemyWaveEnded_Params Params);This takes care of the struct definition and the BlueprintCallable function declaration. Now we can handle the definition for this function.
//MutatorSubsystem.cpp
IMPLEMENT_MUTATOR_EVENT(EnemyWaveEnded)In the implementation file we can add the macro that provides the function definition. This expands to a function which just forwards the parameter onto a function of the same name on each active Mutator. Minus some implementation details for clarity that looks something like this
void UMutatorSubsystem::EnemyWaveEnded(FEnemyWaveEnded_Params Params)
{
for(auto Mutator : ActiveMutators)
{
Mutator->EnemyWaveEnded(Params);
}
}You can see we don’t need to know anything about the parameter struct other than the name, which we can derive, meaning we don’t need to respecify the parameter list. Also this function definition is trivial to create just using the preprocessor so we just do that instead of generating code for it.
The last macro will take care of both of the remaining items: the BlueprintNativeEvent declaration and definition.
//Mutator.h
class MODIOUGC_API UMutator : public UObject
{
GENERATED_BODY()
public:
...
DECLARE_MUTATOR_EVENT_BLUEPRINT(EnemyWaveEnded)
...
};This macro pulls double duty. It tells UHT to generate some code and also acts as a regular macro. The second part is easier so we will start with that.
void EnemyWaveEnded(FEnemyWaveEnded_Params Params);
virtual void EnemyWaveEnded_Implementation(FEnemyWaveEnded_Params Params) {}The macro portion expands to the code above and provides the declaration of the BlueprintNativeEvent as well as defining a default implementation of its _Implementation function. For this to be complete though we are missing UFUNCTION(BlueprintNativeEvent). Unfortunately we can’t just add it here but we can generate the equivalent code with our extension.
Now we have all our pieces in place all that’s left to do is call our new event for our game code.
...
if(EnemyCount <= 0)
{
FEnemyWaveEnded_Params Params;
Params.WaveNumber = CurrentWave;
auto MutatorSubsystem = GameInstance->GetSubsystem<UMutatorSubsystem>();
auto DamageEvent = MutatorSubsystem->EnemyWaveEnded(Params);
}
...One of the big reasons we chose to use a subsystem to help manage our Mutators is they are easy to access from anywhere. This makes it easy for us to call our new function from the code that tracks how many enemies are left.
So you want to generate some code?
Now that we have a good idea about what we need to do we can start to figure out how we can do it.
Set up
All of our code for our UHT extension needs to live in a UBT plugin. This is because when you build the project Unreal finds all the UBT plugins, builds them, and makes the code available when UHT runs. So lets set that up first. In your project or plugin make a new folder in your Source directory with whatever name you want to use for the plugin. This will look similar to creating a new module but this time we are going to add a C# project. The project needs to be named <PLUGIN_NAME>.ubtplugin.csproj and will need assembly references to UHT and UBT. There is an example in Engine\\Plugins\\ScriptPlugin\\Source\\ScriptGeneratorUbtPlugin if you want to copy that.
With the project set up we can add our generator class. Nothing fancy here, the only thing you need to make sure you do is add the UnrealHeaderTool class attribute to it. Here is what ours looks like
[UnrealHeaderTool]
public static class UhtMutatorGeneratorInvoking our parsing code
To get UHT to parse our custom macros we need to create a new function that handles the processing of that token. The functions needs to have the following signature:
[UhtKeyword(Extends = UhtTableNames.<Scope>)]
private static UhtParseResult <TokenName>Keyword(UhtParsingScope topScope, UhtParsingScope actionScope, ref UhtToken token)where TokenName is the name of the Macro and Scope determines where the token is valid (for instance UPROPERTY is valid in classes and structs so UhtTableNames.Class and UhtTableNames.ScriptStruct are it’s scopes. If a keyword has multiple scopes you can add a UhtKeyword attribute for each of them). Valid scopes are defined in UhtParsingScope.cs
For our DEFINE_MUTATOR() macro our function is declared as follows:
[UhtKeyword(Extends = UhtTableNames.Class)]
private static UhtParseResult DEFINE_MUTATORKeyword(UhtParsingScope topScope, UhtParsingScope actionScope, ref UhtToken token)The UhtParseResult signals whether or not this function has handled the parsing of this token (Handled), if other functions should attempt to parse it (Unhandled), or if it was invalid (Invalid).
UhtParsingScopes are a little more complicated but essentially are what they sound like: they contain information regarding the scope at which the token is being parsed. It is a logical view of it, rather than a concrete one, which allows us to inject new information into the scope that doesn’t exist in the actual file. Among other useful information, the scope also has a reference to the Token Reader so we can parse any additional information that we need ourselves if necessary.
Parsing Scopes contain data about the declarations made in it and this data is used to generate code. If you have messed around with Unreal Engines reflection system at all you may find the structure familiar but, essentially, there is a type for each construct you want to be able to track i.e. for function you have UhtFunction, for structs you have UhtScriptStruct, etc.
There are a number of other UHT attributes that can be used to define other things (we will touch on another one, UhtExporter, later).
Parsing DEFINE_MUTATOR
The three steps we need to do are interleaved but we think it’s simpler to talk about each one separately instead of in a big monolith slab of text.
Creating the struct
The first thing we need to do to make a struct is to construct it
UhtScriptStruct ParamStruct = new(Scope.HeaderFile, Scope.HeaderParser.GetNamespace(), Scope.ParentScope.ScopeType, Token.InputLine);This is the constructor for any UhtType subclass. The first two parameters are the header file and namespace this new type belongs to. Pulling these from the scope will be fine in most cases. The second two are the types outer and the line number it is defined on. Scope is, in this case, the class where our macro is being parsed from. Using the same header file makes sense but we don’t want to declare the struct inside the class so we will use the parent scope of the class instead. For the last parameter we don’t have a line number for the struct itself (since it doesn’t actually have any lines of code) so will just use the same line number as the macro token.
Next we need to configure ParamStruct to do want we need
ParamStruct.EngineName = Name.Value.ToString() + "_Params";
ParamStruct.SourceName = "F" + ParamStruct.EngineName;
ParamStruct.MacroDeclaredLineNumber = Token.InputLine;
ParamStruct.ScriptStructFlags |= EStructFlags.Native | EStructFlags.NoExport;
ParamStruct.MetaData.Add(UhtNames.BlueprintType, true);EngineName and SourceName are properties of UhtType and they refer to the name for this type in Unreal’s type system and in the source code respectively. We will use the name of the event (don’t worry about where we got that from, we’ll get to that later) with a _Params suffix for our structs name. This could be anything but having it be related to the event makes sense and will be useful later. Unreal expects structs to begin with F so we add that prefix to the source name now.
Now we get to struct specific properties. MacroDeclaredLineNumber is the line number where the GENERATED_BODY macro is found. Again we don’t have one of those so we just use the same line number we did before. It is worth noting that this value does need to be set to a non-negative value as it is used to declare some macros and left default this can cause some issues.
All UhtTypes have specific Flag types. Here, on a ScriptStruct, we have ScriptStructFlags. We want to make this a native struct and, because we are generating this ourselves, we will also mark it as NoExport. And finally we want this to be a Blueprint type. UhtType::MetaData seems to store values for a lot of the shared UMACRO specifiers but all we need here is to set UhtNames.BlueprintType to true.
With that out of the way we can move on to filling out the members of our new struct. To do that we are going to have to talk about UhtProperty ’s. UHT code generation uses UhtProperty’s to refer to any kind of variable being defined for a type. This includes class/struct members, function parameters and return types, etc. Properties are polymorphic, so if you want an int property you make a UhtIntProperty and so on, though as you’ll see later you typically don’t need to deal with them directly.
To get our members we are going to have to take each pair of arguments of the macro we are parsing (the first being the type, the second being the name of the member) and convert those into properties. Once we’ve made the properties we can add them to the struct using
ParamStruct.AddChild(Property);Creating Properties
We skipped over how to create a property and have separated it out into it’s own section because it is a little involved and requires explanation.
To create a new property we need to start by creating property settings for each property
UhtPropertySettings MemberPropertySettings = new UhtPropertySettings();
MemberPropertySettings.SourceName = NameToken.Value.String;
MemberPropertySettings.EngineName = MemberPropertySettings.SourceName;
MemberPropertySettings.LineNumber = NameToken.InputLine;
MemberPropertySettings.PropertyCategory = UhtPropertyCategory.Member;
MemberPropertySettings.Outer = ParamStruct;The first three properties should be familiar to you. There are no special naming requirements for struct members so we just use the same name for each here. We tell it that this property is a member of a type and we set the outer to that type. There are PropertyFlags but we don’t need any here (thought they will make an appearance later).
Now that we have our settings for our property we can get to actually making it and this is where the magic happens. It was mentioned earlier that we wouldn’t have to deal with property classes directly and that’s because we can use helper functions to find the appropriate type for a property and construct a property of that type.
Scope.Session.TryGetPropertyType(TypeString, out UhtPropertyType PropertyType)TryGetPropertyType() takes a type as a string and gives you the corresponding property type if it finds it. It will take any string but we used the value of the type token for each pair.
To use our property type to make a new property we can use UhtPropertyType::Delegate to create a new instance
UhtPropertyResolveArgs Args = new(UhtPropertyResolvePhase.Parsing, PropertySettings, TokenReader);
UhtProperty? OutProperty = PropertyType.Delegate(Args);Because we are currently parsing the file we want to use UhtPropertyResolvePhase.Parsing here (we’ll talk about that more in a bit) and PropertySettings are the settings we made before. The only new thing here we need to worry about is the TokenReader. If you look at the type section of a declaration like const int * it’s possible to have multiple tokens contribute qualifiers. Rather than have every combination of these as individual types Unreal will use a UhtIntProperty set to be const and a pointer to represent that property. The TokenReader provides the delegate with all the tokens needed. Because the current TokenParser has already read these tokens we will have to construct a UhtTokenReplayReader using the tokens.
UhtToken DeclarationTerminationToken = new UhtToken(UhtTokenType.Symbol);
DeclarationTerminationToken.Value = ";";
Tokens.Add(DeclarationTerminationToken);
IUhtTokenReader TokenReader = new UhtTokenReplayReader(Scope.HeaderFile, Scope.HeaderFile.Data.Memory, new ReadOnlyMemory<UhtToken>(Tokens.ToArray()), UhtTokenType.EndOfDeclaration);Note that we explicitly add a terminating ; symbol here. This is because we are taking segments of a statement and treating them like whole declarations. Without this the reader will react the same way a compiler would with a missing semicolon: assume it’s hit the end of the file and emit an error. If you are parsing a normal declaration that would likely include the terminating symbol and you wont need to add it yourself.
Remember when we said TryGetPropertyType() gives you the property if it find it? Let’s talk about that in some more detail. The most obvious reason why it might not find it is if it doesn’t exist. But there is a much more likely reason a type couldn’t be found. Much like a compiler how a compiles individual translation units and then links them together, UHT runs in phases. The Parsing Phase we saw earlier is much the same as the compilation step while the Resolve Phase is similar to linking. So if the type we are looking for is declared in a scope other than the one we are parsing we wont be able to find it.
At least not right away. When UHT is in the Resolving Phase the entire program has been parsed and all the type information will be available. What we can do is use a UhtPreResolveProperty to delay the resolution of a property until the Resolve Phase.
This is the one time you will deal directly with a concrete property type but it’s even easier because the constructor just takes the property settings. Because UhtPreResolveProperty is a UhtProperty after it’s constructed you can treat it the same.
The one extra thing you will need to do is add the list of tokens that are declaring the type to the property settings. This is because the property will still need those when it gets resolved to correctly instantiate the property.
PropertySettings.TypeTokens = new(UhtTypeTokens.Gather(TokenReader), 0);
UhtPreResolveProperty PreResovleProperty = new UhtPreResolveProperty(PropertySettings);Generating the struct definition
Now we finally have our completed ParamStruct we need to generate the actual C++ code . For now we are going to generate an intermediate include file , <STRUCT_NAME>.generated.inl, for each struct we generate. At a later step we will combine all of these into one header which makes it easier to use as you only need to remember to include on header even if you add new events.
foreach (UHTManifest.Module module in Scope.Session.Manifest!.Modules)
{
if (String.Equals(module.Name, "ModioUGC", StringComparison.OrdinalIgnoreCase))
{
pluginModule = module;
break;
}
}
string FullPath = Path.Combine(Module!.OutputDirectory, ParamStruct.EngineName + ".mutator.inl");
ParamStruct.Session.FileManager!.WriteOutput(FullPath, GenerateStructText(ParamStruct));All we do is find our module, create the output path, and write the file to disk. GenerateStructText() uses a string builder to build the text to write to the file. It’s mostly static text but something to mention is how it gets the text for the properties. UhtProperty's have a number of methods which will provide the text for that property in certain context. Here we use AppendFullDecl() to add the full declaration to the string builder.
The final step is to add the struct to the header file so UHT will generate it.
Scope.HeaderFile.AddChild(ParamStruct);Manual Parsing
Before we move on we’ve glossed over how to get the name of the event and it’s parameters. While UHT is doing a lot of parsing on it’s own we can do a little ourselves too using Scope.TokenReader .
Scope.TokenReader.Require('(');
UhtToken Name = Scope.TokenReader.GetToken();
Scope.TokenReader.PeekToken().Value.ToString().Equals(",");
Scope.TokenReader.RequireList(',', ')', ',', false, (IEnumerable<UhtToken> tokens) =>
{
MemberTokens.Add(tokens.ToList());
});Thankfully the parsing we need to do is pretty simple. A usage of our macro might look like this DEFINE_MUTATOR(EnemyWaveEnded, int32, WaveNumber). Once tokenised this will turn into a list of eight tokens: DEFINE_MUTATOR, (, EnemyWaveEnded, ,, int32, , , WaveNumber , and ). By the time it has reached us UHT has already consumed the first token (though it does pass it to our function as one of the parameters) so the next expected token should be (. We can check this using TokenReader.Require('('). This will check if the next token is ( and consume it, otherwise it will throw an exception.
The next token should be the name of the event so we can use TokenReader.GetToken() to return and consume that token. Next we can look at the next token without consuming it using TokenReader.PeekToken(). We need to peek here because we need to check if there are any more arguments that need parsing. If there’s not, meaning we are defining an event that has no parameters, then we can stop parsing and continue.
In the much more likely event that there are extra arguments we use TokenReader.RequireList() to read them and store them for later. Because we need to handle them in pairs this is a little easier than trying to pair them up as we go. TokenReader.RequireList() is pretty flexible in defining a list, it just needs a start token, end token, and separator token, meaning it can parse (1, 2, 3, 4, 5) as easily as % 1+ 2+ 3+ 4+ 5+^.
Creating the native function declaration
We’ve figured out how to create a new struct. We can now move onto the functions, starting with the native one.
UhtFunction NativeFunction = new(Scope.HeaderFile, Scope.HeaderParser.GetNamespace(), Scope.ScopeType, Token.InputLine);
NativeFunction.SourceName = Name.Value.ToString();
NativeFunction.EngineName = Name.Value.ToString();
NativeFunction.Outer?.AddChild(NativeFunction);Compared to generating the struct this is pretty simple. The constructor for the function is almost identical to the one we use for the struct. The only difference is we want the function to be part of the current scope rather than outside it. Then we just need to set the names and add it to the functions outer. Now when UHT generates the code for the class it will include any necessary code for this function too.
Creating the BlueprintCallable thunk
The last thing we need to do is generate the thunk for the Blueprint VM to be able to call this function.
UhtFunction ThunkFunction = new(Scope.HeaderFile, Scope.HeaderParser.GetNamespace(), Scope.ScopeType, Token.InputLine);
ThunkFunction.MacroLineNumber = Token.InputLine;
ThunkFunction.FunctionType = UhtFunctionType.Function;
ThunkFunction.FunctionFlags |= EFunctionFlags.Native | EFunctionFlags.BlueprintCallable;We want this to be a regular function, rather than some type of delegate, so we set the function type to UhtFunction.Function and set the Native and BlueprintCallable flags.
switch (Scope.AccessSpecifier)
{
case UhtAccessSpecifier.Public:
ThunkFunction.FunctionFlags |= EFunctionFlags.Public;
break;
case UhtAccessSpecifier.Protected:
ThunkFunction.FunctionFlags |= EFunctionFlags.Protected;
break;
case UhtAccessSpecifier.Private:
ThunkFunction.FunctionFlags |= EFunctionFlags.Private | EFunctionFlags.Final;
break;
}We also want to preserve the access specifier of the macro definition so we also set the appropriate flags for that.
ThunkFunction.MetaData.Add(UhtNames.Category, "mod.io|Mutators|Events");
ThunkFunction.MetaData.Add(UhtNames.DisplayName, MutatorFunction.SourceName);For ease of use in Blueprint we add some metadata entries setting the category and display name of the function.
ThunkFunction.SourceName = "Mutator_" + MutatorFunction.SourceName;
ThunkFunction.UnMarshalAndCallName = "execMutator_" + MutatorFunction.EngineName;
ThunkFunction.CppImplName = MutatorFunction.EngineName;We are prefixing the function name with Mutator_ because we can’t have functions with the same name. The next two properties are what we need to make this function do what we need. The UnMarshalAndCallName is the name of the thunk that will get generated and conventionally is the name of the function prefixed with exec. This will generate a function that will handle pulling the necessary information from the Blueprint VM and calling the native function. And CppImplName is the name of the native function that the thunk will call.
UhtPropertySettings StructParamSettings = new();
StructParamSettings.PropertyCategory = UhtPropertyCategory.RegularParameter;
StructParamSettings.SourceName = "Params";
StructParamSettings.EngineName = "Params";
StructParamSettings.Outer = ThunkFunction;
UhtStructProperty StructParamProp = new(StructParamSettings, ParamStruct);
StructParamProp.PropertyFlags |= EPropertyFlags.Parm;
StructParamProp.PropertyCaps |= UhtPropertyCaps.IsParameterSupportedByBlueprint;
ThunkFunction.AddChild(StructParamProp);Lastly we need to add a parameter of type ParamStruct to the function by adding a struct property to it as a child. We set the PropertyCategory to UhtPropertyCategory.RegularParameter (if we wanted to add a return value instead all we would need to do is change it to UhtPropertyCategory.Return and set the EPropertyFlags.OutParm and EPropertyFlags.ReturnParm property flags) and create a new UhtStructProperty using those settings. For some reason we need to manually set that this parameter is a Blueprint parameter by setting the property capability to UhtPropertryCaps.IsParameterBlueprintSupported but once we have done that we can add it to our function.
Parsing DECLARE_MUTATOR_EVENT_BLUEPRINT
Parsing this macro follows the same pattern but is simpler because we only need to provide the BlueprintImplementableEvent thunk as the macro takes care of the native declarations. We also don’t have the parameter list we would need to remake struct (and trying to do so would cause a redefinition anyway). Thankfully we know the name of the type so we can make a token representing that identifier and create a UhtPreResolveProperty with it that UHT can resolve later.
UhtToken T = new UhtToken(UhtTokenType.Identifier, 0, 0, 0, 0, "F" + Name.Value.ToString() + "_Params");
List<UhtToken> Tokens = new List<UhtToken>() { T };
...
UhtPreResolveProperty ParamProp = new UhtPreResolveProperty(StructParamSettings, new ReadOnlyMemory<UhtToken>(Tokens.ToArray()));Because creating the BlueprintNativeEvent thunk is almost the same as the BlueprintCallable one we don’t need to go over all the details. Instead we’ll just cover what’s different between them.
BNEThunkFunction.FunctionFlags |= EFunctionFlags.Native;
BNEThunkFunction.FunctionFlags |= EFunctionFlags.Event | EFunctionFlags.BlueprintEvent;
BNEThunkFunction.FunctionExportFlags |= UhtFunctionExportFlags.ImplFound;Just like before we will set the native function flag but this time we need to set the Event and BlueprintEvent flags instead of the BlueprintCallable one. To explain the function export flag we’ll need to talk about the anatomy of a BlueprintNativeEvent. When you mark a function as a BlueprintNativeEvent you need three things: a function declaration for the thunk, and an accompanying function declaration and definition of an _Implementation function. When UHT parses the function it looks for this implementation function and if it can’t find it, it throws an error. If it does find it then it sets this UhtFunctionExportFlags.ImplFound export flag. Because we aren’t parsing this function from source code we can’t get UHT to do this check for us (but we do know the function exists because the macro we are parsing defines it) we just have to set it ourselves.
BNEThunkFunction.UnMarshalAndCallName = "exec" + Name.Value.ToString();
BNEThunkFunction.MarshalAndCallName = Name.Value.ToString();
BNEThunkFunction.CppImplName = Name.Value.ToString() + "_Implementation";UnMarshalAndCallName we are familiar with but now we also have MashalAndCallName. Perhaps unsurprisingly this is used to define a function that operates the opposite to the unmarshalling function, one that is used to put information into the Blueprint VM rather than get it out. And this time we have to point our CppImplName to our _Implementation function rather than another UhtFunction.
Those are all the functional changes needed to generate a BlueprintNativeEvent but, as a quick aside, we want to touch on one last thing.
BNEThunkFunction.MetaData.Add(UhtNames.Category, "mod.io|Mutators|Events");
BNEThunkFunction.MetaData.Add(UhtNames.DisplayName, function.SourceName);
BNEThunkFunction.MetaData.Add("ForceAsFunction", "");Along with the same QoL metadata we used earlier we are also adding the ForceAsFunction metadata. Normally if you override a function with no return type in Blueprint it will be created in the Event Graph instead of as a function. However, if you set this UFUNCTION meta specifier it will always show up as a function, with or without a return type.
Consolidating Generated Struct Definitions
When we were talking about generating the parameter struct definitions we said we would combine them into a single header. For this we will use an Exporter. Similarly to how we defined our keywords using the UhtKeyword attribute, we can create exporter functions with the UhtExporter attribute.
[UhtExporter(Name = "MutatorEventHeaderConsolidator", ModuleName = "ModioUGC", Options = UhtExporterOptions.Default)]
public static void Generate(IUhtExportFactory Factory)This is the definition of our exporter function. For the attribute Name is the name of the exporter (this should be unique if possible), ModuleName is the name of the plugin module containing the exporter, and Options is set to UhtExporterOptions.Default.
The IUhtExportFactory parameter provides limited access back into UHT and can be used to write additional files to the Intermediate directory. The standard code generator, which handles creating all of the generated.* files, is a built in exporter so in theory we should be able to do anything it does (at least at a high level).
Thankfully we wont need anything nearing that complicated. We just need to scan the directory for the the intermediate definition files we output earlier (we gave them a unique file extension to make this easy), combine the contents together, and then output the new, consolidated header. We also delete the intermediate files once we have combined them just to keep things clean and prevent code bloat if structs from old events were still floating about.
That’s All Folks
As you can see UHT Extensions can be really powerful and, even though we went through a lot, this post only scratches the surface. And, while some of their features will require engine modifications to fully implement, a lot can be added and distributed via plugins making it a viable option for tool development. We hope this post was both informative and inspirational.
You can dive into our documentation to learn more, and see how to apply it to your project using modioUGC.