How it all works
Every game you build runs on our own little engine. You can program it two ways — by snapping together Blocks, or by writing code. They're the same thing: blocks turn into the exact code shown below, so you can start with blocks and peek under the hood whenever you like.
How a game runs
A game is made of one or more levels. Each level has objects (player, enemies, walls, collectibles), a goal, and a UI layer (your HUD, title screen and game-over screen).
Every level moves through three screens:
- Title — Shown first — your start screen. A Start button begins play.
- Playing — The game is live. Objects move, your blocks and scripts run every frame.
- Game over — Shown when you win or lose. A Restart button plays again.
You set how a level is won in the scene settings:
- collect — pick up every collectible.
- defeat — clear every enemy.
- survive — stay alive until the timer runs out.
Your blocks and scripts run once every framewhile the game is playing (about 60 times a second). That's why blocks like Chase or Move with keyskeep working — they're re-checked constantly.
Moving between levels (and ending the game)
A game can have several levels (add them from the Levels strip in Studio). To move the player on, you use the level commands — in Blocks or in code. Score and your variables carry across; health refills each level.
| Block | Code | What it does |
|---|---|---|
| Go to the next level → | level.next() | Move to the next level (shows YOU WIN on the last one). |
| Go to level [n] | level.go(2) | Jump straight to a level by its number (1 = the first). |
| Win the game 🏆 | level.win() | Finish this level as a win. |
| Game over ✗ | level.lose() | End the game right now — game over. |
There's one trick: these run every frame, so don't leave one on its own — it would skip the level instantly. Put it inside a conditionso it only fires when you want. For example, “when you've scored 100, go to the next level”:
if (vars.score >= 100) {
level.next();
}Health & damage
Any object can have health(hit points). Set it in the object's inspector. Enemies default to 1 (one hit kills them); give them more to make them tougher. Walls and pickups have no health until you give them some — then they become destructible.
Dealing damage
- Bullets carry a Damage amount — set it on the Shoot a bullet block. An enemy loses that much health per hit and dies at 0 (awarding score).
- The built-in shot reads a damage variable if you make one — so a power-up like “Change damage by 2” buffs every shot mid-game.
- Use Damage [tag] I'm touching by [n] for contact damage — spikes, melee, an enemy that hurts you on touch.
Reacting to your own health
Blocks (and code via self.data.hp) let any object respond to its own health:
if (self.data.hp <= 1) {
self.data.hp += 1; // heal
}Health bars
Tick Show health baron an object and a small bar floats above it, draining as it takes damage and vanishing when it dies. The player's health also drives the game-over screen and shows on the HUD bar (bind a HUD bar to health).
Multiplayer
Any game can be played together — no setup, no accounts. You don't build multiplayer; it's built in. There are no special blocks to learn.
In Studio, press 🔗 Host. You get a room and your game is sent to the room — you're the host, and you play too.
Copy the room link and send it to friends. They open it in any browser and land in the lobby — no install, no sign-up.
When everyone's in, the host presses Start and you're all in the same game. Friends can even join mid-game.
How it works
Everyone runs the same game in their own browser. Each player controls their own player object; their position is shared with the room many times a second, and you see everyone else as a moving avatar. The server is just a relay— it passes messages along, it doesn't run the game — so there's nothing to host yourself and it stays fast.
Because the in-game title screen is replaced by the lobby, your Title screen is skipped in multiplayer — the lobby is the pre-game.
Settings (👥 Multiplayer in Studio)
- Max players — the room caps at this many (1–16).
- Teams — leave empty for free-for-all, or add teams and joiners are auto-balanced across them. A player's team shows in the lobby.
These apply the moment you press Host. Published games can be hosted by anyone who opens them — same flow, straight from the library.
remote, so query("remote")gives you everyone else in the room — handy for tag, scoring, or “don't touch each other” rules.Block reference
Every block in Studio, and the exact code it turns into. Settings in [brackets] are the fields you fill in on the block.
Motion
— How an object moves around the world.self.vx = game.input.axisX() * 220;
self.vy = game.input.axisY() * 220;{ const t = find("player"); if (t) { const a = Math.atan2(t.y - self.y, t.x - self.x); self.vx = Math.cos(a) * 120; self.vy = Math.sin(a) * 120; } }self.rotation += 120 * Math.PI / 180 * dt;if (!self.vx && !self.vy) { const a = (self.x + self.y) % 6.283; self.vx = Math.cos(a) * 120; self.vy = Math.sin(a) * 120; }self.vx = game.input.axisX() * 220;
if (self.grounded && (game.input.justPressed("Space") || game.input.justPressed("ArrowUp") || game.input.justPressed("KeyW"))) self.vy = -620;Actions
— Things an object does — shoot, or remove itself.spawn({ x: self.x, y: self.y, render: { kind: "circle", r: 5, color: "#7c5cff" }, vx: Math.cos(self.rotation) * 400, vy: Math.sin(self.rotation) * 400, body: { gravity: false }, faceVelocity: true, data: { damage: 1 }, tags: ["bullet"], lifespan: 2 });self.destroy();self.data.sa = Number(self.data.sa) || 0;
if (game.time > self.data.sa) {
self.data.sa = game.time + 1.5;
const _p = find("player");
if (_p) { const a = Math.atan2(_p.y - self.y, _p.x - self.x); spawn({ x: self.x, y: self.y, render: { kind: "triangle", w: 16, h: 6, color: "#ff5d73" }, vx: Math.cos(a) * 300, vy: Math.sin(a) * 300, faceVelocity: true, body: { gravity: false }, tags: ["ebullet"], lifespan: 2.6 }); }
}{ const _t = query("player").find((o) => o !== self && Math.abs(o.x - self.x) < (o.w + self.w) / 2 && Math.abs(o.y - self.y) < (o.h + self.h) / 2); if (_t) _t.data.hp = (Number(_t.data.hp) || 0) - 1; }Control
— Decide when the blocks inside run — and move between levels or end the game.if ((Number(vars["health"]) || 0) <= 0) {
// the blocks you put inside run here
}{ const _t = query("enemy").find((o) => o !== self && Math.abs(o.x - self.x) < (o.w + self.w) / 2 && Math.abs(o.y - self.y) < (o.h + self.h) / 2); if (_t) {
// the blocks you put inside run here
} }if ((Number(self.data.hp) || 0) <= 0) {
// the blocks you put inside run here
}if (game.input.isDown("Space")) {
// the blocks you put inside run here
}if (game.input.justPressed("Space")) {
// the blocks you put inside run here
}self.data["t_1"] = (Number(self.data["t_1"]) || 0) + dt;
if (self.data["t_1"] >= 1) {
self.data["t_1"] = 0;
// the blocks you put inside run here
}if (Math.random() * 100 < 2) {
// the blocks you put inside run here
}level.next();level.go(2);level.win();level.lose();Variables
— Change the game's numbers — score, health, and your own variables.self.data.hp = (Number(self.data.hp) || 0) + -1;vars["score"] = (Number(vars["score"]) || 0) + 1;vars["score"] = 0;Writing code
When you outgrow blocks, switch to code. A script is plain JavaScript — the body of the object's update, run every frame. Blocks compile to this exact same language, so everything you learn with blocks carries straight over.
Here's everything you have to work with:
self.x self.yIts position.self.vx self.vyIts velocity — how fast it moves each second.self.rotationIts angle, in radians.self.alphaHow see-through it is, 0 to 1.self.scaleSize multiplier — 1 is normal, 2 is double.self.dataYour own scratch storage on this object.self.data.hpIts current health, if it has any (self.data.maxHp = the starting value).self.has(tag)True if this object has that tag.self.destroy()Remove this object from the game.game.input.axisX()−1, 0 or 1 from ← → / A D.game.input.axisY()−1, 0 or 1 from ↑ ↓ / W S.game.input.isDown("KeyA")True while a key is held down.game.input.justPressed("Space")True only on the frame a key goes down.game.input.pointerThe mouse — .x, .y and .down.game.input.pointerPressed()True only on the frame you click.game.timeSeconds since the game started.game.width game.heightThe size of the view.game.focusedTrue when the game has focus (is clicked).dtSeconds since the last frame — multiply movement by this.find("player")The first object with that tag, or null.query("enemy")Every object with that tag, as a list.spawn(cfg)Make a new object — bullets, pickups, anything.vars.score vars.health vars.maxHealthBuilt-in variables, always there.vars.anythingAny variable you create — read and write it by name.level.next()Go to the next level (YOU WIN on the last).level.go(2)Jump to a level by number (1 is the first).level.win()Finish this level as a win.level.lose()End the game now — game over.level.current level.totalThis level's number, and how many there are.A tiny example — an enemy that chases the player:
const p = find("player");
if (p) {
const a = Math.atan2(p.y - self.y, p.x - self.x);
self.vx = Math.cos(a) * 120;
self.vy = Math.sin(a) * 120;
}Errors in your script are caught per-object, so one mistake won't crash the whole game — check the browser console to see what went wrong.
Text tokens (for HUD & menus)
Any UI text can include tokensin curly braces — they're swapped for live values as the game runs. Use them in score readouts, timers and the game-over screen.
{score}The score variable.{health}The player's current health.{time}Time left (survive) or time elapsed.{enemies}How many enemies are still alive.{collected}Collectibles picked up, e.g. 3/8.{result}“YOU WIN” or “GAME OVER”.{yourVariable}Any variable you've made — by name.Now go build one
The fastest way to learn it is to open Studio and press Play.
Open Studio →