sinbad / SPUD

Steve's Persistent Unreal Data library
MIT License
300 stars 44 forks source link

Map values as UObjects within Components #70

Closed RealIbbz closed 4 months ago

RealIbbz commented 4 months ago

I have a component within an actor that has a Map with an integer (key) to UObject (value). I can get the component itself to save fine, but the values of the map are always null (keys are fine) when they're UObjects - is this expected? (This is after I load the save game - I dont see any error messages in the log & the map itself is saved)

If it is, could I use the custom data to store it instead? And if so, are you able to give a brief example on how to use it from a blueprint perspective? (IE What function to use to write the map & associated UObjects) I tried looking at the SPUD examples but I dont have 5.2 installed & it fails for some reason when I try and upgrade to 5.3.

(I'm actually developing on 5.1 - not sure if that makes a difference in this case as well)

sinbad commented 4 months ago

I've updated the SPUDExamples project to work on 5.3.

Maps and arrays of raw UObject will work, but will not work with TObjectPtr. If you're defining the map in Blueprints, it's probably using TObjectPtr. Sadly from UE 5.2 this is no longer supported so I had to disable it. (https://github.com/sinbad/SPUD/blob/master/doc/props.md#supported-property-types). If you defined the actor in a C++ base class you could use UObject and it should work automatically.

To use custom data, you need to implement the ISpudObjectCallback interface and define SpudStoreCustomData / SpudRestoreCustomData. From Blueprints you don't have quite as much power as C++ but read/write methods are exposed to blueprints.

RealIbbz commented 4 months ago

Yeah unfortunately I'm doing it in blueprints, which presumably are using TObjectPtr. Ah well, back to the drawing board.

RealIbbz commented 4 months ago

So just as a test - I created a dummy C++ Actor class with a UObject* map - still same issue - the test variable object in the class is stored perfectly fine (as it is in blueprints), the key of the map is OK, but the value of the map is null / unknown.


#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "ISpudObject.h"
#include "InventoryStorageActor.generated.h"

/**
 * Temp hack so that inventory can be stored.
 */
UCLASS(Blueprintable)
class ABCD_API AInventoryStorageActor : public AActor, public ISpudObject
{
    GENERATED_BODY()

public:
    AInventoryStorageActor()
        : SpudGuid(FGuid::NewGuid()) // Initialize SpudGuid with a new GUID
    {
    }

    // The map storing integer keys mapped to UObject pointers.
    UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame)
    TMap<int32, UObject *> InventoryMap;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame)
    UObject *Test;

    // FGuid variable to uniquely identify something for SPUD
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "SPUD", SaveGame)
    FGuid SpudGuid;

    // New function to add a UObject to the inventory map
    UFUNCTION(BlueprintCallable, Category = "Inventory")
    void AddObjectToInventory(int32 Key, UObject *ObjectToAdd);
};

The AddObjectToInventory literally just adds the object to the map - I was doing it through blueprints and it wasnt working so i wasnt sure if something odd was happening so i just created a basic method to add in C++ to confirm. Is there something I'm missing to have it working? (Note I also upgraded the project to 5.3.2 to see if that helped with anything but it would appear not)

Thanks again for your help - SPUD is a great plugin so I've been determined to try and get it working hence the test class above.

sinbad commented 4 months ago

I just added a test to the project which uses a map from int to a simple UObject, and it worked fine: https://github.com/sinbad/SPUD/commit/575f9710c4b0b9a3f340175e786604522ecdbd20. So I'm not sure why it's not working for you, perhaps take a look in debug at the SpudPropertyUtil::StoreContainerProperty and SpudPropertyUtil::RestoreContainerProperty functions, specifically the section marked else if (IsPropertyFallbackSupported(Property)) (because Maps of UObjects are not natively supported by SPUD, they use the generic UE binary serialization which is more limited).

One thing worth noting is that you should not mark your FGuid SpudGuid property as SaveGame, as per the docs: https://github.com/sinbad/SPUD?tab=readme-ov-file#runtime-spawned-actors

You also only need SpudGuid if this is a runtime spawned actor which is referenced by something else in the save game.

Other things that may be messing with your state are things you do in BeginPlay, which can be hard to time correctly on restore and you might need to review things that should actually be in PostRestore.

sinbad commented 4 months ago

Another fallback option: consider using a struct for your inventory data instead of a UObject. It can be cleaner to keep your data in structs instead anyway rather than using UObjects too much, they're also leaner (no garbage collection and all the pointer maintenance that goes with it). They're also much easier for SPUD to handle.

RealIbbz commented 4 months ago

I still couldn't figure out whats causing UObjects in maps to be null - if I had to hazard a guess, maybe its something to do with the playing it in the Editor ? I also tested it with Tom Loomans more basic save system, and again had the same issue with TMaps / Arrays not restoring UObjects.

But I'm actually going to switch across to storing the inventory data for the Savegame in a Custom UStruct as you suggest - I tested them earlier and they're working fine for saving. I'll also use this as an opportunity to refactor some inventory structs out of blueprints into C++ & and it'll probably turn out to be a better solution anyway. Thanks again for your help.