Download

Components don't have correct world location until relative location has been altered - why?!

I have a component whose structure is an hierarchy of SceneComponents like this:

Point
----Rotator
--------Placer
------------VizSphere (UStaticMeshComponent)

I am adding this component (a UPoint) to my Pawn, and then offsetting its relative location in X by 500 units, like so:


FName NewPointName = MakeUniqueObjectName(this, UPoint::StaticClass(), TEXT("Point"));
UPoint* NewPoint = NewObject<UPoint>(this, UPoint::StaticClass(), NewPointName);
NewPoint->SetupAttachment(PawnRoot);
NewPoint->RegisterComponent();

CurrentPoint = NewPoint;

CurrentPoint->SetRelativeLocation(FVector(500.0f, 0.0f, 0.0f));


What I discovered though, is that if I try to get the world location of all the various sub-components (Rotator, Placer, VizSphere), they return (0,0,0) even though the Point has been moved 500 units. Weird. Through trying to debug this, I discovered that if I set the relative rotation and location of each of these components to any non-zero value, then they suddenly get updated with the correct world location. This happens even if I immediately set the rotation and location back to zero again.

Here is some code showing this:


LogPosition(CurrentPoint->GetComponentLocation(), "Point Location: ");

LogPosition(CurrentPoint->PointRotator->GetComponentLocation(), "Rotator Location before: ");
CurrentPoint->PointRotator->SetRelativeLocation(FVector(0.1f, 0.0f, 0.0f));
CurrentPoint->PointRotator->SetRelativeRotation(FRotator(0.1f, 0.0f, 0.0f));
CurrentPoint->PointRotator->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
CurrentPoint->PointRotator->SetRelativeRotation(FRotator(0.0f, 0.0f, 0.0f));
LogPosition(CurrentPoint->PointRotator->GetComponentLocation(), "Rotator Location after: ");

LogPosition(CurrentPoint->PointPlacer->GetComponentLocation(), "Placer Location before: ");
CurrentPoint->PointPlacer->SetRelativeLocation(FVector(0.1f, 0.0f, 0.0f));
CurrentPoint->PointPlacer->SetRelativeRotation(FRotator(0.1f, 0.0f, 0.0f));
CurrentPoint->PointPlacer->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
CurrentPoint->PointPlacer->SetRelativeRotation(FRotator(0.0f, 0.0f, 0.0f));
LogPosition(CurrentPoint->PointPlacer->GetComponentLocation(), "Placer Location after: ");

LogPosition(CurrentPoint->VizSphere->GetComponentLocation(), "VizSphere Location before: ");
CurrentPoint->VizSphere->SetRelativeLocation(FVector(0.1f, 0.0f, 0.0f));
CurrentPoint->VizSphere->SetRelativeRotation(FRotator(0.1f, 0.0f, 0.0f));
CurrentPoint->VizSphere->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
CurrentPoint->VizSphere->SetRelativeRotation(FRotator(0.0f, 0.0f, 0.0f));
LogPosition(CurrentPoint->VizSphere->GetComponentLocation(), "VizSphere Location after: ");

The LogPosition function is just debugging a vector position to UE_LOG. Here is the output of the above code:


Point Location: 500.0, 0.0, 0.0
Rotator Location before: 0.0, 0.0, 0.0
Rotator Location after: 500.0, 0.0, 0.0
Placer Location before: 0.0, 0.0, 0.0
Placer Location after: 500.0, 0.0, 0.0
VizSphere Location before: 0.0, 0.0, 0.0
VizSphere Location after: 500.0, 0.0, 0.0

As you can see, the Point’s location is correct immediately. But the Rotator, Placer, and VizSphere’s locations are only correct after setting their relative rotation and location to some tiny value, and then setting them back to zero again.

What’s going on here? Why do these components need their relative transforms to be fiddled with before they inherit the correct world location?

OK, I’ve been continuing to mess around with this issue, and discovered the problem. It’s a really strange issue, and I almost feel like I should make a separate post about this but I’ll put it here for now. If I don’t get any response then I’m making another post.

The issue can be summed up like this: Constructors in C++ classes don’t seem to be working correctly in Blueprint versions of those classes.

I’m kinda surprised that I can’t seem to find anything about this anywhere, as it seems like a pretty massive issue, unless I’m massively misunderstanding something (which is very possible, probable even given how new I am to C++).

So in the C++ constructor for my **UPoint **class, I set up the basic hierarchy like this:


// Sets default values for this component's properties
UPoint::UPoint()
{
    // Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
    // off to improve performance if you don't need them.
    PrimaryComponentTick.bCanEverTick = false;

    // ...

    FName RotatorName = MakeUniqueObjectName(this, USceneComponent::StaticClass(), TEXT("Rotator"));
    PointRotator = CreateDefaultSubobject<USceneComponent>(RotatorName);
    PointRotator->SetupAttachment(this);

    FName PlacerName = MakeUniqueObjectName(this, USceneComponent::StaticClass(), TEXT("Placer"));
    PointPlacer = CreateDefaultSubobject<USceneComponent>(PlacerName);
    PointPlacer->SetupAttachment(PointRotator);

    // Viz Sphere
    FName VizName = MakeUniqueObjectName(this, UStaticMeshComponent::StaticClass(), TEXT("VizSphere"));
    VizSphere = CreateDefaultSubobject<UStaticMeshComponent>(VizName);
    VizSphere->SetupAttachment(PointPlacer);

    UStaticMesh* SphereMesh = LoadObject<UStaticMesh>(NULL, TEXT("/Engine/BasicShapes/Sphere.Sphere"), NULL, LOAD_None, NULL);
    VizSphere->SetStaticMesh(SphereMesh);

    FVector SphereScale = FVector(0.05f, 0.05f, 0.05f);
    VizSphere->SetRelativeScale3D(SphereScale);
}

