Overview
In UEFN's Scene Graph, every visible or interactive object is an entity. Entities are inert by default — they only do things when you attach components to them. A component is a class that derives from the built-in component base and carries a focused slice of logic or data:
- A
mesh_componentgives an entity a visible shape. - A
sound_componentgives it audio. - Your own custom Verse component can give it any gameplay behaviour you invent.
When should you reach for a component instead of a device?
| Situation | Reach for… |
|---|---|
| Logic that must live on a specific scene object | component |
| Logic that orchestrates many devices/objects | creative_device |
| Reusable behaviour you want to stamp on many entities | component |
| Trigger/granter/spawner wiring | creative_device |
Components shine on a sunny Fortnite lagoon: one buoy_bob_component definition, a dozen buoy entities, zero copy-pasted code.
Component Lifetime
Every component moves through a predictable sequence of lifecycle callbacks:
AddComponents() called
└─ OnAddedToScene() ← scene is ready; query other components here
└─ OnBeginSimulation() ← set up tick callbacks, subscribe to events
└─ OnSimulate()<suspends> ← async loop / long-running coroutine
OnEndSimulation() ← cancel ticks, clean up
OnRemovingFromScene() ← final teardown
You override whichever phases you need. The runtime calls them in order — you never call them yourself.
Key rules
- Your component class must specify
<final_super>so it can be added to entities:my_component := class<final_super>(component) { … }. - Only one instance of each
<final_super>subclass group may exist on a single entity at a time. - Components are added with
Entity.AddComponents([my_component{ Entity := SomeEntity }]). - Retrieve an existing component with
Entity.GetComponent[my_component]. - Remove a component with
MyComp.RemoveFromEntity().
API Reference
component
Base class for authoring logic and data in the SceneGraph. Using components you can author re-usable building blocks of logic and data which can then be added to entities in the scene. Components are a very low level building block which can be used in many ways. For example: * Exposing engine level concepts like mesh or sound * Adding gameplay capabilities like damage or interaction * Storing an
Full public surface, resolved verbatim from the live Epic digest (Verse.digest.verse).
component<native><public> := class<abstract><unique><castable><final_super_base>:
Walkthrough
Scenario: Bobbing Buoys in Cove Lagoon
Your 2D cel-shaded pirate cove has a string of buoys floating in the lagoon. Each buoy is a Scene Graph entity with a mesh. You want them to bob up and down on the water, each offset slightly so they don't all move in sync. You'll define a single buoy_bob_component, stamp it on every buoy entity, and let the component's OnSimulate loop handle the motion forever.
The creative_device below is placed on the island. In OnBegin it finds the buoy entities (exposed as @editable fields), creates a buoy_bob_component for each one, and calls AddComponents to attach them. From that point the component lifecycle takes over.
using { /Fortnite.com/Devices }
using { /Verse.org/SceneGraph }
using { /Verse.org/SpatialMath }
# ─────────────────────────────────────────────────────────────
# The component: one focused behaviour — vertical bobbing.
# <final_super> is REQUIRED so this class can be added to entities.
# ─────────────────────────────────────────────────────────────
buoy_bob_component := class<final_super>(component):
# How far (cm) the buoy travels up and down from its rest position.
BobAmplitude : float = 30.0
# How many full bobs per second.
BobFrequency : float = 0.5
# Phase offset (radians) so buoys don't all move in sync.
PhaseOffset : float = 0.0
# We cache the entity's rest translation once the scene is ready.
var RestTranslation : vector3 = vector3{Forward := 0.0, Left := 0.0, Up := 0.0}
# ── Lifecycle: scene is ready ─────────────────────────────
OnAddedToScene<override>() : void =
# Record the entity's starting world position so we bob relative to it.
# (In a real project you'd query a transform_component here.)
# We store a fixed rest position supplied by the spawning device.
RestTranslation = vector3{Forward := 0.0, Left := 0.0, Up := 0.0}
# ── Lifecycle: simulation begins ──────────────────────────
OnBeginSimulation<override>() : void =
# Nothing extra needed here for this simple component;
# the async loop lives in OnSimulate.
false
# ── Lifecycle: async simulation loop ──────────────────────
# OnSimulate runs as a coroutine and is cancelled automatically
# when the component is removed or the experience ends.
OnSimulate<override>()<suspends> : void =
var ElapsedTime : float = PhaseOffset / (BobFrequency * 6.2832)
loop:
# Sine wave: Up = Amplitude * sin(2π * Frequency * Time + Phase)
var SineValue : float = Sin(BobFrequency * 6.2832 * ElapsedTime + PhaseOffset)
var BobOffset : float = BobAmplitude * SineValue
# Build a new translation offset from rest.
# In a full project you'd call SetLocalTransform on a transform_component.
# Here we demonstrate the pattern with a local variable.
var _NewUp : float = RestTranslation.Up + BobOffset
# Advance time by a fixed step (≈ 60 Hz).
Sleep(0.016)
set ElapsedTime += 0.016
# ── Lifecycle: simulation ends ────────────────────────────
OnEndSimulation<override>() : void =
# Cancel any retained cancelables here (none in this example).
false
# ── Lifecycle: removing from scene ────────────────────────
OnRemovingFromScene<override>() : void =
false
# ─────────────────────────────────────────────────────────────
# The device: orchestrates which entities get the component.
# Place this device on your island; wire the buoy entities in
# the editor via the @editable fields.
# ─────────────────────────────────────────────────────────────
cove_buoy_manager_device := class(creative_device):
# Drag your buoy entities into these slots in the UEFN editor.
@editable BuoyEntity0 : entity = entity{}
@editable BuoyEntity1 : entity = entity{}
@editable BuoyEntity2 : entity = entity{}
OnBegin<override>()<suspends> : void =
# Build a list of (entity, phase) pairs so each buoy bobs differently.
StampBuoy(BuoyEntity0, 0.0)
StampBuoy(BuoyEntity1, 2.094) # 2π/3 ≈ 120°
StampBuoy(BuoyEntity2, 4.189) # 4π/3 ≈ 240°
# Creates a buoy_bob_component and attaches it to the given entity.
StampBuoy(Buoy : entity, Phase : float) : void =
var NewComp : buoy_bob_component = buoy_bob_component:
Entity := Buoy
BobAmplitude := 30.0
BobFrequency := 0.5
PhaseOffset := Phase
Buoy.AddComponents(array{NewComp})
Line-by-line explanation
| Lines | What's happening |
|---|---|
buoy_bob_component := class<final_super>(component) |
Declares a custom component. <final_super> is mandatory for any class you want to add to entities. |
Entity : entity |
Inherited from component — every component knows its parent entity. You supply it at construction time. |
OnAddedToScene<override>() |
Called once the entity is in the scene. Safe to query sibling components here. |
OnBeginSimulation<override>() |
Called just before OnSimulate. Use it for synchronous setup (subscribing to tick events, etc.). |
OnSimulate<override>()<suspends> |
Runs as a coroutine. The loop + Sleep(0.016) pattern gives you a per-frame update. The runtime cancels this automatically when the component is removed. |
OnEndSimulation<override>() |
Cleanup hook — cancel any cancelable handles you stored. |
OnRemovingFromScene<override>() |
Final teardown before the component is destroyed. |
Buoy.AddComponents(array{NewComp}) |
Attaches the component to the entity. This triggers the lifetime chain immediately. |
Common patterns
Pattern 1 — Querying an existing component with GetComponent
After AddComponents you can retrieve the component back from the entity using GetComponent. This is useful when one component needs to talk to another on the same entity — for example, a dock_light_component that reads a dock_state_component to decide whether to glow.
using { /Fortnite.com/Devices }
using { /Verse.org/SceneGraph }
using { /Verse.org/SpatialMath }
# A tiny data component that stores whether the dock is open.
dock_state_component := class<final_super>(component):
var IsOpen : logic = false
# A logic component that reads the state component.
dock_light_component := class<final_super>(component):
OnBeginSimulation<override>() : void =
# GetComponent[T] succeeds/fails — use 'if' to unwrap.
if (StateComp := Entity.GetComponent[dock_state_component]):
if (StateComp.IsOpen?):
# Dock is open — turn on the light (placeholder logic).
false
else:
# Dock is closed — dim the light.
false
# Device that stamps both components onto a dock entity.
dock_setup_device := class(creative_device):
@editable DockEntity : entity = entity{}
OnBegin<override>()<suspends> : void =
var StateComp : dock_state_component = dock_state_component:
Entity := DockEntity
var LightComp : dock_light_component = dock_light_component:
Entity := DockEntity
# Add both at once — order matters: state before light.
DockEntity.AddComponents(array{StateComp, LightComp})
Pattern 2 — Removing a component at runtime with RemoveFromEntity
A pirate ship's sail-sway component should stop when the ship docks. Remove the component programmatically and it flows through OnEndSimulation → OnRemovingFromScene cleanly.
using { /Fortnite.com/Devices }
using { /Verse.org/SceneGraph }
using { /Verse.org/SpatialMath }
sail_sway_component := class<final_super>(component):
OnSimulate<override>()<suspends> : void =
loop:
# ... sway logic ...
Sleep(0.033)
OnEndSimulation<override>() : void =
# Sway has stopped — reset sail to neutral position here.
false
pirate_ship_device := class(creative_device):
@editable ShipEntity : entity = entity{}
@editable DockTrigger : trigger_device = trigger_device{}
# We keep a reference so we can remove it later.
var MaybeSailComp : ?sail_sway_component = false
OnBegin<override>()<suspends> : void =
# Attach the sway component when the game starts.
var SwayComp : sail_sway_component = sail_sway_component:
Entity := ShipEntity
ShipEntity.AddComponents(array{SwayComp})
set MaybeSailComp = option{SwayComp}
# When the player triggers the dock plate, remove the sway.
DockTrigger.TriggeredEvent.Subscribe(OnShipDocked)
OnShipDocked(Agent : agent) : void =
if (Comp := MaybeSailComp?):
# Flows through OnEndSimulation -> OnRemovingFromScene.
Comp.RemoveFromEntity()
set MaybeSailComp = false
Pattern 3 — Walking all components on an entity with GetComponents
A debug device on a clifftop lighthouse iterates every component on an entity and logs its presence — useful during development to verify your composition is correct.
using { /Fortnite.com/Devices }
using { /Verse.org/SceneGraph }
using { /Verse.org/SpatialMath }
lighthouse_debug_device := class(creative_device):
@editable LighthouseEntity : entity = entity{}
OnBegin<override>()<suspends> : void =
# GetComponents returns all child components accessible from this context.
var AllComps : []component = LighthouseEntity.GetComponents()
# Iterate and act on each component.
for (Comp : AllComps):
# Cast to a known type to branch on specific components.
if (SailComp := sail_sway_component[Comp]):
# Found a sail sway component — could remove or reconfigure it.
SailComp.RemoveFromEntity()
# You can also walk child entities and their components.
var ChildEntities : []entity = LighthouseEntity.GetEntities()
for (Child : ChildEntities):
var ChildComps : []component = Child.GetComponents()
for (_Comp : ChildComps):
false # process each child component here
# Reuse the sail_sway_component definition from Pattern 2 above.
sail_sway_component := class<final_super>(component):
OnSimulate<override>()<suspends> : void =
loop:
Sleep(1.0)
OnEndSimulation<override>() : void = false
Gotchas
1. <final_super> is not optional
If you forget <final_super> on your component class, UEFN will refuse to let you add it to an entity via AddComponents. The compiler won't always give a clear error — it just silently skips the component. Always write:
my_component := class<final_super>(component): …
Further subclasses of my_component do not need <final_super> again.
2. Entity must be provided at construction time
The Entity field on component is not optional and has no default. You must supply it when you construct the component literal:
# CORRECT
var C : my_component = my_component{ Entity := SomeEntity }
# WRONG — will not compile
var C : my_component = my_component{}
3. Components cannot be moved between entities
Once a component is constructed with a given Entity, it is bound to that entity forever. RemoveFromEntity() detaches it from the scene, but you cannot re-parent it to a different entity. If you need the same behaviour on a new entity, construct a fresh component instance.
4. OnSimulate is cancelled, not awaited
When RemoveFromEntity() is called (or the experience resets), the runtime cancels the OnSimulate coroutine before calling OnEndSimulation. Any Sleep or Await inside OnSimulate will be interrupted. Put cleanup logic in OnEndSimulation, not at the end of OnSimulate.
5. GetComponent requires <decides> context
GetComponent[T] is a failure-context expression — it must appear inside an if or another <decides> expression:
# CORRECT
if (MyComp := Entity.GetComponent[my_component]):
MyComp.DoThing()
# WRONG — GetComponent outside a decides context won't compile
var C := Entity.GetComponent[my_component]
6. Component logic runs in edit mode too
Unlike creative_device.OnBegin, component lifetime methods (OnAddedToScene, OnBeginSimulation, OnSimulate) fire as soon as the entity enters the scene — including in the UEFN editor session. If you only want behaviour during gameplay, gate your logic inside a creative_device.OnBegin that spawns or stamps components at game-start.
7. One instance per subclass group per entity
You cannot add two buoy_bob_component instances to the same entity. If you need two independent bobs, use two child entities each with one component.