Download

Pointer to Actor object becomes null for unknown reason

Hi guys,

Completely new to UE4 therefore I may be doing something completely wrong, but here goes:

Context:

I have an actor called ABattleField, and an editor-only actor component called UBattleFieldGeneratorComponent.
UBattleFieldGeneratorComponent is meant to be a component of ABattleField used to assist the level designers placing static meshes at editor time, and should not appear at all during any gameplay.
The component needs a pointer to ABattleField; therefore, I’ve decided that when an ABattleField object is constructed, it should also do CreateDefaultSubobject<UBattleFieldGeneratorComponent>, and assign itself to the newly created component’s ABattleField pointer.

Relevant code follows:



// BattleField.h
UCLASS()
class MYGAME_API ABattleField : public AActor
{
  GENERATED_BODY()

public: 
  // Sets default values for this actor's properties
  ABattleField();
  
  UPROPERTY(VisibleInstanceOnly)
  UBattleFieldGeneratorComponent* Generator;

  // ...
}

// BattleField.cpp
ABattleField::ABattleField()
{
  // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
  PrimaryActorTick.bCanEverTick = true;

  // Only initialize generator for editors only
#if WITH_EDITOR
  Generator = CreateDefaultSubobject<UBattleFieldGeneratorComponent>(TEXT("Battlefield Generator Component"));
  Generator->SetBattleField(this);
#endif

  // ...
}


// BattleFieldGeneratorComponent.h
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class MYGAME_API UBattleFieldGeneratorComponent : public UActorComponent
{
  GENERATED_BODY()

public: 
  // Sets default values for this component's properties
  UBattleFieldGeneratorComponent();

  UFUNCTION(CallInEditor, Category="Battlefield Generation")
  void GenerateBattleField();

private:
  UPROPERTY()
  ABattleField* BattleField;

  // ...
}

// BattleFieldGeneratorComponent.cpp

UBattleFieldGeneratorComponent::UBattleFieldGeneratorComponent()
{
  // 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 = true;
  bIsEditorOnly = true;
}

void UBattleFieldGeneratorComponent::SetBattleField(ABattleField* InBattleField)
{
  if (BattleField == nullptr)
  {
    BattleField = InBattleField;
  }
}

void UBattleFieldGeneratorComponent::GenerateBattleField()
{
  if (BattleField == nullptr)
  {
    UE_LOG(LogTemp, Error, TEXT("BattleField of Generator Component cannot be null! did you forget to initialize it?"));
    return;
  }

  // ... Do something with BattleField pointer
}


Symptom:
Initially this seems to be fine. I can drag&drop an ABattleField object into the level, choose the generator component of ABattleField from within editor, and execute GenerateBattleField() to place static meshes into the scene at editor time. Even after closing the editor and restarting, recompiling code, with ABattleField object already present in saved level, everything seemed to be working properly.

However, for unknown reason, maybe after numerous editor restarts or workspace machine reboot, upon re-opening the level and try to execute GenerateBattleField() on an ABattleField object that is saved from previous sessions, suddenly the code breaks, and the component complains that the pointer to ABattleField is null.

Diagnostics:
I have attempted to set breakpoints in the functions, and see that upon loading the level, ABattleField constructor is indeed being run to construct a new UBattleFieldGeneratorComponent, and assigning the newly constructed component’s pointer to self. However, after level load is complete and I attempt to execute GenerateBattleField() again (I have verified that this is the same component object, as its ‘this’ pointer is pointing to the exact same address in memory during the time BattleField pointer is being passed and assigned), I see that the BattleField pointer is now null.

Question:
What am I doing wrong here? Is there something else I need to do to prevent the pointer from becoming null?
Or, my whole design is flawed and that’s not how UE4 works; pointers assigned at construction time does not point to valid objects when I attempt to edit / play the game? Thus I need to scan the whole scene from within BeginPlay() and assign the pointers then?

Don’t you happen to be using the Hot Reload feature (the big Compile button), aren’t you? If so, I recommend stopping using it. A good read: Hot Reload and Live Coding | UE4 Community Wiki

I am guilty of using this yes - particularly because I find reopening the editor every time I compile code to be a huge productivity killer.

Is this hot-reload the only reason why its breaking my code? Meaning the way I wrote the code will achieve what I want?

The code you posted seems totally ok for me, so unless the issue is somewhere else in the code I would say that the issue here could be hot reload itself (already had many issues while using it myself).

I don’t think the problem is in the way of compilation. Have you tried testing your code without the “if WITH_EDITOR”?

I can give it a try and see if the problem persists. But given that this component should be editor only, should it be wrapped within this macro?

Technically yes. There may also be a reason for the fact that the generator pointer refers to the object that was BEFORE the level was saved. So you can see that ABattleField instance exists in memory, except that the generator link is not saved to this instance. But this is just my guess. Anyway, it seems to me that it is safer to just grab the link for BattleField in the generator via Cast <ABattleField>(GetParentActor()).

Understood, that would be my fallback plan if this approach doesn’t work.

Another reason I wanted to do things this way is that the function GenerateBattleField() will perform a similar action. When I execute GenerateBattleField() at editor time, this function will create and place multiple static mesh objects into the level, then ABattleField will have a TArray of pointers tracking these objects.

If UE4 wipes pointer references created at editor time by design, then I’m going to de-reference a lot of null pointers at play time. I would like to avoid the act of scanning the whole level in *BeginPlay() *for those static mesh objects and try to reconstruct the TArray in the order I needed those objects to be in.