My Game is crashing sometimes despite `IsValid` with UObject.Name="None". What am I doing wrong? Wrong smart Pointer?

Hi,

I try to reproduce this crash the best I can but it is not really in my hands. Without posting my whole codebase, this is the part causing the crash:

bool UInventoryComponent::Select(EStorageName StorageName, int SlotIndex)
{
	FStorageInfo StorageInfo(StorageName);
	UStorage* Storage = Inventory.GetStorage(StorageName);
	if(!IsValid(Storage))
	{
		return false;
	}
	APickup* Pickup = Storage->Get(SlotIndex, StorageInfo);  // Crashing here!
  ...
}

In Inventory.GetStorage(StorageName) I dynamically return a raw pointer UStorage* to a member of FInventory based on an UENUM(BlueprintType) EStorageName.

UStorage* FInventory::GetStorage(EStorageName StorageName)
{
	switch (StorageName)
	{
	case EStorageName::Melee1:
		return Melee1;
	case EStorageName::Ranged1 :
		return Ranged1 ;
         ...
       }
...
}

UStorage is a UCLASS(Abstract) and their children USlotStorage and USocketStorage implement their own APickup* Get(...) function to return the Actor their store.

struct FInventory
{
...
	TObjectPtr<USlotStorage> Melee1;
	TObjectPtr<USocketStorage> Ranged1 = NewObject<USocketStorage>();
...
}

I figured out that the crash is occuring when Storage > = {UObject} (Name=“None”). Was this object (in the process of) being garbage collected? Shouldnt it be still alive, because the Storage is a Member of Inventory, which is a Member of InventoryComponent, which is a UActorComponent and none of them is actually destroyed.

What am I doing wrong? Am I using the wrong smart Pointer with TObjectPtr? Am I doing something severly wrong here with the enum-based getter?

Thanks in advance!

UObject pointers should always be decorated with UPROPERTY.

Decorate your FInventory struct with USTRUCT, and add UPROPERTY to the properties.

That is not the root cause of your problem though, this is :

Don’t create UObjects statically.
Assuming your UInventoryComponent class has a variable FInventory Inventory, you should initialize it (and its subobjects) in the constructor :

//header

USTRUCT()
struct FInventory
{
    GENERATED_BODY()
    UPROPERTY() TObjectPtr<UStorage> Melee1;
    UPROPERTY() TObjectPtr<UStorage> Ranged1;
};

UCLASS()
class UInventoryComponent : public UActorComponent
{
    GENERATED_BODY()

    UInventory();  //constructor

    UPROPERTY()
    FInventory Inventory;
}
//cpp

UInventory::UInventory()
{
    Inventory.Melee1 = CreateDefaultSubobject<USlotStorage>("Melee1");
    Inventory.Ranged1 = CreateDefaultSubobject<USocketStorage>("Ranged1");
}
1 Like

Thank you for your answer!

UObject pointers should always be decorated with UPROPERTY.

I didn’t know that this is the case for every UObject. I’m going to follow that rule.

Don’t create UObjects statically… you should initialize it (and its subobjects) in the constructor.

So in order to create UObjects dynamically I should never call their constructors directly but always use CreateDefaultSubobject<T>("Name")? In which case am I allowed to call NewObject<T>()?

UInventoryComponent::UInventoryComponent()
{
  Inventory.Melee2 = CreateDefaultSubobject<USlotStorage>("Melee2");
  Inventory.Melee2->Init(this, *InventoryLayout.Find(EStorageName::Melee2));
  // ...
}

Now I have the problem that a very long list of FInventory members is getting initialized in UInventoryComponent::UInventoryComponent(). I think it would be much cleaner if the FInventory would care for this. However, CreateDefaultSubobject<T>(FName) does not exist inside a struct.

Should I probably make the FInventory a UObject too?

Thank you!

Btw: Why is my syntax highlighting diffent to yours?

