Ue Actor Component Architecture
Use this skill when working with Actor and component design in Unreal Engine. Triggers on: Actor, component, BeginPlay, Tick, SpawnActor, lifecycle, CreateDefaultSubobject, composition, EndPlay, PostInitializeComponents, UActorComponent, USceneComponent, UINTERFACE, attachment, spawn, interface. See references/actor-lifecycle.md and references/component-types.md for detailed tables.
- Skill ID
- quodsoler/unreal-engine-skills/ue-actor-component-architecture
- Publisher
- quodsoler
- Repository
- unreal-engine-skills
- Installs
- 0
- Files
- 3
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
quodsoler/unreal-engine-skills/ue-actor-component-architectureInstalls these files- SKILL.md
- references/actor-lifecycle.md
- references/component-types.md
What this skill tells the agent
UE Actor-Component Architecture
You are an expert in Unreal Engine's Actor-Component architecture.
Project Context
Before responding, read .agents/ue-project-context.md for the project's subsystem inventory, coding conventions, and any existing actor hierarchies or component patterns. This tells you which base classes are established and what naming conventions apply.
Information Gathering
Clarify the developer's specific need before diving in:
- New actor from scratch, or adding behavior to an existing one?
- Logic-only (UActorComponent) or needs world position (USceneComponent)?
- Spawning requirement (deferred init, pooling, net-spawned)?
- Lifecycle bug (BeginPlay/Constructor confusion, component not initialized)?
- Cross-actor behavior via interfaces?
Core Architecture Mental Model
Unreal's Actor-Component system is composition over inheritance. An AActor is a container that owns components. Behavior, rendering, collision, and logic are all expressed through UActorComponent subclasses.
UObject
└── AActor (placeable/spawnable world entity)
└── owns N x UActorComponent (reusable behavior units)
└── USceneComponent (adds transform + attachment)
└── UPrimitiveComponent (adds collision + rendering)AActor is a full UObject — never new/delete an actor. Always use SpawnActor and Destroy.
Actor Lifecycle
Full event order and safety rules are in references/actor-lifecycle.md. Key sequence:
Constructor → CreateDefaultSubobject, tick config, default values
PostActorCreated → spawned actors only; before construction script
PostInitializeComponents → all components initialized; world accessible
BeginPlay → game running; full logic OK; components BeginPlay fires here
Tick(DeltaTime) → per-frame; each ticking component's TickComponent fires
EndPlay(EEndPlayReason) → cleanup; ClearAllTimers; call Super
Destroyed → pre-GC; avoid complex logicConstructor vs BeginPlay
Constructor runs first on the Class Default Object (CDO) — an archetype used for default values. GetWorld() returns nullptr on the CDO. Never access the world or other actors in the constructor.
// CORRECT — constructor-time only
AMyActor::AMyActor()
{
MeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
SetRootComponent(MeshComp);
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.TickInterval = 0.1f;
}
// CORRECT — world-dependent code belongs in BeginPlay
void AMyActor::BeginPlay()
{
Super::BeginPlay(); // Required — always call Super
GetWorld()->SpawnActor<AProjectile>(...);
}PostInitializeComponents
Called before BeginPlay; components are initialized; world exists. Use it to bind delegates to own components.
void AMyCharacter::PostInitializeComponents()
{
Super::PostInitializeComponents();
HealthComponent->OnDeath.AddDynamic(this, &AMyCharacter::HandleDeath);
}EndPlay — reasons matter
| Reason | When |
|---|---|
Destroyed | Actor->Destroy() called explicitly |
LevelTransition | Map change |
EndPlayInEditor | PIE session ended |
RemovedFromWorld | Level streaming unloaded the sublevel |
Quit | Application shutdown |
void AMyActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
GetWorld()->GetTimerManager().ClearAllTimersForObject(this);
Super::EndPlay(EndPlayReason);
}Network lifecycle note
Replicated actors: on clients, BeginPlay may fire before all replicated properties arrive. Use OnRep_ callbacks for initialization that depends on replicated state. PostNetReceive() fires after each replication update (including the initial one); guard one-time setup inside it with a bHasInitialized flag. PostNetInit is not a standard AActor virtual and should not be used as a general init hook.
Component System
The three layers
| Class | Transform | Rendering/Collision | Use for |
|---|---|---|---|
UActorComponent | No | No | Pure logic — health, inventory, AI data |
USceneComponent | Yes | No | Transform anchors, grouping, pivot points |
UPrimitiveComponent | Yes | Yes | Meshes, shapes, anything visible or collidable |
Notable subclasses: UStaticMeshComponent, USkeletalMeshComponent, shape primitives (UCapsuleComponent, UBoxComponent, USphereComponent), UWidgetComponent (3D UI in world space — requires "UMG" module), USpringArmComponent + UCameraComponent, UChildActorComponent. See references/component-types.md.
Component creation
In the constructor (for default components that appear in the Details panel):
AMyActor::AMyActor()
{
// CreateDefaultSubobject registers the component as a subobject —
// it is serialized with the actor and visible in Blueprint editors.
MeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
SetRootComponent(MeshComp);
ArrowComp = CreateDefaultSubobject<UArrowComponent>(TEXT("Arrow"));
ArrowComp->SetupAttachment(MeshComp); // Parent set here; no world needed
HealthComp = CreateDefaultSubobject<UHealthComponent>(TEXT("Health"));
// Logic-only components need no attachment
}At runtime (dynamic addition):
void AMyActor::AddLight()
{
// NewObject creates but does NOT register with the world
UPointLightComponent* Light = NewObject<UPointLightComponent>(this,
UPointLightComponent::StaticClass(), TEXT("DynamicLight"));
Light->SetupAttachment(GetRootComponent());
Light->RegisterComponent(); // Gives it world presence (render proxy, physics)
Light->SetIntensity(5000.f);
}
void AMyActor::RemoveLight(UActorComponent* Comp)
{
Comp->DestroyComponent(); // Unregisters and marks for GC
}
// UnregisterComponent() removes a component from the world without destroying it (reversible).
// DestroyComponent() marks it for GC — irreversible. Use Unregister when you may re-enable it later.Why this distinction matters: constructor-created components are owned subobjects and participate in the actor's GC root. Runtime components via NewObject are not automatically serialized unless you add them to a UPROPERTY array.
Attachment
// Constructor (SetupAttachment — no world required)
SpringArmComp->SetupAttachment(RootComponent);
CameraComp->SetupAttachment(SpringArmComp);
// Runtime (AttachToComponent — world must exist)
WeaponMesh->AttachToComponent(
CharMesh,
FAttachmentTransformRules::SnapToTargetNotIncludingScale,
TEXT("WeaponSocket") // Named socket on the skeletal mesh
);
WeaponMesh->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);Activation
// In constructor — opt out of auto-activation for optional components
SoundComp->bAutoActivate = false;
// Runtime — Activate() checks ShouldActivate() internally
SoundComp->Activate();
SoundComp->Deactivate();
SoundComp->SetActive(true, /*bReset=*/false);Spawning
Standard spawn
FActorSpawnParameters Params;
Params.Owner = this;
Params.Instigator = GetInstigator();
Params.SpawnCollisionHandlingOverride =
ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
Params.Name = FName("Enemy_Boss"); // deterministic name for replication (must be unique)
AEnemy* Enemy = GetWorld()->SpawnActor<AEnemy>(
AEnemy::StaticClass(), Location, Rotation, Params);Deferred spawning — configure before BeginPlay
Use when the actor's BeginPlay reads data that must be set before it runs.
AEnemy* Enemy = GetWorld()->SpawnActorDeferred<AEnemy>(
AEnemy::StaticClass(), SpawnTransform, Owner, Instigator,
ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
if (Enemy)
{
Enemy->SetEnemyData(EnemyDataAsset); // Set BEFORE BeginPlayMore from quodsoler
Use this skill in a project
Open any RiverX project, search the Skills panel for quodsoler/unreal-engine-skills/ue-actor-component-architecture, and install it in one click.
