Reference Scene Graph

component: Reusable Building Blocks for Scene Graph Entities

A `component` is the fundamental unit of logic and data in UEFN's Scene Graph system. Instead of cramming all behaviour into one monolithic device, you snap focused, reusable components onto entities — a buoy that bobs, a lantern that swings, a dock plank that creaks — and compose rich island moments from small, testable pieces. This article shows you exactly how to define a custom component, attach it to an entity at runtime with `AddComponents`, and drive it through the component lifetime (`On

Updated

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_component gives an entity a visible shape.
  • A sound_component gives 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 OnEndSimulationOnRemovingFromScene 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.

Guides & scripts that use component

Step-by-step tutorials that put this object to work.

Build your own lesson with component

Generate a personalized, step-by-step lesson plan built around this object — grounded in this exact reference and our compile-verified knowledge base.

Build a lesson →