Trying to create instanced list of blueprints, subclassed from C++ class, no duplicates

Hi everyone

I’m trying to set up a system (a Utility AI system) to allow users to create Blueprint classes from a C++ base class which itself inherits from UObject. These Blueprints are to be stored in a list (either TSet or TArray) in a Component with no duplicate entries and which allow inline editing of a property within the child Blueprint such that each will have it’s own value for this property for different instances of the owning component. I also want to ensure that only child classes of the base class are listed in the editor when populating the list which is why I’m trying to use class types and the abstract class specifier in the UCLASS macro.

The problem is that I am really not sure how to achieve this and I clearly am not understanding how class variables, as in TSubclassOf, and pointers to instances relate to each other. I have done research and I understand the TSubclassOf is a class template, not an instance, and that it can be used to create an instance via NewObject or GetDefaultObject() and returns a pointer to that instance.

I had assumed that I would create a set (TSet) of TSubclassOf class types and use them to populate an array (TArray) of pointers to the base class using NewObject in BeginPlay. This works if I don’t use instancing but if I try to used instanced in the UPROPERTY macro for the class types I get an error, not surprisingly and if I put the pointers to the instances in the set I can put in duplicates of the class type, again not suprisingly as they are pointing to different places in memory. I have created a small test project to experiment and tried a few variations of TSet, Instanced, Blueprintable and EditInlineNew but I can get close to the desired behaviour but never quite there. I’ve attached my test code and examples of the results.

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

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "MyTestCPPUObject.generated.h"

/**
 * 
 */
UCLASS(DefaultToInstanced, Blueprintable, Abstract, EditInlineNew)
class MYUCLASSTESTPROJECT_API UMyTestCPPUObject : public UObject
{
	GENERATED_BODY()
public:
	UPROPERTY(BlueprintReadWrite, EditAnywhere)
	float AFloatProperty = 0;
};

Base class for Blueprint child objects

#include "MyTestCPPUObject.h"
#include "MyUPropertyTestActor.generated.h"

UCLASS()
class MYUCLASSTESTPROJECT_API AMyUPropertyTestActor : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AMyUPropertyTestActor();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	// class template varaibles?
	UPROPERTY()
	TSet<TSubclassOf<UMyTestCPPUObject>> myTestClasses;
	// the pointers to the actual instances?
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Instanced)
	TArray<UMyTestCPPUObject*> myTestInstancees;
};

Test Actor storing list of blueprint child objects

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


#include "MyUPropertyTestActor.h"

// Sets default values
AMyUPropertyTestActor::AMyUPropertyTestActor()
{
	PrimaryActorTick.bCanEverTick = true;
}

// Called when the game starts or when spawned
void AMyUPropertyTestActor::BeginPlay()
{
	Super::BeginPlay();

	// Create instances for use in game, though not sure if I need to do this 
	// if they are instanced, how else to create the objects though?
	for (auto testClass : myTestClasses)
	{
		UMyTestCPPUObject* newInstance = NewObject<UMyTestCPPUObject>(testClass);

		if (newInstance)
		{
			myTestInstancees.Add(newInstance);
		}

	}
	
}

// Called every frame
void AMyUPropertyTestActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

Test Actor with BeginPlay creating instances. Edited this too as I established that looping through the myTestClasses set does nothing, as I should have known, so the instances are solely derived from the Blueprint assets

I created two instances of my test Actor and gave them both an instance of the same Blueprint asset and was able to give them different values for thier property which worked, but currently I can also add duplicate entries, that is, of the same child class type as you can see below.
test actor instanced property
Test Actor 1 with correct editable list of Blueprint instances

another test actor instanced property
Test Actor 2 with correct editable list of Blueprint instances

test child blueprint asset
The child Blueprint, this inherits from UClass and has only on float property which is editable anywhere


Different Actor instances printing out the respective values of thier copies of the child Blueprint, correctly

duplicate entry incorrect
Test Actor 1 with duplicate entry in list of Blueprint instances, undesired behaviour (edited as I highlighted the name of the base class type, not the child)

output showing duplicate blueprint instance
Test Actor with duplicated Blueprint output, undesired behaviour.

Any suggestions for how to achieve this would be great and an explanation of how class types and instances work together would also be really helpful.

Yeah using Instanced/EditInlineNew sounds like what you need, but you won’t be able to guarantee unicity of the classes of instanced objects via built-in methods.

Best you can do is implement validation manually and throw an error at the user, for example :

#if WITH_EDITOR
#include "Logging/MessageLog.h"
#include "Misc/UObjectToken.h"
void AMyUPropertyTestActor::CheckForErrors()
{
    Super::CheckForErrors();

    TSet<UClass*> Classes;
    for (auto Obj : myTestInstances)
    {
        if (!Obj)
            continue;

        UClass* Class = Obj->GetClass();
        if (Classes.Contains(Class))
        {
            FMessageLog("MapCheck").Error()
            ->AddToken(FUObjectToken::Create(this))
            ->AddToken(FTextToken::Create(FText::FromString("has duplicate instanced object class: ")))
            ->AddToken(FUObjectToken::Create(Class));
        }
        Classes.Emplace(Class);
    }
}
#endif

Hi Chatouille, thanks for your reply. Yeah, I’m starting to realise this can’t be done as is. I’m going to look at writing a custom details panel for it as I do want this plugin to be designer friendly. Cheers for the help

An alternative to consider: What if there was a way that didn’t require them to be unique?

I really like instanced objects and the power they can allow for extensibility from multiple sources. In my experiences so far, I’ve usually found it best to tackle this problem (at least when dealing with arrays of them) to assume duplicates and factor that into the design.

Think of it similar to components. Imagine if you were limited to a single mesh component, awfully limiting. Or take an component that manages inventory. It could make sense for that to be unique to an actor, but you open up some design space if you’re not so rigid. Perhaps one inventory is what is equipped, one inventory is your backpack and one is the bag-of-holding you have equipped. Just an example.

I’m not suggesting that you’re wrong to attack it with an eye to uniqueness, but reframing the problem to not require uniqueness has come up a few times at work when reviewing designs by junior engineers and it’s always worked out for the better for us to drop that restriction one way or another. Worse case there are some duplicates that are redundant but don’t cause a problem to coexist with another instance.

Also, you shouldn’t need the myTestClasses or the construction code in BeginPlay. The Instanced keyword should do all that for you.

Hi MagForceSeven

Thanks for your reply, I did discover that the TSet and loop to create instances are now redundant and I’ve removed that code.
Regarding your other comment, I’m building a Utility AI system and the part involved here is assiging utility satisfaction to actions. In this case it’s possible to create a situation where duplicate utility satisfactions could contradict each other which would potentially break the AI, and the problem could be hard to find.
As I’m trying to make this system very designer friendly I would prefer to reduce the possibilty of errors by the designers which they may find hard to understand. My temporary solution is to give a warning when duplicates are encountered, or maybe even an error and then force a return to the editor.
Preventing the possibility of duplicates in this case is definitely my preferred option so I’ll probably go ahead with a custom details panel in the longer term.