Constructing an UObject while assigning object members

For an object derived from UObject I’m using NewObject<>() and assign its members using a separate Init() function. I don’t like it (e.g. I’d like to use const members assigned at construction time), but this seems to be the way to go. When using UCLASS() and GENERATED_BODY() and define a custom constructor with parameters, I get the compilation error:


error C2338: You have to define MyUObject::MyUObject() or MyUObject::MyUObject(const FObjectInitializer&). This is required by UObject system to work correctly.

Now, when I derive from UObject and omit the usage of UCLASS()/GENERATED_BODY() and using whichever constructors I like, the program compiles fine. Instantiating also seems to work, however when I try to assign that object, the compilation fails b/c UObject::UObject(const UObject &) already defines the copy constructor.

So I’m stuck to NewObject<>(), however that function does not allow to pass arguments to the objects constructor. Is that correct?
Is it meaningful to add my own copy constructor in MyUObject or would that mitigate the functionality of UObject?
Is it meaningful to omit UCLASS()/GENERATED_BODY()?

Thanks in advance!

1 Like

I would like to know that also, I was looking for a way to do the same yesterday but couldn’t find anything.

There is no way to create custom constructors for UObjects, or pass parameters through NewObject. An Init() function (or public variables and direct assignment) is currently the way to go, even if it isn’t very pretty.

Understood, thanks. Weird that they didn’t put something like SpawnActorDeferred for NewObject.

Thanks for the answer! It is good to know that this is the way Unreal Engine should be used instead of always thinking “I’m doing smthg wrong”.

Sounds to me you are fighting against the engine instead of just using ‘TSubClassOf’.
Let’s say you have a base UObject class with default values assigned in constructor:



UCLASS()
class MYMODULE_API UMyObject : public UObject {
    GENERATED_BODY()
public:

    UPROPERTY(Category = Default, EditAnywhere)
    int32 SomeValue;

};




UMyObject::UMyObject() {
   SomeValue = 123;
}


If you instantiate that object from somewhere else, the default value of *SomeValue *of the new object will always be ‘123’, because constructor:



UMyObject* MyObj;

MyObj = NewObject<UMyObject>(this,TEXT("MyObj"));
UE_LOG(LogTemp,Log,TEXT("SomeValue: %i"),MyObj->SomeValue);


So if you/someone creates a Blueprint or instance of the object and, through code or Editor’s UI changes SomeValue to ‘789’, because the way NewObject<> is used there the variable would still be ‘123’ when you create your new object that way so you use an Init() function to fix that;
But if you use the UClass stored in a TSubclassOf field, through code or Editor UI, to assign the instance class to your NewObject<> function then you’d have somewhat what you’re looking for:



private:
    UPROPERTY()
    UMyObject* MyObj;

public:
    UPROPERTY(Category = Default, EditAnywhere, BlueprintReadWrite)
    TSubclassOf<UMyObject> TMyObj;




MyObj = NewObject<UMyObject>(this,TMyObj->GetFName(),RF_NoFlags,TMyObj.GetDefaultObject());
UE_LOG(LogTemp,Log,TEXT("SomeValue: %i"),MyObj->SomeValue);


The print log would be ‘789’ and not ‘123’ (or whatever user has assigned as default value there).
That won’t work for *const *variables though, you can’t make const UPROPERTY().

3 Likes

Not sure I’m understanding: So that other UClass which is assigned to TMyObj contains SomeValue = 789? So instead of an Init() function I have one (or more) classes derived from UClass which can be used to set the initial state of the UObject?

That is a way to assign “default values” of an UObject when using NewObject<> function.
A class default object is generated when you compile and the values you set in C++ constructor are only meaningful until the generated default values of UProperty() are changed and serialized.
If you have to change initial values with an Init() function and you need to call NewObject<> with different values set I assume you have to spawn a same class with different default values, no?!

If that’s the case then just make those value exposed to UEditor and create a Blueprint asset for each instance of your UObject. Even though you create a Blueprint of type ‘UMyObject’, it is a “child” of your C++ class.

TSubClassOf can only hold pointer to class child of your ‘UMyObject’. The Blueprints of type ‘UMyObject’ included.
NewObject<> function can accept an optional param object where you want your default properties values coming from. So when you pass TMyObj.GetDefaultObject() to it, Unreal Engine will automatically assign the initial values of every property you have declared in UMyObject from the Blueprint you set to TMyObj.
It’s a downcast of the child class while keeping its default values instead of using defaults from UMyObject’s constructor. It’s basically a dynamic constructor for an instance of your object.