And then, as in the code above, I am adding one of these UPoints to my Pawn in its constructor. Then I have adapted my debug code to include outputting whether these sub-components on the UPoint are even valid or not, while still also testing the positional issue I was having before:


void APawn::DebugPoint()
{
    if (CurrentPoint)
    {
        UE_LOG(LogTemp, Warning, TEXT("CurrentPoint is valid"));
        LogPosition(CurrentPoint->GetComponentLocation(), "Point Location: ");

        if (CurrentPoint->PointRotator)
        {
            UE_LOG(LogTemp, Warning, TEXT("PointRotator is valid"));

            LogPosition(CurrentPoint->PointRotator->GetComponentLocation(), "Rotator Location before: ");
            CurrentPoint->PointRotator->SetRelativeLocation(FVector(0.1f, 0.0f, 0.0f));
            CurrentPoint->PointRotator->SetRelativeRotation(FRotator(0.1f, 0.0f, 0.0f));
            CurrentPoint->PointRotator->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
            CurrentPoint->PointRotator->SetRelativeRotation(FRotator(0.0f, 0.0f, 0.0f));
            LogPosition(CurrentPoint->PointRotator->GetComponentLocation(), "Rotator Location after: ");
        }
        else
        {
            UE_LOG(LogTemp, Warning, TEXT("PointRotator is NOT valid"));
        }

        if (CurrentPoint->PointPlacer)
        {
            UE_LOG(LogTemp, Warning, TEXT("PointPlacer is valid"));

            LogPosition(CurrentPoint->PointPlacer->GetComponentLocation(), "Placer Location before: ");
            CurrentPoint->PointPlacer->SetRelativeLocation(FVector(0.1f, 0.0f, 0.0f));
            CurrentPoint->PointPlacer->SetRelativeRotation(FRotator(0.1f, 0.0f, 0.0f));
            CurrentPoint->PointPlacer->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
            CurrentPoint->PointPlacer->SetRelativeRotation(FRotator(0.0f, 0.0f, 0.0f));
            LogPosition(CurrentPoint->PointPlacer->GetComponentLocation(), "Placer Location after: ");
        }
        else
        {
            UE_LOG(LogTemp, Warning, TEXT("PointPlacer is NOT valid"));
        }

        if (CurrentPoint->VizSphere)
        {
            UE_LOG(LogTemp, Warning, TEXT("VizSphere is valid"));

            LogPosition(CurrentPoint->VizSphere->GetComponentLocation(), "VizSphere Location before: ");
            CurrentPoint->VizSphere->SetRelativeLocation(FVector(0.1f, 0.0f, 0.0f));
            CurrentPoint->VizSphere->SetRelativeRotation(FRotator(0.1f, 0.0f, 0.0f));
            CurrentPoint->VizSphere->SetRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
            CurrentPoint->VizSphere->SetRelativeRotation(FRotator(0.0f, 0.0f, 0.0f));
            LogPosition(CurrentPoint->VizSphere->GetComponentLocation(), "VizSphere Location after: ");
        }
        else
        {
            UE_LOG(LogTemp, Warning, TEXT("VizSphere is NOT valid"));
        }

        return;
    }

    UE_LOG(LogTemp, Warning, TEXT("CurrentPoint is NOT valid"));
}

Now, if on my WorldSettings->GameMode I set the default pawn to be the C++ version of the pawn, here is the output I get:


CurrentPoint is valid
Point Location: 100.0, 0.0, 0.0
PointRotator is valid
Rotator Location before: 100.0, 0.0, 0.0
Rotator Location after: 100.0, 0.0, 0.0
PointPlacer is valid
Placer Location before: 100.0, 0.0, 0.0
Placer Location after: 100.0, 0.0, 0.0
VizSphere is valid
VizSphere Location before: 100.0, 0.0, 0.0
VizSphere Location after: 100.0, 0.0, 0.0

As you can see, all the pointers are valid, and the world location of each component (I’ve offset by 100 in this example) is correct before doing the stupid fiddle with their relative locations.

However, if I create a Blueprint version of the pawn and use that as default pawn instead, here is the output I get (with nothing else changed):


CurrentPoint is valid
Point Location: 100.0, 0.0, 0.0
PointRotator is NOT valid
PointPlacer is NOT valid
VizSphere is NOT valid

The weird thing is, those sub-components have been created - they’re there in the hierarchy during runtime, but the pointers to it’s components are all NULL.

Can someone explain what’s going on here? Why would a C++ constructor work perfectly for a C++ pawn, but not work correctly for a BP version of that pawn?

I would really like to be able to do a lot of functionality in C++ for my pawn, but then create a BP version to add certain things there, and have confidence that it’s the same thing. Is this a bug, or something by design that I don’t understand?

PS.
Apologies for the really long messages, I massively appreciate someone helping me out with this!

Constructors in UE4, in general, are a bit wonky simply due to how the Actor Lifecycle works. In short, Actors are created, constructors are executed, but then members are stomped by the property initializers/blueprint construction scripts/saved data.

In general, you likely want to put initialization code in OnPostLoad or PostInitializeComponents.

https://docs.unrealengine.com/latest…ctorLifecycle/

If I had to guess, I’d say your Blueprint is stomping the values some how and thus they’re null once the Construction scripts for the Blueprint are executed.

Ah, OK, I thought it might be something like this. I can totally build everything without using constructors, just wasn’t sure if I was just doing something wrong!

Thanks for the explanation.