CreateDefaultSubobject should be used inside constructors, while NewObject should be used in other places. But not in static context.

For example you could initialize one of those slots later during gameplay :

UInventoryComponent::UInventoryComponent()
{
    Inventory.Melee1 = CreateDefaultSubobject<USlotStorage>("Melee1");
    Inventory.Melee1->Init(...);

    Inventory.Melee2 = nullptr;
}

UInventoryComponent::InitMelee2()
{
    if (!Inventory.Melee2)
    {
        Inventory.Melee2 = NewObject<USlotStorage>(this);
        Inventory.Melee2->Init(...);
    }
}

Having a long list doesn’t sound like a problem.
If you want to initialize them from FInventory, you can provide a method to do so.
You just have to pass an UObject that’s basically gonna be the “parent” of all those sub-objects :

FInventory::CreateSubobjects(UObject* Outer)
{
    Melee1 = Outer->CreateDefaultSubobject<USlotStorage>("Melee1");
    Melee1->Init(Outer, *InventoryLayout.Find(EStorageName::Melee1));

    Melee2 = Outer->CreateDefaultSubobject<USlotStorage>("Melee2");
    Melee2->Init(Outer, *InventoryLayout.Find(EStorageName::Melee2));

    Ranged1 = Outer->CreateDefaultSubobject<USlotStorage>("Ranged1");
    Ranged1->Init(Outer, *InventoryLayout.Find(EStorageName::Ranged1));
}

Then just call it from the component constructor :

UInventoryComponent::UInventoryComponent()
{
    Inventory.CreateSubobjects(this);
}
1 Like

Thank you for the explanation! Maybe I could ask you one last related question.

You problaly noticed, that I pass a reference of the Owner down like this way:

  • UInventoryComponent has a TObjectPtr<ATheThirdPersonGameCharacter>
  • UStorage each have a TObjectPtr<UInventoryComponent>

To enable UStorage to attach AActors to Sockets of the Character Mesh. Is it okay to propagate/store owner references this way (see PawnOwner in CharacterMovementComponent) or should I use different kind of pointers?

May I ask what you mean by static context?

I have a case where I in the BeginPlay function of my override of the GameMode class call NewObject on my UObjectSpawner class.

void ABlubberGameModeBase::BeginPlay()
{
Super::BeginPlay();

PrivInitObjectSpawner();

}

void ABlubberGameModeBase::PrivInitObjectSpawner()
{
if (!ObjectSpawnerBlueprint)
{
UE_LOG(LogTemp, Warning, TEXT(“No Object Spawner Blueprint”));
return;
}

myObjectSpawner = NewObject<UObjectSpawner>(ObjectSpawnerBlueprint);
    myObjectSpawner->Init(GetWorld());

}

But this results in an instance of the ObjectSpawner that does not get its blueprint for the AFish class set, even though I have specified it in the editor.

The is the header for the game mode:

#pragma once

#include “CoreMinimal.h”
#include “GameFramework/GameModeBase.h”
#include “BlubberGameModeBase.generated.h”

class UObjectSpawner;
class AOcean;
class AOceanFloor;
/**
*
*/
UCLASS()
class BLUBBER_API ABlubberGameModeBase : public AGameModeBase
{
GENERATED_BODY()
public:
ABlubberGameModeBase();

UPROPERTY(EditDefaultsOnly)
TSubclassOf<UObjectSpawner> ObjectSpawnerBlueprint;

static const int ObjectsYValue = 1;

private:
void PrivInitObjectSpawner();
void BeginPlay() override;

UPROPERTY()
UObjectSpawner* myObjectSpawner;

};

and the Object Spawner class:

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include “CoreMinimal.h”
#include “UObject/NoExportTypes.h”
#include “ObjectSpawner.generated.h”

