My question is: Can I expose this function to BP so it returns an array of Struct pointers?
pointers are important here, because The Widget should manipulate the Inventory directly and not a copy of it
No, you cannot have an array of pointers. However, you can return a reference to an array containing FInventoryStructs(not pointers) which can then be directly modified. Also, do note that all UStructs must be prefixed with an ‘F’ for the reflectiom system.
Separately, I would recommend building methods to get/set/manipulate items by category into your Component.
It will likely work better for blueprints than trying to make arrays work right.
Arrays are a freaking pain in Blueprints, and you typically want to end up going to C++ when you need detailed array manipulation.
My setup is like this:
(currently BP only,pseudo-syntax)
class InventoryComponent : UActorComponent
{
ItemStack] inventory; //Array of ItemStack
...]
}
struct ItemStack
{
BP_Item* item;
int amount;
}
Now let’s say I make a function like above:
ItemStack] getItemsByCategory(FName category)
the ItemStack] will be copy-by-value, won’t it? which means I’m not operating on the ItemStack] from InventoryComponent, but working on a “copy” of it.
Yeah, BP Arrays are actually LinkedLists and not arrays.
In strict C++ terms, the function you posted will return a copy of the array pointer, which points to the first element in the array. As such, if you modify something at that address, for example by getItemsByCategory(FName())[0] = ItemStack(), the array will be modified. In my first post I meant you use a TArray<FItemStack> and return a reference to that. In addition to being much safer (bound checking, overflow-protection), TArrays can be exposed to blueprints while regular C/C++ style arrays cannot.
I would also like to point out to make sure to annotate your class with UCLASS() prefixing it with a ‘U’, and struct with USTRUCT(BlueprintType) prefixing it with an ‘F’. This is important for the reflection system to know your types. This is important so the garbage collector knows which pointers are valid and which are stale. If you tried attaching the inventory component to an actor, at the very least the garbage collector will destroy your component in the next collection run.
Also make sure to annotate UObjects class members with UPROPERTY() as otherwise the garbage collector will not be able to see that the object is still being referenced and destroy it.
I guess you misunderstand me:
I don’t want to modify the whole array. I want to modify a part of it.
Every Item has a category.
The InventoryComponent consists of an Array of ItemStack
ItemStack consists of
Item*
Amount
InventoryComponent has a method getItemsByCategory(FName category)
which shall return an array of pointers to the ItemStacks of the corresponding items
That is a C# rule, where “value types” versus “reference types” are determined by the type declaration. (And then it ends up sometimes converting value types to references through boxing anyway, for various reasons.)
In C++, value versus reference is determined at time of use. If you pass something using referene semantics (&) or using pointer semantics (*) then it’s a reference, else it’s a value. The only difference between “struct” and “class” in C++ is that, in “class,” members are by default private, whereas in “struct,” members are by default public.
Unreal adds USTRUCT() and UCLASS() parameter annotations, the semantics of which you can read about here:
I think you’ll do better if you make your InventoryComponent have an Array of ItemStack, rather than deriving from Array. Maybe that’s what you’re already doing, and just not saying it
If you want to add or remove items, then you need a reference to the array (not a copy/value.)
If you want to modify an item in-place, then you need a reference to the item (not a copy/value.)
Finally, C++ doesn’t allow un-sized arrays in concrete locations (where the language defines “storage.”) You can take an un-sized array as an argument to a function, but you can’t return an un-sized array, and you can’t declare an un-sized array as a member.
Thus,
SomeItem] MyFunction(int arg);
This will generate a syntax error.
In general, you cannot return arrays by value in C++. Even when you take them as arguments with size, the compiler will turn that into a pass-by-reference, and the size will let the compiler enforce that the correct size array is passed in, or give an error/warning if it isn’t. (And, more usefully, you can detect the size of the array using template arguments.)
It sounds to me like you want to declare your UItemStack as a UCLASS().
You then want your UInventoryComponent to contain a UPROPERTY that’s a TArray<ItemStack *>.
The UItemStack should derive from UCLASS() and have UPROPERTY() values of type UItem * and int.
Once it’s all made of UObjects, you will be able to get/set the values of individual items, as well as add/remove items from the array.
Something like this:
UCLASS()
class UItemStack : public UObject {
...
UPROPERTY(EditAnywhere, BlueprintReadWrite)
UItem *Item;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int Amount;
};
UCLASS()
class UInventoryComponent : public ... {
...
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TArray<UItemStack *> Stacks;
};
(This is in BP! Ignore possible syntax errors etc, it just returns an array of the structs in BP)
If I use this function and get an ItemStack from this array and alter it(for example with “Set Members in ItemStack”), the original struct in the original array is unchanged (copy by value).
Isn’t Copy-by-value a performance hit? Imagine if ItemStack holds 100 different variables each variable gets copied.
However, making the Struct an UClass seems to be a possible solution. I’ll try it out.
€dit:
I converted my UStruct to an UObject and it indeed works!
I can now have real references that point to the stacks of the real inventory. Thank you very much!
It’s a performance hit if you end up spending all of your time mutating fields of the value, and then assigning the full struct back into storage.
However, it’s a performance win if the main cost of the data is the overhead in managing it. Reference values need to allocate on the heap, which requires some kind of memory management (malloc/free, reference counting, garbage collection, etc.)
I e, this is the difference:
struct MyStruct {
int a;
int b;
};
void fun_by_value(MyStruct ms) {
do stuff
}
void fun_by_reference(MyStruct *ms) {
do stuff
}
void call_by_value() {
MyStruct ms = { 1, 2 };
fun_by_value(ms);
}
void call_by_reference() {
MyStruct *ms = new MyStruct({ 1, 2 });
fun_by_reference(ms);
delete ms;
}
C and C++ additionally allows you to turn a value into a (temporary) reference by using address-of (&) or pass-by-reference (&) which allows you to get the best of both worlds – but then instead makes it incumbent on you to always manage memory correctly, not call delete on a pointer that’s not a heap block, etc.