-
Notifications
You must be signed in to change notification settings - Fork 67
Exposing types to UE4
If you're not familiar with the general architecture of UE4 you should reference the following when getting started with C# / UE4
- https://docs.unrealengine.com/en-US/Programming/UnrealArchitecture
- https://docs.unrealengine.com/en-US/Gameplay/Framework
The following code shows some of the basic functionality of exposing C# defined types to UE4
[UClass]
class AExampleClass : AActor, IExampleInterface
{
[UProperty, EditAnywhere, BlueprintReadWrite, Category("MyCategory")]
public int ExampleValue { get; set; } = 50;
// "return null;" is re-written to return a list which is capable of
// reading / writing the native memory of UE4. All other properties require get;set;
[UProperty, EditAnywhere, BlueprintReadWrite]
public IList<int> ExampleList { get { return null; } }
[UProperty, EditAnywhere, BlueprintReadWrite]
public FExampleStruct ExampleStruct { get; set; }
[UProperty, EditAnywhere, BlueprintReadWrite]
public EExampleEnum ExampleEnum { get; set; }
[UProperty, EditAnywhere, BlueprintReadWrite]
public FExampleSimpleDelegate SimpleDelegate { get; set; }
// BlueprintAssignable / BlueprintCallable are special values used for multicast delegates
[UProperty(PropFlags.BlueprintAssignable | PropFlags.BlueprintCallable), EditAnywhere, BlueprintReadWrite]
public FExampleMulticastDelegate MulticastDelegate { get; set; }
public override void Initialize(FObjectInitializer initializer)
{
ExampleValue = 10;
ExampleStruct = new FExampleStruct() { Value1 = 20, Value2 = 30 };
ExampleEnum = EExampleEnum.Val2;
// Enable tick
PrimaryActorTick.SetStartWithTickEnabled(true);
PrimaryActorTick.SetCanEverTick(true);
}
protected override void BeginPlay()
{
base.BeginPlay();
}
protected override void ReceiveTick_Implementation(float DeltaSeconds)
{
base.ReceiveTick_Implementation(DeltaSeconds);
}
// This is an example of a BlueprintCallable function
[UFunction, BlueprintCallable]
public void ExampleFunction(int value)
{
PrintString("ExampleFunction: " + value, FLinearColor.Red);
// Bind the delegates in Blueprint, this will call the bound target
if (SimpleDelegate.IsBound)
{
SimpleDelegate.Invoke(value, 200);
}
if (MulticastDelegate.IsBound)
{
MulticastDelegate.Invoke(value, 300);
}
}
// This is an example of a BlueprintNativeEvent function
[UFunction, BlueprintCallable, BlueprintNativeEvent]
public void ExampleVirtualFunction(int value)
{
// This code will get overwritten by AssemblyRewriter.
// Write the code in the _Implementation function.
throw new NotImplementedException();
}
protected virtual void ExampleVirtualFunction_Implementation(int value)
{
PrintString("ExampleVirtualFunction: " + value, FLinearColor.Red);
}
// This is an example of a BlueprintImplementableEvent function
[UFunction, BlueprintCallable, BlueprintImplementableEvent]
public void ExampleBlueprintImplementedFunction(int value)
{
// This code will get overwritten by AssemblyRewriter.
// No _Implementation function is required as it should be implemented in Blueprint.
throw new NotImplementedException();
}
// This is an example of an interface function
public void InterfaceFunction(int value)
{
PrintString("InterfaceFunction: " + value, FLinearColor.Red);
}
}
[UStruct]
struct FExampleStruct
{
[UProperty, EditAnywhere, BlueprintReadWrite]
public int Value1;
[UProperty, EditAnywhere, BlueprintReadWrite]
public int Value2;
}
[UInterface]
interface IExampleInterface : IInterface
{
[UFunction, BlueprintCallable]
void InterfaceFunction(int value);
}
[UEnum]
enum EExampleEnum : byte
{
Val1,
Val2
}
class FExampleSimpleDelegate : FDelegate<FExampleSimpleDelegate.Signature>
{
public delegate void Signature(int arg1, int ag2);
}
class FExampleMulticastDelegate : FMulticastDelegate<FExampleMulticastDelegate.Signature>
{
public delegate void Signature(int arg1, int ag2);
}
By default all UE4 exposed classes / structs / interfaces / enums are visible to Blueprint. If you don't want your type to be used from Blueprint then add the [NotBlueprintType]
attribute. If you don't want your class to be inheritable by Blueprint add the [NotBlueprintable]
attribute.
Types exposed to UE4 should use the same naming convention as C++. There are checks which enforce you to use these naming conventions:
- Classes that inherit from UObject are prefixed by 'U' - e.g. UMyObject
- Classes that inherit from AActor are prefixed by 'A' - e.g. AMyActor
- Interfaces are prefixed by 'I' - e.g. IMyInterface
- Enums are prefixed by 'E' (this is optional) - e.g. EMyEnum
- Structs are prefixed by 'F' - e.g. FMyStruct
- Delegates are prefixed by 'F' - e.g. FMyDelegate
-
[UClass]
states that the class should be exposed to UE4. -
Initialize(FObjectInitializer initializer)
is called when an object is constructed. This function should be used as a replacement of the regular C# constructor (it's unsafe to access anything UE4 related in the regular C# constructor).Initialize()
(parameterless) will be called after hotreload for C# objects which already exist in the world.
-
[UStruct]
states that the struct should be exposed to UE4.
-
[UInterface]
states that the interface should be exposed to UE4. Exposed interfaces MUST also inherit IInterface (this is important so that the UObject can be resolved from the interface). - Interfaces can be tricky to work with, and may not be ideal for extensive usage. Read more about the limitations, and some examples of interfaces.
-
[UEnum]
states that the enum should be exposed to UE4. - Due to Blueprint limitations enums should be of type byte
enum FMyEnum : byte
. If you want a UE4 exposed enum to not be a byte then you need to flag your enum as[NotBlueprintType]
. -
[Bitflags]
states that the enum can is to be used as a bitmask. If you want to use real flags values (0x2, 0x4, 0x8, etc) then you also want to use[UseEnumValuesAsMaskValuesInEditor]
. If you don't use the second attribute then all values will be treated as bit indices, which can be interacted with via the classBitflags
.
- Classes which inherit from
FDelegate
are exposed to UE4 as single-cast delegates. Single-cast delegates can be only be bound to 1 target function. - Classes which inherit from
FMulticastDelegate
are exposed to UE4 as multi-cast delegates. Multi-cast delegates can be bound to many target functions. -
FMulticastDelegate
can use a couple of custom flags when exposed as aUProperty
:PropFlags.BlueprintAssignable
andPropFlags.BlueprintCallable
. These allow multicast delegates to be assigned / called from Blueprint.
- Exposed variables in classes MUST be defined with an empty getter/setter
{ get; set; }
. Exposed variables in structs MUST be defined as a field (no getter/setter). This is important as the IL is rewritten to read / write the native UE4 object memory. -
[UProperty]
states that the property should be exposed to UE4. -
[EditAnywhere]
states that the property should be visible in the details panel and can be edited. Use[VisibleAnywhere]
if you want it to be visible but not editable. -
[BlueprintReadWrite]
states that the property can be accessed via get/set nodes to modify the value. -
[Category]
can be used to list the given function / variable within the named category. Sub categories can be used such as "MyCategory|MySubCategory". Spaces shouldn't be used between the '|' separator. -
[ExposeOnSpawn]
can be used to make a variable visible as an input pin when constructing a C# type from Blueprint.
-
[UFunction]
states that the function should be exposed to UE4. -
[BlueprintCallable]
states that the function can be called from Blueprint. -
[BlueprintPure]
states that the function doesn't modify the owning object in any way. In Blueprint these functions don't have an execution pin. -
[Exec]
the function can be executed from the in-game console. -
[BlueprintNativeEvent]
states that the function is virtual, and has an implementation defined in C#. An additional function with an_Implementation
suffix must be defined with the implementation of the function. The non_Implementation
function body should be left blank as the code will be re-written to call the most derived implementation (which may be defined in Blueprint). -
[BlueprintImplementableEvent]
states the the function can be implemented in Blueprint. The function body should be left blank in C# as the code will be re-written to call the Blueprint function. -
[RPC]
is used for Remote Procedure Calls. There is only 1 attribute, and enums are used to configure how the RPC is set up._Implementation
/_Validate
should be implemented in a similar way to C++.
- The
[UMeta]
attribute can be used to add additional metadata values which aren't already implemented as custom attributes. See UMetaAttribute.cs for a list of the majority of the metadata keys in UE4.
The following types can be exposed as variables / function params which are visible to Blueprint:
IList, ISet, IDictionary, TSubclassOf, TSoftObject, TSoftClass, UObject, float, int, long, byte, FName, bool, string, FText, delegates, interfaces, enums, structs
The following types can also be exposed to UE4 as variables / function params (but not to Blueprint):
sbyte, short, ushort, uint, ulong, double, TLazyObject, TWeakObject
- Care should be taken when dealing with FText (disposing is required when creating a new FText object, as FText is reference counted in C++).
Regular arrays (e.g. int[] MyArray
) can't normally be used on anything exposed to UE4. One exception to this is for fixed sized arrays on structs (this is subject to change in the future for performance reasons). Fixed sized arrays allows you to define an array which size will always be the same. Any regular array in a struct is treated as a fixed sized array, FixedSizeArrayDim
should be set appropriately.
[UClass]
class UFixedArrayTestClass : UObject
{
[UProperty(FixedSizeArrayDim = 10), EditAnywhere, BlueprintReadWrite]
public TFixedSizedArray<int> MyArray { get; set; }
}
[UStruct]
struct FFixedArrayTestStruct
{
[UProperty(FixedSizeArrayDim = 10), EditAnywhere, BlueprintReadWrite]
public int[] MyArray;
}
IList
, ISet
, IDictionary
are used to work with collections in UE4. These internally are constructed as wrappers to read/write the native collection memory belonging to underlying the UE4 object. These come with some limiations:
- Don't assign collections e.g.
myObject.MyList = new List<int>();
The setter is internally stripped byAssemblyRewriter.exe
and an attempt to assign the list will result in an error when compiling"Using a setter on a property that had a setter stripped! Collections and a few other types have their setters stripped."
. You can instead modify your property to have just a getter which returns null, to avoid having a setter being exposed at all. This will be re-written to return a collection which is capable of reading / writing native memory.