Components, garbage collection & replication primer (UE 5.8 C++)
Depth for unreal-cpp-gameplay. Verified against the Gameplay Framework docs and the UE5
C++ programming references. Covers component creation/attachment, the UPROPERTY/GC ownership
rules that trip up newcomers, and a minimal replication example.
Creating and attaching components
Default subobjects are created in the constructor; runtime components use NewObject +
RegisterComponent.
// In the constructor — built into the Class Default Object, editable in the Blueprint subclass.
ACharacterBase::ACharacterBase()
{
SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
SpringArm->SetupAttachment(RootComponent); // attach to an existing component
SpringArm->TargetArmLength = 300.f;
Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
Camera->SetupAttachment(SpringArm); // socket chain: root -> arm -> camera
}// At runtime (e.g. add a component after spawn):
UAudioComponent* Audio = NewObject<UAudioComponent>(this);
Audio->RegisterComponent(); // required, or it won't tick/render
Audio->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);CreateDefaultSubobjectis only valid in a constructor; using it later asserts.SetupAttachmentis for constructor-time hierarchy;AttachToComponentis the runtime call.
Garbage collection ownership (the #1 newcomer crash)
The UE garbage collector only keeps UObjects alive if it can reach them through a reflected
reference. A raw UObject* member that isn't a UPROPERTY is invisible to the GC and can be
collected, leaving a dangling pointer.
UPROPERTY() // GC sees it -> stays alive while this owner lives
TObjectPtr<UMyDataObject> Data;
UPROPERTY()
TArray<TObjectPtr<AActor>> Tracked; // containers of UObjects also need UPROPERTY
UMyDataObject* Raw; // BUG: not tracked; may be GC'd -> crashRules:
- Every
UObject/Actor pointer that must persist gets aUPROPERTY(useTObjectPtr<T>in UE5; raw pointers still compile butTObjectPtradds access tracking in the editor). - Plain C++ data (ints, FStrings, FVectors, USTRUCTs of POD) doesn't need this — it's owned by value.
- For a UObject you create that has no UPROPERTY owner (rare),
AddToRoot()pins it andRemoveFromRoot()releases it — but prefer proper ownership.
USTRUCT and UENUM for reflected data
UENUM(BlueprintType)
enum class EWeaponType : uint8 { Pistol, Rifle, Shotgun };
USTRUCT(BlueprintType)
struct FWeaponStats
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite) int32 Damage = 10;
UPROPERTY(EditAnywhere, BlueprintReadWrite) float FireRate = 0.25f;
};BlueprintType makes the enum/struct usable as Blueprint variables.
Minimal replication (multiplayer)
// Header
UPROPERTY(ReplicatedUsing = OnRep_Health)
float Health = 100.f;
UFUNCTION()
void OnRep_Health(); // client callback when Health replicates
// Source
#include "Net/UnrealNetwork.h"
void AMyActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& Out) const
{
Super::GetLifetimeReplicatedProps(Out);
DOREPLIFETIME(AMyActor, Health); // register the property for replication
}- Set
bReplicates = true;in the constructor for the actor to replicate at all. - Server authority: gameplay changes happen on the server; clients receive replicated state and
react in
OnRep_callbacks.
Gotchas
NewObjectwithoutRegisterComponentgives a component that exists but never ticks or renders.- Forgetting
#include "Net/UnrealNetwork.h"breaksDOREPLIFETIME. TObjectPtrimplicitly converts to the raw pointer for calls, so existing->/.usage is unchanged; the difference is GC visibility and editor tooling, not syntax at call sites.