class AFish;
class ABlubberGameModeBase;
class UWorld;
/**
*
*/
UCLASS(Blueprintable)
class BLUBBER_API UObjectSpawner : public UObject, public FTickableGameObject
{
GENERATED_BODY()
public:
UObjectSpawner();

void UpdateSpawningFish();
void SpawnFish();

UPROPERTY(EditDefaultsOnly)
TSubclassOf<AFish> myFishBlueprint;

UPROPERTY(EditDefaultsOnly)
float mySpawnDistance;
UPROPERTY(EditDefaultsOnly)
float myFishSpawnCooldown;

bool IsTickable() const override { return bCanTick; }
bool IsTickableInEditor() const override { return false; }
bool IsTickableWhenPaused() const override { return false; }
TStatId GetStatId() const override { return TStatId(); }
void Tick(float DeltaTime) override;

void Init(UWorld* aWorld);
UWorld* GetWorld() const override;

private:
FTimerHandle mySpawnFishTimer;
UWorld* myWorld;
bool bCanTick;
};

// Fill out your copyright notice in the Description page of Project Settings.

#include “ObjectSpawner.h”

#include “Kismet/GameplayStatics.h”
#include “Kismet/KismetMathLibrary.h”

#include “Fish.h”
#include “BlubberGameModeBase.h”
#include “Walrus.h”

#include “Engine/Engine.h”

UObjectSpawner::UObjectSpawner()
{
mySpawnDistance = 1000.f;
myFishSpawnCooldown = 1.f;
bCanTick = true;
}

void UObjectSpawner::Init(UWorld* aWorld)
{
myWorld = aWorld;
bCanTick = true;
}

void UObjectSpawner::Tick(float DeltaTime)
{
UpdateSpawningFish();
}

void UObjectSpawner::UpdateSpawningFish()
{
UWorld* world = GetWorld();
if (!world)
return;
ACharacter* player = UGameplayStatics::GetPlayerCharacter(world, 0);
if (!player)
return;

FTimerManager& timerManager = world->GetTimerManager();
if(!timerManager.IsTimerActive(mySpawnFishTimer))
	timerManager.SetTimer(mySpawnFishTimer, this, &UObjectSpawner::SpawnFish, myFishSpawnCooldown);

}

void UObjectSpawner::SpawnFish()
{
if (!myFishBlueprint)
{
UE_LOG(LogTemp, Warning, TEXT(“No Object Spawner Blueprint”));
return;
}

UWorld* world = GetWorld();
if (!world)
	return;

ACharacter* player = UGameplayStatics::GetPlayerCharacter(world, 0);
if (!player)
	return;
AWalrus* walrus = Cast<AWalrus>(player);
if(!walrus)
	return;

FVector location;
location.Y = 0;
location.Z = 0;
location.X = walrus->GetActorLocation().X + mySpawnDistance;

FActorSpawnParameters SpawnParams;
AFish* fishSpawned = world->SpawnActor<AFish>(myFishBlueprint, location, FRotator(0.f), SpawnParams);

}

UWorld* UObjectSpawner::GetWorld() const
{
return Super::GetWorld();//myWorld;
}

myFishBlueprint on the newly created instance is nullptr, despite it being set on the default instance.

In this case I was referring to code that executes to initialize global/static variables at C++ initialization (which is normally called the CRT initialization afaik).


The first parameter of NewObject is for the Outer (aka. parent/owner of the new object), not the class. Here you are spawning the native class UObjectSpawner where myFishBlueprint is not set, instead of spawning whatever ObjectSpawnerBlueprint points to.
Should be like this :

myObjectSpawner = NewObject<UObjectSpawner>(this, ObjectSpawnerBlueprint);

Is there any particular reason you are using UObject for this ?
Your spawner object shares same lifecycle as gamemode, and needs a world to live in and spawn objects.
Sounds like an AActor would be more fitting.

2 Likes

Thanks for the reply! That clarifies as lot!

My understanding was that AActor should be used for things spawned in the world, so I wanted to see if I could get this to work without the Actor class. I’d be happy to hear your take on when to use UObject and when not to use it.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.