$npx -y skills add quodsoler/unreal-engine-skills --skill ue-actor-component-architectureUse 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, s
| 1 | # UE Actor-Component Architecture |
| 2 | |
| 3 | You are an expert in Unreal Engine's Actor-Component architecture. |
| 4 | |
| 5 | ## Project Context |
| 6 | |
| 7 | 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. |
| 8 | |
| 9 | ## Information Gathering |
| 10 | |
| 11 | Clarify the developer's specific need before diving in: |
| 12 | |
| 13 | - New actor from scratch, or adding behavior to an existing one? |
| 14 | - Logic-only (UActorComponent) or needs world position (USceneComponent)? |
| 15 | - Spawning requirement (deferred init, pooling, net-spawned)? |
| 16 | - Lifecycle bug (BeginPlay/Constructor confusion, component not initialized)? |
| 17 | - Cross-actor behavior via interfaces? |
| 18 | |
| 19 | --- |
| 20 | |
| 21 | ## Core Architecture Mental Model |
| 22 | |
| 23 | 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. |
| 24 | |
| 25 | ``` |
| 26 | UObject |
| 27 | └── AActor (placeable/spawnable world entity) |
| 28 | └── owns N x UActorComponent (reusable behavior units) |
| 29 | └── USceneComponent (adds transform + attachment) |
| 30 | └── UPrimitiveComponent (adds collision + rendering) |
| 31 | ``` |
| 32 | |
| 33 | `AActor` is a full `UObject` — never `new`/`delete` an actor. Always use `SpawnActor` and `Destroy`. |
| 34 | |
| 35 | --- |
| 36 | |
| 37 | ## Actor Lifecycle |
| 38 | |
| 39 | Full event order and safety rules are in `references/actor-lifecycle.md`. Key sequence: |
| 40 | |
| 41 | ``` |
| 42 | Constructor → CreateDefaultSubobject, tick config, default values |
| 43 | PostActorCreated → spawned actors only; before construction script |
| 44 | PostInitializeComponents → all components initialized; world accessible |
| 45 | BeginPlay → game running; full logic OK; components BeginPlay fires here |
| 46 | Tick(DeltaTime) → per-frame; each ticking component's TickComponent fires |
| 47 | EndPlay(EEndPlayReason) → cleanup; ClearAllTimers; call Super |
| 48 | Destroyed → pre-GC; avoid complex logic |
| 49 | ``` |
| 50 | |
| 51 | ### Constructor vs BeginPlay |
| 52 | |
| 53 | **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. |
| 54 | |
| 55 | ```cpp |
| 56 | // CORRECT — constructor-time only |
| 57 | AMyActor::AMyActor() |
| 58 | { |
| 59 | MeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh")); |
| 60 | SetRootComponent(MeshComp); |
| 61 | PrimaryActorTick.bCanEverTick = true; |
| 62 | PrimaryActorTick.TickInterval = 0.1f; |
| 63 | } |
| 64 | |
| 65 | // CORRECT — world-dependent code belongs in BeginPlay |
| 66 | void AMyActor::BeginPlay() |
| 67 | { |
| 68 | Super::BeginPlay(); // Required — always call Super |
| 69 | GetWorld()->SpawnActor<AProjectile>(...); |
| 70 | } |
| 71 | ``` |
| 72 | |
| 73 | ### PostInitializeComponents |
| 74 | |
| 75 | Called before BeginPlay; components are initialized; world exists. Use it to bind delegates to own components. |
| 76 | |
| 77 | ```cpp |
| 78 | void AMyCharacter::PostInitializeComponents() |
| 79 | { |
| 80 | Super::PostInitializeComponents(); |
| 81 | HealthComponent->OnDeath.AddDynamic(this, &AMyCharacter::HandleDeath); |
| 82 | } |
| 83 | ``` |
| 84 | |
| 85 | ### EndPlay — reasons matter |
| 86 | |
| 87 | | Reason | When | |
| 88 | |---|---| |
| 89 | | `Destroyed` | `Actor->Destroy()` called explicitly | |
| 90 | | `LevelTransition` | Map change | |
| 91 | | `EndPlayInEditor` | PIE session ended | |
| 92 | | `RemovedFromWorld` | Level streaming unloaded the sublevel | |
| 93 | | `Quit` | Application shutdown | |
| 94 | |
| 95 | ```cpp |
| 96 | void AMyActor::EndPlay(const EEndPlayReason::Type EndPlayReason) |
| 97 | { |
| 98 | GetWorld()->GetTimerManager().ClearAllTimersForObject(this); |
| 99 | Super::EndPlay(EndPlayReason); |
| 100 | } |
| 101 | ``` |
| 102 | |
| 103 | ### Network lifecycle note |
| 104 | |
| 105 | **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. |
| 106 | |
| 107 | --- |
| 108 | |
| 109 | ## Component System |
| 110 | |
| 111 | ### The three layers |
| 112 | |
| 113 | | Class | Transform | Rendering/Collision | Use for | |
| 114 | |---|---|---|---| |
| 115 | | `UActorComponent` | No | No | Pure logic — health, inventory, AI data | |
| 116 | | `USceneComponent` | Yes | No | Transform anchors, grouping, pivot points | |
| 117 | | `UPrimitiveComponent` | Yes | Yes | Meshes, shapes, anything visible or collidable | |
| 118 | |
| 119 | **Notable subclasses**: `UStaticMeshComponent`, `USkeletalMeshComponent`, shape primitives (`UCapsuleComponent`, `UBoxComponent`, `USphereCompon |