Scripts
Most Opal games can be built with components and Flow. Use the Scripts workspace when visual graphs need a reusable bit of JavaScript or when a behavior is easier to express as code.
Scripts are project-level authoring tools. They are not required for ordinary objects, scenes, prefabs, animation, or UI.
When to use scripts
Use scripts for:
| Need | Example |
|---|---|
| Reusable calculation | Damage formula, score curve, distance check |
| Complex condition | Inventory + quest + timer gate |
| Custom action | Apply several component calls as one named operation |
| Prototype logic | Try behavior before turning it into a component |
| Utility function | Convert coordinates, clamp values, format text |
Prefer Flow when the behavior is simple and designer-readable. Prefer a component when state and behavior should live together on objects.
Scripts vs components vs Flow
| Tool | Best at |
|---|---|
| Flow | Event wiring, readable gameplay logic, object and scene behavior |
| Components | Reusable stateful capabilities attached to objects |
| Scripts | Reusable code helpers or advanced graph nodes |
A healthy project often uses all three: components hold behavior, Flow wires events, scripts handle the few calculations that are awkward as nodes.
Behavior scripts (write logic on an object, no graph)
If you would rather write behavior as code than wire nodes, give an opal.component lifecycle hooks. This is the text-first alternative to visual scripting: attach the component to any object from the inspector's Add Component menu and it runs — no events, no wires.
Click + Behavior in the Scripts workspace for a starter, or write one directly:
export default opal.component("Spinner", {
category: "Behaviors",
fields: {
speed: { type: "number", default: 90, label: "Speed (deg/sec)" },
},
onTick: { spin: "self.speed" },
});Declarative sugar (recommended for common motion):
| Sugar | Meaning |
|---|---|
onTick: { spin: "self.speed" } | Rotate at degrees/sec (Motion-safe) |
onTick: { drift: { x: "60", y: "0" } } | Move at pixels/sec on each axis |
Imperative helpers inside "onTick: \"...\"" strings:
| Call | Meaning |
|---|---|
helpers.spin(thing, self.speed, dt) | Spin at degrees/sec |
helpers.moveBy(thing, dx, dy) | Nudge position (syncs Motion targets) |
helpers.deg(90) | Degrees → radians |
| Hook | Runs | Use for |
|---|---|---|
onStart | Once when Play begins | Initialize state, seed values |
onTick | Every frame while playing | Movement, timers, per-frame logic |
onEnd | Once when Play stops | Cleanup, final bookkeeping |
Inside a hook body you have:
| Name | What it is |
|---|---|
self | This component's fields — editable in the inspector, saved per object |
state | Per-instance scratch — not saved; reset each Play |
thing | The object the component is on (thing.x, thing.rotation, …) |
dt | Seconds since the last frame (onTick only) |
get / run / check | Read, call, and query sibling components |
emit | Fire one of this component's declared events |
helpers | spin, moveBy, rotateBy, deg, rad — Motion-safe motion helpers |
thing.rotation is in radians. Prefer onTick: { spin: "self.speed" } or helpers.spin(thing, self.speed, dt) instead of hand-rolling targetRotation sync.
The bodies run in the shipped game exactly as they do in the editor, and onTick never runs while you are editing. Keep them short: for/while loops and arrow functions are intentionally disallowed — for iteration, reach for a component action or Flow.
Open the Scripts workspace
Click Scripts in the workspace tabs.
The workspace includes:
| Area | Purpose |
|---|---|
| Script list | Project scripts and templates |
| Editor | JavaScript source |
| Docs / tutorials drawer | API reference and starter examples |
| Validation feedback | Compile or syntax messages where available |
Start with a small script. If it grows large or stores object state, consider turning it into a component.
Use scripts from Flow
Flow includes a Script node. The script node can access runtime context such as the current object, event params, and components.
Typical use:
On Tap
-> Script
-> Play AudioKeep Script nodes named and documented. A graph full of anonymous code blocks is harder to maintain than either pure Flow or reusable project scripts.
Script safety habits
- Keep scripts short.
- Name inputs and outputs clearly.
- Avoid hidden global state.
- Prefer component actions for gameplay state changes.
- Use Scene Flow for scene transitions and global rules.
- Test scripts in Play mode after every change.
Good script candidates
| Script | Why |
|---|---|
formatScore | Turns a number into display text |
damageRoll | Combines attack, defense, and randomness |
isBelowWorld | Reusable out-of-bounds check |
pickWeightedReward | Selects a random item from weighted options |
When not to use scripts
Avoid scripts for:
- Simple On Tap behavior
- Plain variable changes
- Basic scene transitions
- Standard Health, Inventory, Physics, or Motion behavior
- Anything a built-in component already does
The manual teaches Flow first because it remains inspectable by non-programmers.