Turning This Device into a Reusable Component
The main lesson turned BP_PressurePlate into one Verse device. It runs — but it does three jobs at once. Lessons 26 and 27 covered Scene Graph's composition model: an entity is a container, and capability comes from the components attached to it. This page takes the same logic apart along those lines — first seeing what the three jobs actually are, then what gets better and what gets more expensive once they are separated.
1. One Device, Three Jobs
Squint at those 43 lines from the main lesson and they are three unrelated things bolted together:
- Sensing — watching
Plateand knowing when someone triggers it. This part cares only about what happened, never about what should happen next. - Bookkeeping — that
var IsOpen:logic. It answers one question, "is the door open right now", for anyone who asks. - Actuating —
TargetDoor.Disable()/Enable(), plus theSleep(OpenDuration)timing. This part cares only about how to open or shut a door, never about who gave the order.
The three barely overlap, and yet they were crammed into one class. Why? Because under inheritance, a thing that can be placed in a level and receive Event BeginPlay is one indivisible lump — the same is true in Blueprint, where BP_PressurePlate is an Actor and its collision box, variables and Event Graph must live together. Want to reuse one part? The classic Blueprint answer is "make a new parent class and push the shared bits up", and the inheritance tree grows until nobody can say where BP_PressurePlate_Timed_Locked_V2 came from.
Composition flips the approach: an entity is an empty container and capability is attached. The same "timed door opening" capability can attach to a pressure plate, a lever, a button, or an empty entity driven purely by a timer — no inheritance relationship required. Scene Graph is that model, and it is built on Verse from the ground up: components are Verse classes, so there is no seam between "half in Blueprint, half in code".
2. The Split: One device → Three Responsibilities
Cut along those three jobs and the map looks like this:
| This part of the main lesson's device | Who owns it after the split | What the split buys you |
|---|---|---|
@editable Plate + Plate.InteractedWithEvent |
The sensor: does one thing — announce that it happened | Swapping the trigger source (button → lever → timer) swaps this one piece; nothing downstream changes |
var IsOpen:logic |
The state: a property of the door, queryable by anyone | State follows the door rather than the mechanism that triggered it — two mechanisms on one door no longer means two ledgers |
TargetDoor.Disable() / Enable() and the two helper functions |
The actuator: only ever "open this door / shut this door" | The polarity trap is sealed inside one piece forever; exactly one place in the project knows that Disable is what opens it |
The loop + Sleep(OpenDuration) choreography |
The rule: wires "who triggers" to "what is driven", and owns the timing | "Press once, open for 5 seconds" becomes a reusable thing in its own right — swap the door or the trigger without rewriting it |
@editable OpenDuration:float |
Travels with the rule | The delay belongs to the rule, not the door and not the plate — put it in the right place and duplicate Duration fields stop breeding in the panel |
Look closely at row two: IsOpen stops being "the plate's variable" and becomes "the door's property". That is not just a change of address — it puts a real design question on the table: if one door is controlled by two mechanisms, who owns the state? In the main lesson's version, two pressure plates each keep their own IsOpen and the ledgers drift apart sooner or later. After the split there is only one answer: the door keeps it. The biggest payoff of componentization is often not reuse but being forced to answer "who does this data actually belong to".
3. A Middle Form You Can Build Today
Scene Graph's component base class, lifecycle functions and exact syntax are still evolving — check the official docs for those. But the way you cut things up is practiceable today, with tools you already know: three Verse devices, each with one job, strung together by a custom event (Lesson 25 — Blueprint's Event Dispatcher).
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Concurrency }
# Answers one question: someone triggered it. What happens next is none of its business.
plate_sensor := class(creative_device):
@editable
Plate:button_device = button_device{}
# A custom event = Blueprint's Event Dispatcher (Lesson 25)
TriggeredEvent<public>:event(agent) = event(agent){}
OnBegin<override>()<suspends>:void =
Plate.InteractedWithEvent.Subscribe(OnPlateHit)
OnPlateHit(Agent:agent):void =
TriggeredEvent.Signal(Agent)
using { /Fortnite.com/Devices }
# Answers one question: how this door opens, how it shuts, what it is right now.
door_actuator := class(creative_device):
@editable
TargetDoor:barrier_device = barrier_device{}
# State follows the door, not the mechanism that triggers it
var IsOpen:logic = false
Open<public>():void =
set IsOpen = true
TargetDoor.Disable() # the polarity trap appears in exactly one place
Close<public>():void =
set IsOpen = false
TargetDoor.Enable()
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/Concurrency }
# Answers one question: after a trigger, how long before it shuts again.
# @editable can reference Verse devices you wrote yourself (Lesson 25)
timed_open_rule := class(creative_device):
@editable
Sensor:plate_sensor = plate_sensor{}
@editable
Actuator:door_actuator = door_actuator{}
@editable
OpenDuration:float = 5.0
OnBegin<override>()<suspends>:void =
loop:
Sensor.TriggeredEvent.Await()
Actuator.Open()
Sleep(OpenDuration)
Actuator.Close()
Graph translation: place all three devices in the level and point them at each other in the Details panel. plate_sensor listens to the button and fires its own Event Dispatcher; timed_open_rule waits for that signal in its own main loop, calls door_actuator's Open(), sleeps OpenDuration seconds, then calls Close(); door_actuator never learns that a pressure plate exists.
Try swapping the trigger: write a lever_sensor that exposes the same TriggeredEvent, and the rule needs not one line changed — just repoint it in the panel. That is the promise of composition, and you already have half of it with today's tools.
4. What Changes on entity + component
The middle form above differs from a real component model in two main ways:
First, ownership becomes real. Right now the three devices know each other through panel references, and there is no physical meaning to who is attached to whom. Under entity + component, the actuator and the state are attached to the door entity, and the sensor is attached to the plate entity. "Which thing does this component belong to" stops being a wire you can point anywhere and becomes the structure itself. Lesson 27 covers writing a component, and 26-x3 is a dedicated exercise in taking an Actor Blueprint apart into components.
Second, configuration gets finer grained. Today you place three devices and wire three references; under a component model, "a door that opens on a timer" can be packaged as a prefab that drags in once with all its components and defaults. This is part of why Epic is changing the skeleton: composition beats inheritance not because it is fashionable, but because past a certain depth nobody dares touch an inheritance tree.
On the timeline, the facts once more in the official phrasing: Scene Graph is UE6's new gameplay framework, built on Verse from the ground up; Actor and Blueprint are fully supported in UE6 Early Access (targeted for late 2027) and the early releases after it, with deprecation waiting on Scene Graph maturity and no date given. So this page is not urging you to refactor today — it is asking you, next time you write something new, to pause and ask: do these three jobs really have to live in one class?
5. Splitting Is Not Free
Having been honest about the benefits, here is the bill. Three pieces cost more than one class: three @editable references to wire in the panel, one event chain to track in your head, and two extra hops for a single trigger to complete its journey. For one door, that may not be worth it.
The test is plain: ask whether the three jobs will each get reused. One door and one mechanism in the whole map? The 43-line version from the main lesson is the correct answer, and splitting is self-inflicted work. Ten doors, five trigger types and three different open/close rules? Then every new combination costs the monolithic style one more class, while the component style costs one more wire. The benefit of composition grows with the number of combinations; the cost of splitting is fixed — where those two curves cross is something only your own project knows.
6. Quick Quiz
After the split, why does IsOpen move to the door side rather than staying with the pressure plate?