I’ve also posted this in the forum: See here for a small project that uses C++ to modify an array of Blueprint-defined structs stored inside a Blueprint. The code is not pretty but seems to work all right. I’d be happy to learn a better way of interacting with Blueprints from C++ but so far this is the best I could do. The main part:
void APropertyTestGameMode::AddEntry(AActor* Actor)
{
if (Actor == nullptr)
{
return;
}
for (TFieldIterator<UProperty> PropIt(Actor->GetClass()); PropIt; ++PropIt)
{
// We are iterating over all properties of Actor
UProperty* Property{ *PropIt };
FString PropertyName{ GetNameSafe(Property) };
if (PropertyName == "HammerOutputs")
{
// Hey, we found the HammerOutputs property. This should be an
// array, right?
UArrayProperty* ArrayProperty{ Cast<UArrayProperty>(Property) };
if (ArrayProperty != nullptr)
{
// Yes it's an array. Now what can we do with an array
// property? Turns out, not much. We need some helper class
// to make handling the array palatable.
FScriptArrayHelper PropertyHelper{
ArrayProperty, Property->ContainerPtrToValuePtr<void>(Actor) };
// Using the helper it is not too difficult to add an
// uninitialized member.
int32 Index{ PropertyHelper.AddValue() };
// But of course we want to add some data, not just default
// initialized entries. Therefore we need the Inner member
// that describes how the entries of the array described by
// ArrayProperty look like.
UProperty* InnerProperty{ ArrayProperty->Inner };
// Well, our array members should be HammerOutput structs,
// right? So InnerProperty should actually be a
// UStructProperty, right? Right.
UStructProperty* StructProperty{ Cast<UStructProperty>(InnerProperty) };
if (StructProperty != nullptr)
{
// Now we have the UStruct property. What can we do with
// it? If you paid attention to our array property you can
// guess the answer. Not much.
// What we need is the UScriptStruct (which derives from
// UStruct) that is stored inside the StructProperty.
UScriptStruct* Struct{ StructProperty->Struct };
// By using the UStruct we can actually - you probably
// guessed it, get UProperty objects for our properties.
// Let's do this!
// Well, wait a moment. We would like to use
// Struct->FindPropertyByName here, but that does not work
// so well. Or at all. Because, the name of the property
// MyFloat is not MyFloat but actually something like
// MyFloat_6_34283427842342987. The thing we want to check
// is the FriendlyName. Or something. Perhaps there is a
// way to find the property using the friendly name, but I
// don't know what it is. So we have to do this the hard way
// by iterating over all properties. And while we're at it,
// let's cast to UNumericProperty, or whatever type of
// property is appropriate for each variable, so that we
// can, you know, actually do something with the result.
UNumericProperty* MyFloatProperty{ nullptr };
UBoolProperty* MyBoolProperty{ nullptr };
UStrProperty* MyStringProperty{ nullptr };
UArrayProperty* MyInnerArrayProperty{ nullptr };
for (UProperty* Prop = Struct->PropertyLink;
Prop != nullptr;
Prop = Prop->PropertyLinkNext)
{
if (Prop->GetName().StartsWith("MyFloat_"))
{
MyFloatProperty = Cast<UNumericProperty>(Prop);
}
else if (Prop->GetName().StartsWith("MyBool_"))
{
MyBoolProperty = Cast<UBoolProperty>(Prop);
}
else if (Prop->GetName().StartsWith("MyString_"))
{
MyStringProperty = Cast<UStrProperty>(Prop);
}
else if (Prop->GetName().StartsWith("MyStringArray_"))
{
MyInnerArrayProperty = Cast<UArrayProperty>(Prop);
}
else
{
UE_LOG(LogTemp, Log,
TEXT("Unknown: %s"), *Prop->GetName());
}
}
// So, we now have some other properties. Can we do
// something with them? Well, not exactly, not right now.
// We first need to figure out the address of the value to
// set. Remember the PropertyHelper above, and the Index we
// got from the AddValue() operation? Of course you do!
uint8* MyStructPointer{ PropertyHelper.GetRawPtr(Index) };
if (MyFloatProperty != nullptr)
{
// Yay! We have another property that allows us to finally
// set one of the values.
MyFloatProperty->SetFloatingPointPropertyValue(
MyStructPointer, Index * 1.0f);
// Easy, right?
}
if (MyBoolProperty != nullptr)
{
// Same old, same old, only for boolean values.
MyBoolProperty->SetPropertyValue_InContainer(
MyStructPointer, Index%2 == 0, 0);
}
if (MyStringProperty != nullptr)
{
// We're really getting the hang of it.
MyStringProperty->SetPropertyValue_InContainer(
MyStructPointer,
FString::Printf(TEXT("Howdy %d!"), Index), 0);
}
if (MyInnerArrayProperty != nullptr)
{
// Uh oh. Another array. Those weeping noises you hear are
// surely a figment of your imagination. Can't have
// anything to do with me breaking down and, no, no no
// siree, no way. We *sniff* get another helper and...
FScriptArrayHelper InnerHelper{ MyInnerArrayProperty,
MyInnerArrayProperty->ContainerPtrToValuePtr<void>(
MyStructPointer) };
// ... you were saying? I'm sorry, I got distracted for a
// moment. It's just, you know, life is really tough
// sometimes...
UStrProperty* InnerStringProperty{
Cast<UStrProperty>(MyInnerArrayProperty->Inner) };
if (InnerStringProperty != nullptr)
{
// Let's add a variable number of strings so that we
// *sigh*, you know... see and stuff...
for (int32 I = 0; I < Index; ++I)
{
// Value, pointer, you know the drill...
int32 J{ InnerHelper.AddValue() };
ensure(J == I);
uint8* MyInnerStringPointer{
InnerHelper.GetRawPtr(J) };
InnerStringProperty->SetPropertyValue_InContainer(
MyInnerStringPointer,
FString::Printf(TEXT(">>> %d"), J), 0);
}
}
}
}
}
}
}
}