High-level scripting
This track teaches OpalScript by attaching small behaviors and pressing Play. If you opened scripting to learn scripting, you are in the right place — start with the spinner.
OpalScript looks like C#. It is not C# or .NET, and it cannot reach the browser or JavaScript.
Five minutes
- Open Scripts → New Behavior.
- Replace the starter class with the spinner.
- Save & Apply (
Cmd/Ctrl+S). - In Arrange, select a visible object → Add Component → Spinner.
- Press Play. It turns.
class Spinner : Component {
void Update(float dt) {
Transform.Rotate(90 * dt);
}
}Update runs every gameplay frame. dt is how many seconds passed, so 90 * dt is ninety degrees per second. Transform.Rotate uses degrees. Stage +Y is down.
The same loop — write, apply, attach, Play — is the whole high-level workflow.
Tiny recipes
Each recipe is a full class. New Behavior, paste, Save & Apply, attach, Play.
Inspector speed
[Property] fields show in the Inspector and save with the object. [Range] only limits the slider.
class Spinner : Component {
[Property] [Range(0, 360)] float Speed = 90;
void Update(float dt) {
Transform.Rotate(Speed * dt);
}
}Attach it to two objects and give them different Speed values.
Drift to the right
class Drift : Component {
[Property] float Speed = 80;
void Update(float dt) {
Transform.Move(Speed * dt, 0);
}
}Transform.Move is pixels this frame. Positive X is right — leave Play running and it will drift off the stage. That's the point.
Hop on a button
In Input, click Jump · Space. Add Rigid Body 2D (Dynamic, Gravity Scale 1) and Collider 2D to the object, then attach Hopper.
class Hopper : Component {
[Property] float JumpSpeed = 420;
void Update(float dt) {
if (!Input.Pressed("Jump")) return;
RigidBody2D body = GetComponent<RigidBody2D>();
if (body != null) body.SetVelocity(new Vector2(body.Velocity.X, -JumpSpeed));
}
}Input.Pressed is true once per press. This sets upward velocity in pixels per second; gravity brings the body back down. This short example allows another hop in midair. For jumping only after landing, including soft bodies, follow Jump with Space.
Collect a coin
Declare a Number GameState variable named score in Variables. Attach this to a coin. Press Play and tap the coin.
class Coin : Component {
void OnTap() {
Game.SetNumber("score", Game.GetNumber("score") + 1);
Scene.Destroy(Self);
}
}OnTap is an engine hook — tap this object during Play and this method runs. Self is this coin.
Want a score on screen? Open UI, add a Text element, and in Data bindings set Source to GameState, Value to score, Format to Integer (rounded), and Template to Score {value}. Tap, watch it count. Walkthrough: UI and HUD.
Wait, then turn
class WaitAndTurn : Component {
async void Start() {
await Time.Wait(1);
Transform.Rotate(90);
}
}Time.Wait uses game time, so Pause holds the wait. Start runs once when Play begins — a nice place for a delayed kickoff.
Move with a stick
In the Input panel, click Move 2D · WASD. For a gamepad, add Left stick on that action.
class Mover : Component {
[Property] [Range(0, 1000)] float Speed = 240;
void Update(float dt) {
Vector2 movement = Input.Vector("Move");
Transform.Position += movement * Speed * dt;
}
}Input.Vector is a 2D stick for that action this frame. Vector2.Zero means nothing pressed. Hold a direction and it slides.
Pop on contact
Both objects need Collider 2D. At least one needs a moving Rigid Body 2D. Resting contact does not repeat OnCollisionStart.
class Pop : Component {
void OnCollisionStart(Entity other) {
if (other == null) return;
Scene.Destroy(Self);
}
}A coin, a bubble, a crate that breaks — attach Pop, bump it.
Chase a named object
Name the player Player in the Inspector. Attach this to something that should follow.
class Chase : Component {
[Property] float Speed = 120;
[Property] string TargetName = "Player";
void Update(float dt) {
Entity target = Scene.FindByName(TargetName);
if (target == null) return;
Transform.Position = Vector2.MoveTowards(Transform.Position, target.Position, Speed * dt);
}
}Missing names return null. Check before using the reference — a typo in TargetName just means it stands still.
What you are writing
| Piece | Meaning |
|---|---|
class Name : Component | One behavior per file, attached like a built-in |
void Start() | Once at Play, after physics is ready |
void Update(float dt) | Every gameplay frame |
void OnTap() | This object was tapped during Play |
[Property] | Inspector field, saved on the object |
public | Visible to other scripts |
A field without [Property] is runtime memory for this Play session — not in the Inspector, not saved.
New Behavior starts an OpalScript class. That is this track.
Built-in Health, Motion, and RigidBody2D are there to call, not to rebuild. Use GetComponent<Health>() when an object already has Health.
This track
- Script Studio — the Scripts workspace, Save & Apply, the docs drawer.
- Your first component — the spinner again, slower.
- Flap — a complete gravity-and-gap flyer (advanced lesson on this track).
Low-level (types, tasks, maps, native contracts): Low-level scripting. User Manual chapter: Scripts.