1 minute read

Warning: This feature is only available for Unreal Engine 5.0 and newer.

Intro

With the release of Unreal Engine 5, Epic Games has added a simpler and more powerful way to iterate through the properties of a UCLASS() or USTRUCT().

Why use it over TFieldIterator?

Contrary to TFieldIterator<>, TPropertyValueRange<> uses a range-based loop which allows you to filter by property type, and also retrieve the value directly. This results in cleaner code and a streamlined process to read from/write to properties using Unreal’s reflection capabilities.

How does it work?

TPropertyValueRange<> requires you to pass in three arguments:

  • The type of FProperty you want to lookup. You can just use FProperty to lookup all properties, or use something more specialised like FStructProperty.
  • The UStruct* you want to read the properties for (UClass* for UObjects, UScriptStruct* for structs).
  • A pointer to an instance of the UStruct* you want to get the values of the properties for.

Let’s say I have the following struct:

USTRUCT()
struct FPlayerFlags
{
    FPlayerFlags()
        : bIsDeveloper(false)
        , bIsAlive(true)
        , DeathCount(0)
    {}

    UPROPERTY()
    bool bIsDeveloper;

    UPROPERTY()
    bool bIsAlive;

    UPROPERTY()
    uint32 DeathCount;
}

In order to iterate through all properties using reflection, we can simply write the following range-based loop:

FPlayerFlags& Flags = MyPlayer->GetPlayerFlags();

for (const TPair<FProperty*, void*>& PropertyValuePair : TPropertyValueRange<FProperty>(FPlayerFlags::StaticStruct(), &Flags))
{
    // The key of the PropertyValuePair contains information about the current property we're iterating at.
    // The value is a void* to the property's value. 
}

Now let’s say we want to set all bool properties to false, we could write the following code:

FPlayerFlags& Flags = MyPlayer->GetPlayerFlags();

for (const TPair<FProperty*, void*>& PropertyValuePair : TPropertyValueRange<FProperty>(FPlayerFlags::StaticStruct(), &Flags))
{
    // Before casting the void* to your desired type, make sure the FProperty matches!
    if (FBoolProperty* const BoolProperty = CastField<FBoolProperty>(PropertyValuePair.Key))
    {
        bool& bBoolPropertyValue = *static_cast<bool*>(PropertyValuePair.Value);
        bBoolPropertyValue = false;
    }
}

However, since TPropertyValueRange<> allows us to only iterate through specific property types, we can simplify the code to this:

FPlayerFlags& Flags = MyPlayer->GetPlayerFlags();

for (const TPair<FBoolProperty*, void*>& PropertyValuePair : TPropertyValueRange<FBoolProperty>(FPlayerFlags::StaticStruct(), &Flags))
{
    bool& bBoolPropertyValue = *static_cast<bool*>(PropertyValuePair.Value);
    bBoolPropertyValue = false;
}

You can also use structured bindings to make your code even cleaner:

FPlayerFlags& Flags = MyPlayer->GetPlayerFlags();

for (const auto& [Property, Value] : TPropertyValueRange<FBoolProperty>(FPlayerFlags::StaticStruct(), &Flags))
{
    bool& bBoolPropertyValue = *static_cast<bool*>(Value);
    bBoolPropertyValue = false;
}

Conclusion

TPropertyValueRange<> provides a clean and streamlined method to iterate through the UPROPERTY() members of a UCLASS() or USTRUCT().