Components and properties
Components are reusable behavior blocks attached to objects — the Unity idea, inside Opal. An object can carry many components, each has typed fields, and those fields can drive runtime behavior and Flow graph nodes.
Manual vs Components guide: this page is using components in the editor (attach, configure, wire to Flow). For the full catalog and for authoring new ones, see the Components guide, Built-in reference, and Behavior Components.
When to use a component vs a graph
| Your object needs… | Reach for… | Example |
|---|---|---|
| Durable state or a rule every instance shares | Component | Health HP, Inventory, Team |
| Physics body or shape | Component | Rigid Body 2D + Collider 2D |
| Input handling (tap/drag/drop) | Component | Interactable / Draggable / Drop Zone |
| Cooldown, timer, spawn wave | Component | Timer, Spawner, Hazard |
| A one-off reaction to an event | Object Flow | On Tap → Play Sound |
| Global win/lose or scene transition | Scene Flow | On Custom Event win → Go To Scene |
Use components for capabilities you attach; use Flow for wiring those capabilities together.
Adding a component
- Select an object in Arrange.
- Open the Components card in the inspector (right panel).
- Click Add Component.
- Pick a built-in or a script-authored Behavior Component (they appear together once the script compiles).
- Configure its fields.
Some components pull dependencies automatically — adding Health Bar can also attach Health, because the bar needs a value to display. Problems will flag a missing dependency if you remove it.
Field types — what you actually edit
| Type | Looks like in inspector | Example fields |
|---|---|---|
| Number | Stepper + expression = toggle | Max HP 100, Speed 320, Cooldown 1.5 |
| Bool | Toggle | Enabled, Starts Active, Is Sensor |
| Text | Input + datalist autocomplete | Label, Tag, Event name coin_collected |
| Choice | Dropdown | Body Type Dynamic/Fixed/Kinematic, Shape Box/Circle/Capsule |
| Object / Asset ref | Picker + search | Target player, Prefab Slime, Sound pickup.wav, Clip Idle |
Fields can be read by Flow graphs (get("health").hp, Get Variable nodes) and change at runtime through component actions.
Literal vs expression. A number field shows 100 by default (literal). Click = to enter =self.maxHp * 0.5 (expression). Flow expressions still read GameState.score. HUD labels use the UI Inspector Data bindings card (Source → GameState), not an = field. Use Get/Set variable nodes to change declared project state.
Components and Object Flow — the real payoff
When you attach a component, the object's Flow catalog grows automatically:
| Component feature | New node | Example |
|---|---|---|
| Action | Does work | health.damage with amount |
| Condition | Branches white wire | health.isAlive → true/false |
| Event | Starts a rule | On Health Changed, On Pickup |
| Field | Data you can read | self.hp, get("inventory").count |
Mini example — damage on tap:
On Tap → health.damage { amount: 10 } → health.isAlive?
├─ true → Play Audio "hit.wav"
└─ false → Spawn VFX "poof" → DestroyPrefer component actions over manual variable math. If Health already models damage, use health.damage instead of score = score - 10.
Built-ins at a glance
| Component | One-line use | Pair with |
|---|---|---|
| Interactable | Tap / double-tap / long-press | Any sprite that receives pointer input |
| Draggable / Drop Zone | Pick up and drop | Inventory puzzles, card games |
| Motion / Sprite Renderer / Sprite Animator | Move, draw, animate frames | Pixel Studio / Art Canvas .ssb + Motion clips |
| Particle Emitter | Looping FX on an object | Particle Canvas .pfx or a built-in name; Spawn VFX for one-shots |
| Health / Stats / Team / Health Bar | HP, damage, factions, bars | Combat, bosses, squad |
| Inventory / Pickup | Hold and collect items | Shop, keys, coins |
| Platformer / Top-Down / Side-Scroller Controller | Player movement | Input and controls — set Controlled By |
| Spawner / Timer / Hazard / Projectile | Waves, clocks, damage zones, sticky arrows | Physics — enable CCD on Projectile |
| Collider 2D / Rigid Body 2D / Joint 2D / Ragdoll | Physics shapes, bodies, constraints | Physics and collisions |
| Scene Camera | Play viewport, follow, bounds | One per scene, usually on a marker object |
Full catalog: Built-in reference. Authoring: Behavior Components + Object Flow integration.
Behavior Components and OpalScript
For logic that appears on several objects but should be maintained once, open Scripts.
New Behavior starts a typed OpalScript class — your first component. Public methods become Flow actions and conditions.
New → Behavior Component starts the JavaScript-shaped opal.component(...) form when you want Inspector fields without types:
export default opal.component("Spinner", {
label: "Spinner",
category: "Behaviors",
fields: {
speed: { type: "number", default: 90, label: "Speed (deg/sec)" },
clockwise: { type: "boolean", default: true, label: "Clockwise" },
},
onTick: { spin: "self.speed" },
});Save & Apply. Spinner (or your OpalScript class) now appears in Add Component alongside built-ins. Attach it, set fields per instance, press Play.
A Behavior Component can define fields, events, actions, conditions, and lifecycle hooks (onStart, onTick, onEnd). The short form onTick: { spin: "self.speed" } or onTick: { drift: { x: "60", y: "0" } } covers common motion; for custom logic use a string body with self, thing, dt, and helpers. Configure per instance in the Inspector; click Edit Source on an attached instance to jump back to its definition.
Typed classes, tasks, and native APIs: high-level scripting. The opal.component API: Behavior Components.
Inspector properties vs component fields
| Inspector properties | Component fields |
|---|---|
| Describe the object itself | Describe one capability on that object |
| Transform, name, art, visibility, parent, prefab link | Health HP, Collider shape, Spawner prefab, Inventory capacity |
| One set per object | One set per attached component |
Keep data where it belongs: score → GameState; door's target scene → door object or its component; enemy HP → Health (not a loose number variable).
Troubleshooting components
| Symptom | Fix |
|---|---|
| Component not in Add menu | The script failed to compile — open Scripts, read diagnostics, Save & Apply. A bad edit does not replace last-known-good; OpalScript classes and Behavior Components only appear after a clean apply |
Health Bar shows — | Missing Health dependency — re-add Health or check Problems |
| Condition always false | Field is in literal mode, not = expression — or wrong scope (self vs get("player")) |
| On Health Changed never fires | Health never changed — verify a health.damage action actually ran (Flow overlay) |
Related docs
- Scripts — OpalScript, Behavior Components, and the high-level track
- Scripting API — native classes such as Health and Motion
- Your first component
- Components guide — first component, step by step
- Built-in reference — every field, action, condition, event
- Behavior Components — authoring API
- Object Flow integration — calling components from graphs
- Visual scripting
- Physics and collisions