Testing and debugging
Testing in Opal is fast because Play mode runs the real game inside the editor. Use short edit-test loops: change one thing, press Play, inspect what happened, then adjust. Every tool on this page is built for that loop.
Play modes
| Control | Use | When to reach for it |
|---|---|---|
| Play in viewport | Fast iteration while keeping editor panels nearby | Default — 90% of tests |
| Fullscreen play | Test framing, mobile focus, and player feel | Before publishing, or when layout looks off |
| Stop | Return to editing | After every play |
| Stats | FPS, draw calls, physics, runtime values | When it stutters or state looks wrong |
| Flow overlay | Watch graph nodes light up while playing | When logic doesn't fire |
Play mode runs input, physics, flow graphs, animation, components, audio, scripts, and scene changes — exactly as the published player will.
Starting Play flushes pending scene, script, and project edits. If the scene is already saved, Play uses it without creating another save checkpoint.
Start scene testing
By default, play starts from the project's start scene. This is correct for testing the complete game flow.
When debugging one scene:
- Temporarily set it as the start scene, or use scene-specific test controls where available.
- Keep initialization logic in Scene Flow so tests behave like the real game.
- Restore the real start scene before publishing.
Keep a Boot scene
For games with 3+ levels, make the start scene a tiny Boot that sets score = 0, lives = 3, then Go To Scene → Main Menu. That way every Play run resets cleanly and you never publish with a half-initialized session.
Flow overlay — see your logic run
The Flow overlay paints execution on top of the running game. Toggle Flow in the play overlay bar.
Use it when:
- A tap does nothing.
- A branch takes the wrong path.
- A timer fires too often or not at all.
- A custom event is emitted but no listener responds.
- A component action runs on the wrong object.
How to read it:
- A node that never lights up — the event or the previous white wire never reached it. Check the trigger, the wire, and whether Play is running.
- A node that lights up but the result is wrong — execution reached it, but the data is wrong. Inspect its fields,
=expressions, and target object. - A node that fires twice — two rules match the same event, or a timer repeats without a guard.
Symptom: coin never disappears, score never rises.
- Press Play in viewport, open Flow overlay, tap the coin.
- If
On Tapnever flashes: select the coin → Inspector → confirm InteractableReceives tapsis on and no largerInteractablewith Block taps behindoncovers it. Check Problems for missing asset / broken wire / invalid target. - If
On Tapflashes butAdd Counternever flashes: execution wire is severed between them — re-drag it. - If both flash but score stays
0: open Variables → confirmscoreis a Number GameState var (capitalization matters). CheckAdd Counter(catalog) /Add to variable(registry) targetsscore, notScore.
Fix: re-wire, enable Interactable, or re-add the variable. One-line symptom → one-line fix.
Symptom: If score ≥ 5 → Win always takes the false arm even when the label says Score 7.
- With Flow overlay on, watch the condition node — does the
truepin ever pulse? - Open the condition's inspector. Is it reading a leftover UI Expression (
self.score) where Flow expects Get Value / Variable at least, or=GameState.score? See Variables and GameState. - Add a temporary
Say → ="score is {score}", or bind a debug Text label with Data bindings → GameState → score, to print the live value while playing.
Fix: swap the bad expression for a Get Variable node. Remove the debug Say.
Symptom: player walks through a hazard, no damage.
- Flow overlay: does
On Collisionflash? If not, select both objects → Inspector → confirm Collider 2D exists on each, the hazard is not a sensor when you want a blocker (or is a sensor when you wantSensor overlap), and bounds actually overlap on the stage. - Moving object needs Rigid Body 2D (Dynamic or Kinematic). Static décor needs none.
- If it's a Projectile that never welds: enable Continuous Collision on the body, wait past Arm Delay, and check Ignore Owner isn't filtering the hit.
See Physics and collisions for the full tuning table.
Fixing script errors
Script Studio highlights the exact name that needs attention. For example, Debug.log("ready") highlights log and offers Change 'log' to 'Log'. Corrections appear beside the problem in Script Output and through Monaco's Quick Fix menu. Suggestions use existing names and must pass the compiler; ambiguous names or code with other errors may need a manual edit.
Click a problem to select its complete source range. Click the status-bar problem count, or use F8 / Shift+F8, to move through located problems. The bottom Problems panel also opens compiler errors at their original source ranges and includes conflicts between scripts, such as duplicate component IDs.
A correction edits your current draft. Undo reverses just that correction, and Redo restores it. Moving focus to the problem list or another Studio control does not save or activate unfinished code. Use Save & Apply when ready; switching scripts or leaving the workspace still saves the draft as before. If the script or its project bindings change while a fix is open, Opal checks again instead of applying an outdated suggestion. A failed build keeps the previous working revision active.
Console — messages from your scripts
Open Console in the bottom shelf, or press Alt+4. OpalScript exposes a Unity-style Debug API:
class PlayerDebug : Component {
void Start() {
Debug.Log("Player ready");
Debug.Log(Transform.Position);
Debug.LogWarning("Using a default spawn point");
Debug.Assert(GetComponent<Health>() != null, "Add Health to this player");
}
}Use Debug.LogError("message") for a failure. Error logs and failed assertions do not throw: code after the call still runs. Use an explicit if and return when you need to stop a method. A true assertion writes nothing. Both assertion arguments are evaluated normally.
You can log numbers, booleans, vectors, records, collections, and object or asset handles directly. Messages capture a detached snapshot when the call runs, so later edits do not change old output. Large/deep values are visibly abbreviated. Each script message records its object, script, method, scene, exact source location, and source revision. Logging also works in LocalInput and asynchronous callbacks, but is not allowed in pure/computed functions.
- Search matches message text and source/object details. Combine it with the severity and source filters to isolate a script or an error.
- Collapse groups identical messages from the same source location and object. Turn it off to inspect each retained occurrence in order.
- Select a row to read the full message. Open its source to go to Script Studio, or its object to inspect the authored counterpart when available. Logs keep their captured information after an object disappears.
- Follow keeps the newest messages in view. Scroll back to read older output without being pulled down by new messages; enable Follow to catch up.
- Copy copies the selected message with its context. Export visible logs downloads the filtered view as text for a bug report.
- Options → Clear on Play starts each run with a clean console. Turn it off to compare runs; Stop and scene transitions preserve output.
- Options → Pause on error shares the Script Studio debugger's setting. An error pauses Play at a safe execution boundary and opens its source in the debugger. It does not interrupt a method halfway through a mutation.
The Console retains the most recent 2,000 messages and shows how many older messages were discarded. This bounds editor memory without stopping script execution. Exported games write Debug messages to the browser's developer console, with source information, and continue running after error logs.
See the Debug API for each method's declaration and examples.
Problems panel
The Problems dock lists live validation warnings. Treat it as a pre-publish gate — a warning you ignore in the editor becomes a player-facing bug.
Common warnings:
- Missing asset reference
- Invalid graph connection / broken wire
- Broken prefab or component data
- Missing target scene (
Go To Scene: —) - Duplicate scene names (transition becomes ambiguous — rename one)
- Project scene records unreadable
- Missing required component (e.g. Health Bar without Health)
- A Flow node the runtime cannot honour: an action on Self in a scene graph whose event has none (On Scene Start, On Key Down), Target on an event that carries no target, an object id that is no longer in the scene, a component action on an object without that component, a Spawn Object with no prefab, an unknown action type
- During Play, the same misses as they happen ("Say: nothing to act on — On Scene Start has no Self here"), reported once per node; a shipped game prints them to the console
Click a row to jump to the offending object, graph, or field. Fix, then re-check — Problems re-validates as you type.
Graph Explorer — when the hierarchy isn't enough
Open the workbench Graph dock → Graph Explorer. It's a read-only map of relationships, not an editor. Use it when the hierarchy is correct but the connections are not.
Choose a mode with View and narrow it with search or Filters. Enter/Space selects a focused node; Ctrl/Cmd+Enter opens its source. The dock's maximize button gives the map more room without changing your saved layout.
| Question you have | Graph to open |
|---|---|
| How is this scene parented? | Scene hierarchy |
| What does this prefab contain? | Prefab anatomy |
| What events can fire, and who listens? | Event map |
| Which assets will break if I delete this image? | Asset dependencies |
| Which systems talk to this component? | Component map |
| What just ran, in order? | Runtime trace |
Walkthrough — trace a score bug:
- Open Runtime Trace after a Play run that felt wrong — it shows which Flow nodes and edges fired (counts, last value, last path). Use it to see whether your
Add Counter/On Tapnodes actually ran, not as a GameState write log. - Switch to Asset dependencies → select
coin.png→ see every scene and.ssbthat depends on it before you replace it. - Switch to Event map → search
coin_collected→ see the listeners across Scene Flow and Object Flow and why only one fired.
See Graph Explorer for every mode and filter.
Stats — performance + state at a glance
Toggle Stats in the play overlay.
- FPS / frame ms — under 16 ms is 60 fps. Spikes usually mean physics sync or too many dynamic bodies.
- Draw calls / texture swaps — health bars are
render2dso they don't split WebGL batches; sprite spam does. - Physics bodies / contacts — crater when you spawn 50 Dynamic boxes at once.
No tuning blindly — change one thing, measure, undo with Ctrl+Z.
Debugging checklist
When anything does not work, run this in order:
- Reproduce it in Play in viewport.
- Confirm the correct scene is running (start scene vs isolated test scene).
- Confirm the object name is right — Flow, UI bindings, and Graph Explorer all use names.
- Check the inspector for missing components or disabled interaction (
Interactable,enabled). - Use Flow overlay to see whether the event fired and which white wire it followed.
- Inspect variables / component fields / expressions for typos, wrong scope (
selfvsget("player")), or literal-vs-expression mode. - Check the Problems panel.
- Open Graph Explorer when the bug is cross-scene or cross-system.
- Export a .opal backup before a risky fix.
Scripts — tests, Tasks, Play review
OpalScript has its own Play-time tools. They sit next to Flow overlay, not instead of it.
[Test]methods in Script Studio —public boolfor a fast check,public async Task<bool>for a disposable gameplay fixture.- Tasks panel — waits, parent/task identity, cancellation reasons.
- Apply selected and Stop — copy reviewed
[Property]fields back as one undoable authoring command. Private fields and unselected Properties stay in the play session.
Frame budgets, packages, and replay limits: tests, packages, debugger.
Browser dev tools — for hard bugs
Opal is browser-native, so the browser helps too.
- Console — script errors, failed asset loads, network/storage messages, Flow warnings. Filter
opalto quiet noise. - Network — confirm images, sounds, and other asset URLs actually load.
Related docs
- Build your first game
- Variables and GameState
- Scripts
- Visual scripting
- Physics and collisions
- Sharing and publishing
- Graph Explorer
- Troubleshooting — every exact error string, searchable via
Ctrl+K
Search tip: press Ctrl+K (or Cmd+K on Mac) from any docs page and type the exact warning text —
blocksBehind,missing badge,Arm Delay,Continuous Collision— to jump straight to the fix.