OpalScript language
OpalScript is a small typed language built for Opal components. It uses braces, semicolons, typed fields, and methods. It does not run C# or .NET. Each source file declares one class derived from Component. Existing opal.component, Flow node, graph template, and scene behavior sources remain supported.
Core types
| Type | Meaning |
|---|---|
int | Safe integers |
float | Finite numbers |
bool | Boolean |
string | Text |
Vector2 | X / Y, copy by value |
Records, enums, options, tagged unions, maps, typed assets, and lists add structured data — see Data.
Use typed locals or inferred var locals. var is local inference only, never a persisted field declaration.
Operators and control flow
- Arithmetic and comparison;
&&/||;cond ? a : b if/else,return, private helper methodsint / inttruncates toward zero; a float operand produces float division+with a string on either side concatenates ("Wave " + wave)- Numeric literals may use an
fsuffix - Field defaults can be literals, vectors, enum members, and constant record/option constructors
Math offers Min, Max, Clamp, Abs, Sin, Cos, Sqrt, Pow, Atan2, Lerp, Random(min, max), PI, and int-returning Floor, Ceil, Round, and Sign. Signatures live on Math.
Loops
void Start() {
for (int step = 0; step < 4; step++) {
if (step == 1) continue;
Transform.Move(10, 0);
}
}A for header accepts one initializer and one step; any clause may be omitted. Supported steps: assignments, +=, -=, postfix ++/--, method calls.
float elapsed;
void Update(float dt) {
elapsed += dt;
while (elapsed >= 0.25) {
elapsed -= 0.25;
Transform.Rotate(45);
}
}break;leaves the nearest loop.continue;skips the rest of that iteration. In afor, the step still runs.returnleaves the entire method, even from nested loops.- Initializer variables are scoped to their loop. Body locals start fresh each iteration. Conditions must be
bool.
Loops run in the current callback. They do not wait for frames. Use Update(float dt) or Tasks for timing.
All loop work and nested component calls share a 10,000-operation limit per callback. An infinite loop stops with a source-located error — an operation limit, not an iteration count. Completed side effects are retained when a callback fails.
There is no do / while, no arrays, no nested mutable lists, and no class inheritance beyond Component.
Names that are reserved
Task, Time, and Math are reserved engine names. A class, field, or local named exactly Task must be renamed. Keep a saved field key with [Property("Task")] int Pending;. Keep a component id with [ComponentId("...")]. Lowercase task is an ordinary identifier.
Native type names such as Health and native component ids such as health are reserved. See lifecycle.
Execution budget
The host meters 250,000 world operations, 20,000 native work units, and 65,536 allocation work units per active frame, plus per-call and task bounds. A script cannot raise these limits. Shared exhaustion stops subsequent script work for the frame; cleanup keeps its allowance. Allocation units count tracked collections/data/query entries, not JavaScript heap bytes. Details: debugging.