Skip to content

Defining a C++ Struct

Similar to classes, Unreal Engine allows you to register a struct with the reflection system via USTRUCT. This would allow for runtime interaction and Blueprints integration. The engine frequently uses structs to define data containers, property bags, etc.

USTRUCTs are commonly defined in the header file of a related class, but they could also have their own dedicated header file. You'll have to manually create the header file, however, as there is no option in the Unreal Editor to create a struct.

Note

USTRUCTs cannot be defined inside the body of a class. The USTRUCT must be defined at the global scope.

Example

The following is a simple example of a struct that defines a geographical coordinate.

GeoPoint.h
#pragma once

#include "GeoPoint.generated.h"/* (1)! */

USTRUCT(BlueprintType)
struct MODULENAME_API/* (2)! */ FGeoPoint /* (3)! */ {
    GENERATED_BODY()/* (4)! */

    UPROPERTY(EditAnywhere)/* (5)! */
      float Latitude;

    UPROPERTY(EditAnywhere)/* (6)! */
      float Longitude;
};
  1. Contains all the boilerplate code generated by UHT. This file must be included as the last #include in the header file.
  2. Akin to the standard extern keyword in C++. See External Linkage in classes for more info.
  3. Per recommended conventions, the F prefix is used to denote a struct.
  4. Used to inject some boilerplate code generated by UHT into the class body.
  5. The UPROPERTY macro in a struct supports the same set of specifiers as in a class. See UPROPERTY for the complete list.
  6. The UPROPERTY macro in a struct supports the same set of specifiers as in a class. See UPROPERTY for the complete list.

USTURCTs cannot contain UFUNCTIONs. You may want to look into UBlueprintFunctionLibrary if you need to define companion functions for your struct.

USTRUCT

USTRUCT is pretty limited compared to UCLASS. There are only a few specifiers and meta specifiers that USTRUCT can accept.

Specifiers

USTRUCT(BlueprintType)

Exposes this struct as a type that can be used for variables in Blueprints.

The inner properties of the struct will show up as nested properties with the right specifiers.

Example Variable

You may want to use UPROPERTY(meta=(ShowOnlyInnerProperties)) to avoid nesting.

Meta Specifiers

USTRUCT(meta=(HasNativeBreak="FunctionName"))

Specifies a custom break function. A break function takes an instance of the struct and breaks it down into its individual properties. USTRUCTs have an automatic break function that exposes every BlueprintReadOnly or BlueprintReadWrite property. You can define a custom break function to expose only a subset of the properties, or extend them with computed ones.

The following example:

USTRUCT(BlueprintType, meta=(HasNativeBreak="ModuleName.GeoPointHelperLibrary.BreakGeoPoint"/* (1)! */))
struct FGeoPoint { /* Same body as in the example above. */ };

UCLASS()
class MODULENAME_API UGeoPointHelperLibrary : public UBlueprintFunctionLibrary {
    GENERATED_BODY()

public:
    UFUNCTION(BlueprintPure)
    static void BreakGeoPoint(const FGeoPoint& GeoPoint, float& Latitude, float& Longitude, FString& AsString) { 
        Latitude = GeoPoint.Latitude;
        Longitude = GeoPoint.Longitude;
        AsString = FString::Printf(TEXT("(%f:%f)"), Latitude, Longitude);
    }
};
  1. An absolute path to a UFUNCTION is formed by ModuleName.ClassName.FunctionName.

results in the following Blueprint break:

USTRUCT(meta=(HasNativeMake="FunctionName"))

The make function is the antithesis to the break function. It takes the necessary properties and constructs an instance of the struct. The default make function takes in all BlueprintReadWrite properties to construct a new instance.

You can define a custom make function to construct an instance from a subset of properties, or something entirely different. For example:

USTRUCT(BlueprintType, meta=(HasNativeMake="ModuleName.GeoPointHelperLibrary.MakeGeoPoint"/* (1)! */))
struct FGeoPoint { /* Same body as in the example above. */ };

UCLASS()
class MODULENAME_API UGeoPointHelperLibrary : public UBlueprintFunctionLibrary {
    GENERATED_BODY()

public:
UFUNCTION(BlueprintPure)
    static FGeoPoint MakeGeoPoint(int32 LongDegrees, int32 LongMinutes, int32 LongSeconds, int32 LatDegrees, int32 LatMinutes, int32 LatSeconds) {
        FGeoPoint GeoPoint;
        GeoPoint.Longitude = LongDegrees + LongMinutes / 60.0 + LongSeconds / 3600.0;
        GeoPoint.Latitude = LatDegrees + LatMinutes / 60.0 + LatSeconds / 3600.0;
        return GeoPoint;
    }
};
  1. An absolute path to a UFUNCTION is formed by ModuleName.ClassName.FunctionName.

If you then try to make an FGeoPoint in Blueprints, you'll see the following:

Constructor

Just like UCLASSes, USTRUCTs are required to have a default constructor. An obvious choice for the example struct we have here is:

FGeoPoint(float Latitude = 0, float Longitude = 0) 
    : Latitude(Latitude), Longitude(Longitude) { }

Instantiation

USTRUCT instances live on the stack. You can instantiate them like any other C++ struct, and expect them to be destroyed when they go out of scope.

FGeoPoint GeoPoint(43.651070, -79.347015);

Serialization

USTRUCTS also support automatic serialization and deserialization. They also support custom serialization logic through Serialize and NetSerialize.

USTRUCT(BlueprintType)
struct FGeoPoint { 
    GENERATED_BODY()

    FGeoPoint();

    /* Properties */

    void Serialize(FArchive& Ar) {
        Ar << Latitude << Longitude;
    }

    bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) {
        /* We could perhaps convert the floats to half-precision floats here */
        /* This might not seem a lot, but over time, it could save a lot of bandwidth */
        Ar << Latitude << Longitude;
        return true;
    }   
};

Simply declaring these functions is not enough. You need to subclass a template type trait struct named TStructOpsTypeTraitsBase2<T> to explicitly inform the engine about your functions.

template<>
struct TStructOpsTypeTraits<FGeoPoint> :
TStructOpsTypeTraitsBase2<FGeoPoint>
{
    enum
    {
        WithSerializer = true,
        WithNetSerializer = true
    };
};