Common gotchas
Use this page when something almost works but behaves differently in a real play session.
Tasks and Function Effect
Use numeric task-state checks
In Function Effect, use $TN{Task} values:
| State | Value |
|---|---|
| Not Active | 0.0 |
| Active | 1.0 |
| Completed | 2.0 |
if($TN{FindKey} == 2.0, SetTask('OpenDoor', 'Active', 0.0), '')
When setting a task, use the state name Active, NotActive, or Completed. The final SetTask argument is a delay in seconds.
The third SetVariable argument is a delay
SetVariable('Coins', 10.0, 0.0)
Do not treat that final value as an update mode or persistence option.
Trigger On Tasks Change is optional
Enable it only when a Function Effect should re-run after a task or value it references changes. A normal Function Effect can instead run from its owning task or trigger.
Avoid legacy inline change subscriptions in new expressions. Use the setting plus clear $TN{...} and $N{...} references.
Avoid self-triggering score rules
A rule that watches Score and always writes Score can keep firing. Attach a local score increment to the player action that earns it. For a shared score, use:
UpdateMultiplayerNumericVariable('TeamScore', 1.0, 1.0, 0.0)
TeamScore must be configured as a multiplayer numeric variable. Operation 1.0 means Add.
Scope, names, and persistence
- Names are exact. Capitalization and spaces in a task or variable name matter.
- A task or variable’s setup controls whether it is single-player or multiplayer; a Function Effect or JavaScript Function does not change that scope.
- Keep local and shared data names distinct.
- Test a renamed task in a fresh play session before relying on existing progress.
JavaScript Function
Use numeric task state checks
if ($TN{FindKey} === 2) {
SetTask('OpenDoor', 'Active', 0);
}
Do not treat it as a browser tab
Do not depend on fetch(), window APIs, requestAnimationFrame(), browser audio APIs, or undocumented helpers. Do not store a key or secret in a JavaScript Function.
UseEffector needs an item ID
UseEffector(12345, 'ShowObject', '{}');
12345 must be the actual numeric ID of an item in the current space. Effect names are case-sensitive. Use only the supported, documented no-code effect types; JavaScript Function is not one of them.
displayHtml
displayHtml() is a static HTML/CSS overlay, not an interactive web app:
displayHtml('<div style="color:white">New objective</div>');
displayHtml('');
Calling it again replaces the overlay. An empty string clears it.
Do not rely on script tags or inline handlers in the HTML to run. For interactive UI, use an iframe instead.
Iframes
Send JSON text, not a JavaScript object
PortalsSdk.sendMessageToUnity(JSON.stringify({
TaskName: 'OpenGallery',
TaskTargetState: 'SetActiveToCompleted',
Delay: 0
}));
TaskName is exact and TaskTargetState must be a supported transition string. Test the iframe inside a real Portals space, not only in a normal browser.
Keep the message contract small
Use one agreed JSON object for iframe-to-Portals actions. Do not put secrets in the iframe URL or message payload. See Iframe Debugging for the supported fields and transition names.
A fast debug loop
- Reproduce from a fresh play session.
- Verify the task or trigger fires.
- Check exact names and scope.
- Test the smallest possible change.
- Test with two players when data is shared.
- Add complexity only after the smaller rule works.
