TMultiMap Not Loading from SavedGame

Having trouble loading anything from a TMultiMap being stored in a SaveGame. The MultiMap uses EGameModeActorName as keys and FPlayerScores as values.

SaveGame headerfile:

	// Player Score data storage object
	UPROPERTY(VisibleAnywhere, Category = "Scoring")
	FPlayerScore PlayerScores;

	// Map to store all scores
	TMultiMap<EGameModeActorName, FPlayerScore> PlayerScoreMap;

	// Add the Player Scores to the map of Player Scores
	UFUNCTION(BlueprintCallable, Category = "Scoring")
	void SavePlayerScores(EGameModeActorName GameModeActorName, FPlayerScore PlayerScoreStructToAdd);

The loading and saving is done inside AGameModeActorBase. Saving doesn’t necessarily seem to be a problem since when I look at both PlayerScores and PlayerScoreMap, they are both non-empty. However, when I load the SaveGamePlayerScore, PlayerScoreMap is always empty, but PlayerScores is not, which leads me to believe there’s something wrong with PlayerScoreMap.

void AGameModeActorBase::LoadPlayerScores()
{
	if (SaveGamePlayerScore)
	{
		PlayerScores = SaveGamePlayerScore->PlayerScores;
		TMultiMap<EGameModeActorName, FPlayerScore> PlayerScoreMap = SaveGamePlayerScore->PlayerScoreMap;
	}
}

Since I’m using a UENUM (EGameModeActorName) as keys inside the MultiMap, do I need to set up custom SetAllocator and KeyFuncs? What’s going wrong here?

Edit: It looks like this might be because I can’t use the UPROPERTY() specifier to TMultiMap?

That’s correct. Savegame serialization can only automatically handle the things that are exposed to reflection and the UPROPERTY macro is how things are exposed to reflection. Since TMultiMap isn’t supported by the UPROPERTY macro you won’t be able to get automatic serialization of that member.

Your two options are: 1) Store it in a multi-map someplace else for runtime accesses and convert it to an array or some other data set when writing data to your save. 2) Modify your data structure to be compatible with UPROPERTY. In this case that probably means a) using TMap, b) the value being a custom structure, c) which contains an array of FPlayerScores. Or something else, but that’s more or less all a multi-map is anyway.

1 Like

Late reply, but I ended up going with the second option you suggested. I just made another USTRUCT with its only member being a TArray of FPlayerScores, so the final signature looks like

TMap<EGameModeActorName, FPlayerScoreWrapper> PlayerScoreMap

where FPlayerScoreWrapper is the USTRUCT.

That’s great! Glad to hear it.

I think you meant for your final signature to use TMap though and not TMultiMap which was source of your original problems.

Lol you’re right, I just copy pasted it and didn’t look very close. I’ll add some more information if someone comes across this in the future.

This is the struct declaration containing the TArray:

USTRUCT(BlueprintType)
struct FPlayerScoreArrayWrapper
{
	GENERATED_USTRUCT_BODY()

		UPROPERTY(EditAnywhere, BlueprintReadWrite)
		TArray<FPlayerScore> PlayerScoreArray;
};

I’m actually using structs for both the key and value of the TMap now. To do this, I overrode the == operator like so (this goes inside the struct that I’m using as the key, which is FGameModeActorStruct)

FORCEINLINE bool operator== (const FGameModeActorStruct& Other) const
{
	if (GameModeActorName == Other.GameModeActorName &&
		SongTitle.Equals(Other.SongTitle) &&
		CustomGameModeName.Equals(Other.CustomGameModeName))
	{
		return true;
	}
	return false;
}

and after the struct declaration I used this for the hash function (I think, I’m not really 100% how this part works):

FORCEINLINE uint32 GetTypeHash(const FGameModeActorStruct& Other)
{
	uint32 Hash = FCrc::MemCrc32(&Other, sizeof(FGameModeActorStruct));
	return Hash;
}

So the final signature is:

TMap<FGameModeActorStruct, FPlayerScoreArrayWrapper> PlayerScoreMap

I use this to store player scores based on which game mode and song are selected. Here’s an example of me saving scores to an instance of FPlayerScores at the end of a game mode:

	// iterate through all elements in PlayerScoreMap
	for (TTuple<FGameModeActorStruct, FPlayerScoreArrayWrapper>& Elem : PlayerScoreMap)
	{
		// get array of player scores from current key value
		TArray<FPlayerScore> TempArray = Elem.Value.PlayerScoreArray;
		
		float HighScore = 0.f;
		// iterate through array of player scores to find high score for game mode & song
		for (FPlayerScore& PlayerScoreObject : TempArray)
		{
			if (PlayerScoreObject.HighScore > HighScore)
			{
				HighScore = PlayerScoreObject.HighScore;
			}
		}
	}