Because UMyObject is a UCLASS(), Unreal generates a default object from your C++ class when you compile.
When you create another C++ class that has UMyObject as parent class, another default object is generated for that as well. These objects’ “packages” exist in your packaged game even if you’re not using any instances of them in runtime.

Everything you declare as UPROPERTY() has a default value attached to it. So the engine can modify those default values when you instantiate an object with NewObject<> and feed a “template” to it (GetDefaultObject() function) or can modify default values within the package of default object itself (“expose property on spawn” does modify the package and not the instance for example).

When you deal with UObjects you are dealing with default values in a packaged object in the project and not really the base C++ class the object is generated from, so changing default values has the same effect of assignig something in constructor because the package will be serialized and the values you have set in constructor may have been changed by someone within Editor;
Set a value of a UProperty in constructor of a class then create a blueprint, change that same value, save, close asset/editor then reopen… The default value has been serialized and whatever you set in constructor is gone, replaced.

So EpicGames gave us a way to instantiate a UClass and set default values of that object while in C++ we can still hold a pointer to the object as if it’s still just the C++ base class.

2 Likes

Since I’m using a lot of C++ classes derived from UUserWidgets (with UProperty members for UWidgets) as base classes for UserWidgets created in the Editor - wherein I assign instances of UWidgets to those UProperty members - I’m basically using this methodology all the time w/o recognizing it as such. Thanks a lot for the educational explanations! :slight_smile:

I do have a problem with virtual functions overridden in the derivated class never gets called (Unreal Version 4.21.2). Instead, the base class’s function gets called (which is more or less empty). Down in the CPP, I do have two variants of the “newObject” line. The one commented out (newObject<UMoveBehavior_SeekFlee> instead of newObject<UMoveBehavior_Base>) performs the derived " getSteering " but, of course, it is not changeable anymore in the editor. What am I doing wrong?

My Setup (of course I cut the unneeded part):

HEADER



// UMoveBehavior_Base ----------------------------------------------------------------------------------------------------------------
UCLASS()
class ACT_API **UMoveBehavior_Base **: public UObject
{
    GENERATED_BODY()

public:
    virtual SteeringOutput getSteering(const MoveBehaviorParameter& moveParam);
};

// UMoveBehavior_SeekFlee ----------------------------------------------------------------------------------------------------------------
UCLASS()
class ACT_API **UMoveBehavior_SeekFlee **: public **UMoveBehavior_Base**
{
    GENERATED_BODY()

public:
    // returns the desired steering output
    virtual SteeringOutput getSteering(const MoveBehaviorParameter& moveParam) override;
};

// UBTTask_MoveToWithBehavior ----------------------------------------------------------------------------------------------------------------
UCLASS()
class ACT_API UBTTask_MoveToWithBehavior : public UBTTask_BlackboardBase
{
    GENERATED_BODY()

**public:
    UPROPERTY(Category = Node, EditAnywhere, BlueprintReadWrite)
    TSubclassOf<UMoveBehavior_Base> TMovementBehaviorSelector;**

**private:
    UMoveBehavior_Base* pMovementBehavior;**

public:
    virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};


CPP



// UMoveBehavior_Base ----------------------------------------------------------------------------------------------------------------
SteeringOutput UMoveBehavior_Base::getSteering(const MoveBehaviorParameter& moveParam)
{
    print(FColor::Blue, "Base_getSteering");
    return *(new SteeringOutput);
};

// UBTTask_MoveToWithBehavior ----------------------------------------------------------------------------------------------------------------
EBTNodeResult::Type UBTTask_MoveToWithBehavior::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
    print(FColor::Magenta, OwnerComp.GetAIOwner()->GetName() + "ExecuteTask");

    // create behavior
    if (pMovementBehavior == nullptr)
    {
**     //pMovementBehavior = NewObject<UMoveBehavior_SeekFlee>((UObject *)GetTransientPackage(), TMovementBehaviorSelector->GetFName(), RF_NoFlags, TMovementBehaviorSelector.GetDefaultObject());
        pMovementBehavior = NewObject<UMoveBehavior_Base>((UObject *)GetTransientPackage(), TMovementBehaviorSelector->GetFName(), RF_NoFlags, TMovementBehaviorSelector.GetDefaultObject());**
    }

** steering = pMovementBehavior->getSteering(moveParam);    // get steering**
    return EBTNodeResult::Succeeded;
}


You’re using NewObject wrong. It should be NewObject<UMoveBehavior_Base>(this, TMovementBehaviorSelector). Also, pMovementBehavior needs to be UPROPERTY() to keep the referenced object alive.

Lol… I am sure that I tried this combination… In fact, I was working for more than 5h to fix this issue, but obviously, something was missing.
Thank you a lot Zeblote, works just pretty :smiley: