Way to implement basic functionality (pure C++ classes vs Unreal classes)

Hi!

I’m currently working on my first project and trying to solve something like a pool for ingame items that might drop after defeating an enemy. For the moment I’d like to provide some kind of a static “database” for the pool (static setup of 5-10 items), so I’m using a TMap that should be filled on game start and I only need one instance.

I implemented two approaches (as my first one did not work) and some variations… let me explain those and forgive me to paste a lot of code (as I think it might be helpful to expain my question)

  1. Pure C++
class ItemPool
{
public:
	ItemPool();
	RawItem* GetWeaponItem(const FName ItemName);
private:
	TMap<FName, RawItem*> Weapons;
};
  1. As UObject
UCLASS(NotBlueprintable)
class PROTOTYPE_20241003_API UItemPool : public UObject
{
	GENERATED_BODY()

public:
	URawItem* GetWeaponItem(const FName ItemName);

private:
	UPROPERTY(VisibleDefaultsOnly)
	TMap<FName, URawItem*> Weapons = TMap<FName, URawItem*>();

And here is the usage:

UCLASS(Blueprintable)
class PROTOTYPE_20241003_API UItemSpawner : public UActorComponent
{
	GENERATED_BODY()

public:
	UItemSpawner()
	{
		UItemPool = CreateDefaultSubobject<UItemPoolU>("ItemPool");
		ItemPool = new NewItemPool();
	};

	UFUNCTION(BlueprintCallable)
	void SpawnWeaponActor(FName ItemName);

private:
	UPROPERTY(VisibleDefaultsOnly)
	TObjectPtr<UItemPool> UItemPool;
	
	ItemPool* ItemPool;
};

So, whenever I use the UItemPool (UObject) I don’t retrieve items from my implementation and get access violations etc. (or no item is returned as the map is empty). I guess the instance of my UObject is removed by GC…
Not sure why I can’t keep an instance like that. So one “workaround” for the current phase could be to use a either a fresh instance on every call (UItemPool* ItemPool = NewObject<UItemPool>();) or to use a static approach.

On the other hand the pure C++ implementation is working out of the box as expected. No need to make it a UPROP etc.

So, you guessed it… my questions :slight_smile:

  1. Is it okay to use pure C++?
  2. Do I need to keep track of pointers (in pure C++) and free the memory in deconstructors?
  3. when should I switch to Unreal framework? what are are advantages?
  4. why is my UObject implementation not working? (when using the basic implementation, not the fresh classes on call or static)

Thanks for reading. Answers are much appreciated :slight_smile:

ItemPool will be null when you run it because instances of UObjects are copies from CDO. What is a CDO? It is a Class Default Object. It’s like a preview of what the instances will look like. If you use CreateDefaultSubobject, then those will be copied. But raw pointers won’t. So it will be null when you create your own instance.

edit: Calling base class constructor is unnecessary. Leaving it in to keep the original message intact.
Also, your constructor should call the base class constructor in the initializer list.

UItemSpawner() 
  : Super()
{
   ...
}

You then say that UItemPool is null. That’s a bit strange. That would only happen if UItemSpawner no longer exists or has been GCd.

You haven’t shown what the owner of UItomSpawner is so I can’t answer why UItemPoll is null. Also, I don’t know how you got it to compile. I wouldn’t use the class name as a member name.

To answer your questions:

  1. Yes
  2. You cannot use constructors and destructors in the normal C++ way. Yes, you need to manually track raw pointers’ lifetimes. If you have smart pointers, that should be fine (used by destructor eventually), but you should probably use an engine method like EndPlay or some other method that indicates when the object is no longer used.
  3. If you’re using Unreal Engine, you should always use its framework. You’ll run into a lot of problems if you don’t.
  4. Hard to say. Show us the owner of UItemSpawner and how it is created. It is a component, so likely it should be in an actor. Show us how you set that up.

This is unnecessary in this case. The parent class constructor will be run regardless. You only need to manually call it if a) the parent doesn’t have a default constructor or b) the parent has multiple constructors and you want to call a specific one that is not the default.

Ah yeah. I stand corrected. My personal preference got through there I suppose. I just got caught multiple times forgetting to call the base class methods on virtual functions. I’d rather have the habit to do it all the time, even on constructors. But my suggesting it was misplaced.