So this is my situation: I have a UObject class called Upgrade.
My player character stores its upgrades in a dedicated array.
Another array is used to store inventory items, but they are AActors.
So far so good.
Then Death comes. Or a level load I guess, but I’ll keep it to Death for dramatic effect.
When the character dies, the GameInstance is triggered, the character’s inventory is stored along with the upgrades. On BeginPlay, my character copies the inventory and the upgrades from GameInstance and everything proceeds as usual. And this works, the upgrade is working, I can see the effects of the upgrade being triggered.
This thing, however, works almost randomly. If I kill my character again, when the upgrades are copied, sometimes they are null pointers.
GameInstance code called on death
void UCustomGameInstance::ActorKilled(ABaseCharacter* deadCharacter)
{
AProtagonistCharacter* protagonistCharacter = Cast<AProtagonistCharacter>(deadCharacter);
if(protagonistCharacter)
{
UE_LOG(LogProcess, Display, TEXT("GameInstance: detected character's death. Copying inventory"));
UInventoryComponent* playerInventory = protagonistCharacter->GetInventoryComponent();
PlayerItems.Empty();
PlayerUpgrades.Empty();
// Copy the inventory
for(auto& item: playerInventory->GetHeldItems())
{
UE_LOG(LogProcess, Display, TEXT("GameInstance: copying item %s"),*(item->GetName()));
PlayerItems.Add(item->GetClass());
}
// Copy the upgrades
for(auto& upgrade : playerInventory->GetUpgrades())
{
PlayerUpgrades.Add(upgrade->GetClass());
UE_LOG(LogProcess, Display, TEXT("GameInstance: copying upgrade item %s"),*(upgrade->GetName()));
}
}
}
Inventory restore code on BeginPlay
void UInventoryComponent::GameInstanceInit()
{
UCustomGameInstance* gameInstance = Cast<UCustomGameInstance>(GetWorld()->GetGameInstance());
UE_LOG(LogTemp, Display, TEXT("Inventory: retrieving data from game instance"));
AProtagonistCharacter* playerCharacter = Cast<AProtagonistCharacter>(GetOwner());
for(TSubclassOf<UUpgrade> upgradeClass : gameInstance->GetPlayerUpgrades())
{
// This next line is where the crash will occur
UUpgrade* newUpgrade = NewObject<UUpgrade>(this,upgradeClass);
newUpgrade->SetMainCharacter(playerCharacter);
AddUpgrade(newUpgrade);
UE_LOG(LogTemp, Display, TEXT("Inventory: Adding upgrade: [%s]"),*(newUpgrade->GetName()));
}
for(TSubclassOf<AUsableObjects> itemClass : gameInstance->GetPlayerItems())
{
AUsableObjects* item = Cast<AUsableObjects>(GetWorld()->SpawnActor(itemClass));
THeldItems.Emplace(item);
UE_LOG(LogTemp, Display, TEXT("Inventory: Adding item: [%s]"),*(item->GetName()));
}
}
And this is what happens when we put all of this to work:
GameMode: detected character's death
LogProcess: Display: GameInstance: detected character's death. Copying inventory
LogProcess: Display: GameInstance: copying pocket item BP_OW_Sword_C_3
LogProcess: Display: GameInstance: copying backpack item BP_OW_Sword_C_4
LogProcess: Display: GameInstance: copying upgrade item BP_BackpackUpgrade_C_0
LogTemp: Display: He dead!
...
// This is a case where everything goes fine
LogTemp: Display: GameMode: BeginPlay
LogTemp: Display: Inventory: retrieving data from game instance
LogTemp: Display: Inventory: Adding upgrade: [BP_BackpackUpgrade_C_0]
LogTemp: Display: Inventory: Adding item: [BP_OW_Sword_C_3]
LogTemp: Display: Inventory: Adding item: [BP_OW_Sword_C_4]
// Now let's kill this guy again
...
LogTemp: Display: Current Health: 0.000000
LogProcess: Display: GameMode: detected character's death
LogProcess: Display: GameInstance: detected character's death. Copying inventory
LogProcess: Display: GameInstance: copying item BP_OW_Sword_C_3
LogProcess: Display: GameInstance: copying backpack item BP_OW_Sword_C_4
// And there it is:
LogProcess: Display: GameInstance: copying upgrade item None
[2022.10.24-21.43.50:878][346]LogTemp: Display: He dead!
And with that none, I’ll automatically go into segfault once my Inventory tries to create an instance of UObject
NB: This cannot be a case where the inventory is destroyed before the GameInstance manages to copy everything. The player destruction is invoked by GameMode AFTER the copy has been done.