Actor Components as reusable gameplay¶
When several actors need the same health, inventory, interaction, team, or status logic, an Actor Component is often the missing middle between "copy the variables" and "make a giant parent class."
The one-minute version¶
- An Actor Component is a reusable object owned by an actor. It has variables, functions, events, dispatchers, and optional tick, but no transform.
- Use one when the behavior belongs on many actor types but is not the actor's whole identity: health, inventory, interactability, team, lock state, sensor memory, status effects.
- Do not use an Actor Component for something that needs a location, mesh, socket, collision shape, or camera. Use a Scene/Primitive Component or an Actor for that.
- The component talks to its owning actor with
Get Owner, interfaces, functions, and dispatchers. The owning actor should still decide actor-level actions such as destroying itself, playing body animations, or changing collision. - Widgets and other listeners usually bind to dispatchers on the component, then unbind or rebind when the owning actor changes.
- In multiplayer, a component does not magically solve authority. Server-owned gameplay still belongs on the server, and replicated component state needs a replicated owning actor and correct component replication setup.
What an Actor Component is¶
An Actor Component is a UActorComponent instance that belongs to one actor.
It is not placed separately in the level. It does not have its own world
location. It lives and dies with its owning actor unless you create/destroy it
manually at runtime.
BP_Enemy
-> HealthComponent
- CurrentHealth
- MaxHealth
- ApplyDamage()
- OnHealthChanged dispatcher
- OnDeath dispatcher
The owning actor still exists as the visible game object. The component is the reusable health logic attached to that object.
That split is useful because health is not unique to one class. A player, enemy, destructible barrel, and boss weak point may all need "take damage, clamp health, broadcast changed, report dead" without sharing a parent class.
When a component beats copied variables¶
Copied variables are fine for one Blueprint. They become a maintenance problem when the same mini-system appears in many places.
Use an Actor Component when you notice this shape:
BP_Player has:
Health, MaxHealth, ApplyDamage, OnDeath
BP_Enemy has:
Health, MaxHealth, ApplyDamage, OnDeath
BP_Barrel has:
Health, MaxHealth, ApplyDamage, OnDeath
Better:
BP_Player -> HealthComponent
BP_Enemy -> HealthComponent
BP_Barrel -> HealthComponent
Now the damage/clamp/broadcast rules live in one component class. Each actor can still decide what death means:
- player: disable input, show death UI, ask GameMode to restart;
- enemy: stop AI, play montage, destroy after delay;
- barrel: spawn effects, damage nearby actors, destroy itself.
The component owns the shared state. The actor owns the actor-specific result.
What it can do without a transform¶
An Actor Component can:
- store values and object references;
- define Blueprint functions;
- receive BeginPlay and EndPlay;
- tick if you enable component ticking;
- bind to owner or world events;
- call
Get Owner; - call interfaces on the owner or other objects;
- broadcast event dispatchers;
- replicate properties when set up correctly on a replicated actor.
It cannot by itself:
- be placed at a socket;
- provide a collision volume;
- render a mesh;
- be the target of
Set Actor Location; - appear as a separate actor in the World Outliner;
- receive component overlap events unless a primitive component provides the collision and passes the event along.
If your "component" needs a visible position, start with a Scene Component or Primitive Component instead.
Owner communication¶
The component should not assume every owner is the same exact Blueprint class unless that is truly the contract.
Good communication options:
| Need | Pattern |
|---|---|
| Component needs basic actor data | Get Owner, then use actor-level functions or interfaces. |
| Owner needs to react to component state | Owner binds to component dispatchers or calls component functions directly. |
| Widget needs health updates | Widget receives the component reference and binds to OnHealthChanged. |
| Other actor needs generic behavior | Use a Blueprint Interface, such as BPI_Damageable or BPI_Interactable. |
| Component needs a specific owner class | Cast once on BeginPlay, store the typed owner, and fail loudly with a print/assertion while developing. |
For a health bar:
PlayerController creates WBP_HealthBar
-> gets current character
-> gets HealthComponent from character
-> widget binds to HealthComponent.OnHealthChanged
-> component broadcasts whenever health changes
When respawn replaces the pawn, the widget should unbind from the old component and bind to the new character's component. The component did its job; the player/UI ownership path still has to handle lifetime.
Component lifecycle¶
Designed components in the Components window are created with the actor. Runtime components can be added with Add Component by Class.
Important lifecycle points:
- Construction/defaults. Designed components exist on the class/instance setup path and can expose defaults in Details.
- BeginPlay. Component gameplay setup usually belongs here, after the owner is in the play world.
- Runtime add. A newly added component must be registered before it ticks, renders, or participates in the world. Blueprint's add-component path handles the normal finish/register step.
- EndPlay/destroy. Clear timers, unbind external listeners when needed, and broadcast shutdown only if your own design promises it.
Do not put heavy world searches in every component's Tick. If the component can react to overlap, damage, input, dispatcher, or owner events, make the event the entry point.
Failure modes¶
Actor Components fail mostly through bad assumptions:
| Failure | What it looks like | Fix |
|---|---|---|
| Owner is not the class you assumed | Cast failed, variables stay default, Accessed None later. | Use an interface or validate/cast once and handle failure. |
| Component reference was never assigned | Widget cannot bind; call target is None. |
Get the component from the current actor, pass it on creation, or make it Instance Editable for placed wiring. |
| Component has no transform | You cannot attach it to a socket or use it for collision. | Use Scene/Primitive Component or an Actor. |
| Runtime component not initialized for your pattern | It exists but never starts its setup. | Add through the normal node, call your own Initialize function, and avoid relying on editor-only setup. |
| Old actor was destroyed or respawned | UI/listeners still point at the old component. | Rebind when the current pawn/actor changes and guard with Is Valid. |
| Multiplayer authority is wrong | Client changes local component value but server/other clients disagree. | Make server authoritative; replicate the owning actor/component state intentionally. |
Failure is not a try/catch path. Your component can be valid while your design is wrong, so debug with prints, breakpoints, and explicit owner checks.
The pattern everyone actually uses¶
Health component:
HealthComponent variables:
MaxHealth
CurrentHealth
OnHealthChanged(Current, Max)
OnDeath()
Function ApplyHealthDelta(Delta):
Old = CurrentHealth
CurrentHealth = Clamp(CurrentHealth + Delta, 0, MaxHealth)
if CurrentHealth != Old:
Call OnHealthChanged
if Old > 0 and CurrentHealth == 0:
Call OnDeath
Owning actor:
BeginPlay
-> bind HealthComponent.OnDeath to HandleDeath
HandleDeath
-> disable collision/input/AI as appropriate
-> play effects
-> Destroy Actor or ask GameMode to respawn
Widget:
SetHealthSource(NewHealthComponent)
-> unbind old source if valid
-> bind new source OnHealthChanged
-> update once from current values
The component is reusable because the actor-specific death behavior is not hard-coded inside it.
Lookalikes - which one do I want?¶
| Tool | Use when | Watch out |
|---|---|---|
| Actor Component | Reusable behavior/data with no transform. | Needs explicit communication with owner/listeners. |
| Scene Component | Reusable part needs a transform or attachment point. | Does not render/collide by default. |
| Primitive Component | Reusable part needs mesh/collision/physics. | More expensive and visually/spatially specific. |
| Parent Blueprint class | Actors really are the same kind of thing and share identity. | Single inheritance gets awkward when unrelated actors need one feature. |
| Blueprint Interface | Many objects expose the same callable contract. | Interface stores no shared state by itself. |
| Event Dispatcher | Component broadcasts a state change to listeners. | Listeners must bind/unbind around lifetime. |
| Data Asset | Shared static tuning/configuration. | It is data, not per-actor runtime state. |
Rule of thumb: if you are about to paste the same variables and functions into three unrelated actors, try an Actor Component before making a bigger parent class.
Going deeper¶
- Actor, Scene, and Primitive Components
- the component family tree.
- HealthComponent and damage response pattern
- a concrete reusable health component split across damage intake, UI updates, replication, and owner-specific death behavior.
- Blueprint variables and references
- why component references must still be assigned.
- Event Dispatchers - the health-bar binding pattern.
- Cast, Interface, or Dispatcher? - choosing the communication shape.
- Destroy Actor / Set Life Span
- actor/component lifetime cleanup after death.
- Replication basics - server authority and replicated state.
- Official docs: Components, UActorComponent, Components Window, and Actor Owner and Owning Connection.
- Engine source (requires engine access - we do not reproduce it here):
UActorComponentlifecycle, ticking, activation, and replication helpers inEngine/Source/Runtime/Engine/Private/Components/ActorComponent.cpp; actor ownership of components inAActor::AddOwnedComponent,AActor::RemoveOwnedComponent, and related helpers inEngine/Source/Runtime/Engine/Private/Actor.cpp.