Quick answer: Component BeginPlay order isn’t guaranteed. Do per-component init in InitializeComponent, and do cross-component wiring in the owning actor’s BeginPlay (which runs after all components’).
A HealthComponent’s BeginPlay reads a StatsComponent that isn’t initialized yet — sometimes. Component BeginPlay order varies.
The Lifecycle Order
- All components’
OnRegister/InitializeComponent(if bWantsInitializeComponent). - Each component’s
BeginPlay— order between components is not defined. - The owning Actor’s
BeginPlay— runs after every component’s BeginPlay.
Per-Component Init
UMyComponent::UMyComponent()
{
bWantsInitializeComponent = true;
}
void UMyComponent::InitializeComponent()
{
Super::InitializeComponent();
// set up THIS component's own state only
}
Cross-Component Wiring in the Actor
void AMyCharacter::BeginPlay()
{
Super::BeginPlay();
// every component's BeginPlay has run by now
HealthComp->BindToStats(StatsComp);
}
The actor’s BeginPlay is the one place where every component is guaranteed initialized. Do the wiring there.
Don't Rely on Add Order
Even “the order I added them in the Blueprint” isn’t a contract. Treat component BeginPlay as parallel-ish; never have one read another’s BeginPlay-set state.
Verifying
Spawn the character many times. HealthComponent always sees an initialized StatsComponent. No intermittent null/zero reads.
“Component BeginPlay order is undefined. Wire components together in the actor’s BeginPlay.”
If component A truly must init before B, consider making B a child of A or merging them — ordering dependencies are a smell.