Skip to content

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 ​

TypeMeaning
intSafe integers
floatFinite numbers
boolBoolean
stringText
Vector2X / 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 methods
  • int / int truncates toward zero; a float operand produces float division
  • + with a string on either side concatenates ("Wave " + wave)
  • Numeric literals may use an f suffix
  • 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 ​

csharp
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.

csharp
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 a for, the step still runs.
  • return leaves 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.