Dynamics
Dynamics enables interactive movement, physics, and collision mechanics. Objects can respond to keyboard input, snap to grids, collide with each other, and interact through forces.
Quick Reference
| Want to... | Use | Section |
|---|---|---|
| Make object keyboard-controlled | set X movable + set X.input "keyboard" |
Input |
| Make object click-to-move | set X movable + set X.input "click" |
Input |
| Make object physics-only (no input) | set X movable |
Movable |
| Make object draggable by user | set X draggable |
Draggable |
| Enable jumping | set X jumpable |
Jumpable |
| Make camera follow object | set X.subject true |
Camera Following |
| Snap object to grid | set X.snapToGrid "Grid1" |
Grid Attachment |
| Move object to grid cell | set X.cellX 2 |
Grid Position |
| Block other objects | set X.blocking true |
Blocking |
| Make a one-way platform | set X.oneWay true |
One-Way Platforms |
| Detect overlaps without blocking | set X.sensor true |
Sensor |
| React to blocking collision | onCollide: handler |
Collision Events |
| Set surface friction | set X.friction 0.5 |
Friction |
| Set surface bounciness | set X.restitution 0.5 |
Bounce |
| Set movement animation | set X movable style slide |
Movement Style |
| Add gravity | set Cell.gravity 0.5 |
Gravity |
| Add wind | set Cell.wind 0.3 |
Wind |
| Set wind direction | set Cell.windAngle 90 |
Wind |
| Add air resistance | set Cell.airResistance 0.1 |
Air Resistance |
| Push objects with other objects | Both need movable + blocking |
Momentum Transfer |
| Check if cell is empty | isEmpty("Grid1", x, y) |
Grid Functions |
| Get all empty cells | emptyCells("Grid1") |
Grid Functions |
| Read movement state | if X.moving: |
Movement State |
| React to direction change | onMove "down": |
Movement Events |
| React to stopping | onStop: |
Movement Events |
| React to jumping | onJump: |
Movement Events |
| React to landing | onLanding: |
Movement Events |
| Get movement angle | X.moveAngle |
Movement State |
| Make object pullable | set X.pullable true |
Pullable |
| Wait for movement to finish | wait movement |
Wait Movement |
Input
By default, a movable object does not respond to user input. It only moves from physics forces (gravity, wind) or script commands. Input must be enabled explicitly.
Input Types
| Type | Description |
|---|---|
keyboard |
Responds to WASD / arrow keys |
click |
Click-to-move: clicks set destination, object moves toward it |
| (none) | Physics-only: gravity, wind, collisions, and script commands |
Enabling Keyboard Control
set Player movable # Enable physics movement
set Player.input "keyboard" # Enable keyboard control
Both are required. movable enables the physics engine to process the object. input "keyboard" tells it to read key presses.
Click-to-Move
Objects with input: "click" move toward clicked points:
set Player movable # Enable physics movement
set Player.input "click" # Enable click-to-move
When you click empty space in play mode:
- All objects with
input: "click"start moving toward that point - Movement uses the object's speed, acceleration, and deceleration settings
- Full physics applies (gravity, wind, collisions)
- Object stops when it arrives at the target (or hits a blocker)
- Clicking again sets a new target (interrupts current movement)
Click-to-move works with grid snapping — the object moves cell-by-cell toward the clicked cell.
Player Bindings
Player 1 (default): WASD + Arrow keys. Objects without explicit input binding use Player 1 keys when input is "keyboard".
Draggable
Makes an object directly draggable by the user via mouse/touch.
Basic Usage
set Piece draggable # Enable drag
set Piece draggable false # Disable drag
Drag Events
Objects with draggable fire three events during interaction:
| Event | When |
|---|---|
onDragStart |
User starts dragging (after threshold) |
onDrag |
Each frame during drag |
onDragEnd |
User releases |
onDragStart:
set self.opacity 0.7
set self.state "DRAGGING"
onDrag:
# Called continuously while dragging
log "dragging at" self.x self.y
onDragEnd:
set self.opacity 1
set self.state "IDLE"
Click vs Drag
An object can be both clickable and draggable. The system distinguishes:
- Click: Pointer down + up without significant movement
- Drag: Pointer down + movement beyond threshold
Both onClick and drag events work on the same object:
onClick:
self.toggle # Tap to toggle
onDragEnd:
log "dropped at" self.x self.y # Drag to move
Configuration Options
set Piece draggable once # Can only drag one time
set Piece draggable collision # Respect blockers while dragging
set Piece draggable discrete # Grid objects drag cell-by-cell
| Option | Behavior |
|---|---|
once |
Dragging disabled after first drag ends |
collision |
Object collides with blockers during drag |
discrete |
Grid-attached objects move cell-by-cell |
Camera Behavior
When dragging objects, the camera pauses following subjects to prevent erratic movement. Camera resumes following when the drag ends.
Movable
Makes an object participate in the physics engine. The object can be affected by gravity, wind, collisions, and momentum transfer.
Basic Usage
set Player movable # Enable with defaults
set Player movable speed 0.5 # Custom speed
set Player movable speed 0.5 acceleration 0.2 deceleration 0.1
Configuration
| Property | Default | Description |
|---|---|---|
speed |
0.3 | Max velocity (cells/sec for grid, units/sec for free) |
acceleration |
0.1 | How quickly reaches max speed |
deceleration |
0.1 | Velocity decay when no input (acts as drag) |
style |
teleport | Movement animation: teleport, slide, jump, fade |
Deceleration as Drag
The deceleration property controls how quickly an object slows down when there is no input driving it. This applies to all velocity sources — keyboard input, collision impulses, and momentum transfer.
In side-view (with gravity), deceleration mainly affects horizontal slowdown since gravity dominates the vertical axis. In top-down (no gravity), deceleration acts as floor drag — a pushed object slides and gradually stops.
| Deceleration | Behavior |
|---|---|
| 0.02 - 0.05 | Ice / slippery floor — objects slide a long way |
| 0.1 - 0.15 | Normal — moderate slide after being pushed |
| 0.3+ | Sticky / rough floor — objects stop quickly |
Movement Style
Control how objects animate between grid cells:
set Player movable style teleport # Instant snap (default)
set Player movable style slide # Smooth interpolation
set Player movable style jump # Arc motion (hop style)
set Player movable style fade # Fade out/in transition
Disabling Movement
set Player.movable false # Disable movement
Mass
Objects have a mass property that affects forces and momentum. Default mass is based on the object's area (width × height), set at creation.
set Player.mass 0.5 # Light (blown by wind, easy to push)
set Boulder.mass 5 # Heavy (resists wind, hard to push)
| Force | Mass Effect |
|---|---|
| Gravity | No effect — all objects fall equally |
| Wind | Heavier objects resist more |
| Input | Heavier objects accelerate more slowly |
| Momentum | Heavier objects push lighter ones farther |
Components: mass is the sum of children (not directly editable).
Jumpable
Gives an object the ability to jump. Separate from movable — an object needs both to move and jump.
Basic Usage
set Player movable # Enable movement
set Player.input "keyboard" # Enable keyboard control
set Player jumpable # Enable jump (Space key by default)
Configuration
set Player jumpable height 0.8 # Stronger jump
set Player jumpable multijump 2 # Double jump
set Player jumpable key "w" # Custom jump key
| Property | Default | Description |
|---|---|---|
height |
0.5 | Upward impulse velocity |
key |
Space | Key that triggers jump |
multijump |
1 | Max jumps before landing (2 = double jump) |
Behavior
- Up keys also trigger jump — Arrow Up / W trigger jump in addition to the configured key
- Upward movement suppressed — when jumpable, up input triggers jump instead of moving up
- Ground detection — jumps reset when the object lands on a blocking surface
- Mass-independent — jump impulse is the same regardless of object mass
- Works without gravity — the object will fly upward and keep going (designer's choice)
Physics
Cell-level forces that affect all movable objects. Set in the Cell properties panel or via script.
Gravity
Constant downward acceleration:
set Cell.gravity 0.5 # Gentle gravity
set Cell.gravity 2 # Strong gravity
set Cell.gravity 0 # Disable
set Cell.gravity -1 # Upward (reverse gravity)
Gravity is mass-independent — all objects fall at the same rate. Range: -10 to 10.
Wind
Wind pushes objects in a configurable direction:
set Cell.wind 0.3 # Wind magnitude
set Cell.windAngle 0 # Rightward (default)
set Cell.windAngle 90 # Downward
set Cell.windAngle 180 # Leftward
set Cell.windAngle 270 # Upward
set Cell.windAngle 45 # Diagonal (down-right)
Wind is mass and area dependent — large light objects are blown more, heavy objects resist.
Air Resistance
Air resistance creates drag that opposes velocity. Objects slow down as they move through the medium, creating natural terminal velocity.
set Cell.airResistance 0 # Vacuum (no drag)
set Cell.airResistance 0.1 # Air-like
set Cell.airResistance 0.5 # Thick air / light water
set Cell.airResistance 1 # Water-like (strong drag)
| Value | Behavior |
|---|---|
| 0 | No drag — objects accelerate indefinitely (hard cap applies) |
| 0.05 - 0.1 | Light drag — realistic air resistance |
| 0.2 - 0.5 | Heavy drag — objects slow quickly when forces stop |
| 1+ | Very thick medium — strong velocity damping |
Terminal velocity: With air resistance, objects naturally reach a terminal velocity where drag equals gravity. The relationship is approximately v_terminal ≈ gravity / airResistance. For example, with gravity 1 and airResistance 0.1, terminal velocity is about 10 units/sec.
Hard Velocity Cap
A hard cap of 50 units/sec exists as a safety net. With air resistance, this cap is rarely reached since drag naturally limits velocity.
Contact Blocking
When an object is resting on a surface (e.g., standing on ground with gravity), the surface blocks the force that would push it through. Gravity doesn't accumulate while grounded. Wind is similarly blocked when pressing against a wall.
Surface Physics
Per-object properties that control how surfaces interact during collisions. Set in the properties panel or via script.
Friction
Controls how much parallel velocity is dampened on contact. Think of it as surface roughness.
set Floor.friction 0.8 # Rough surface — objects slow quickly
set Ice.friction 0.05 # Slippery surface — objects slide
set Player.friction 0.3 # Player's own surface friction
| Value | Behavior |
|---|---|
| 0 | No friction — frictionless surface (ice) |
| 0.1 - 0.3 | Low friction — smooth sliding |
| 0.5 | Medium friction — moderate grip |
| 0.8 - 1.0 | High friction — objects stop quickly on contact |
Combination rule: when two objects collide, their friction values are averaged. A low-friction object on a high-friction surface gets moderate friction: (0.1 + 0.8) / 2 = 0.45.
Bounce (Restitution)
Controls how much velocity is reflected on collision. A bouncy ball hitting a wall bounces back.
set Ball.restitution 0.7 # Bouncy ball
set Wall.restitution 0 # Dead wall (no bounce)
set Trampoline.restitution 0.9 # Very bouncy surface
| Value | Behavior |
|---|---|
| 0 | Dead stop — no bounce at all |
| 0.3 | Low bounce — slight rebound |
| 0.5 | Medium bounce |
| 0.7 - 0.9 | High bounce — energetic rebound |
Maximum effective restitution is 0.95 to prevent infinite bouncing.
Combination rule: when two objects collide, the higher restitution wins. A bouncy ball (0.7) hitting a dead wall (0) still bounces at 0.7. Either surface being bouncy makes the collision bouncy.
Surface Property Summary
| Property | Range | Default | Combination | Set on |
|---|---|---|---|---|
friction |
0 - 1 | 0 | Average of both surfaces | Any blocking object |
restitution |
0 - 1 | 0 | Max of both surfaces | Any blocking object |
Both properties apply to the mover and the blocker. A bouncy player hitting a non-bouncy wall still bounces (player's restitution applies). A low-friction player on a high-friction floor gets moderate friction.
Blocking
The blocking property prevents other movable objects from passing through.
Basic Blocking
set Wall.blocking true # Simple blocker
set Player.blocking true # Player blocks others too
An object can be both movable and blocking — it moves but other objects can't pass through it.
Conditional Blocking
Use expressions for dynamic blocking rules:
# Block objects of different color (using tags)
set Piece.blocking "self.color != other.color"
# Block only larger objects
set Piece.blocking "other.width > self.width"
# Block if wrong size order (for stacking puzzles)
set Piece.blocking "self.size < other.size"
In blocking expressions:
selfrefers to the blocking objectotherrefers to the object trying to move in
One-Way Platforms
A one-way platform only blocks objects approaching from above. Objects can jump through from below and land on top.
set Platform.blocking true
set Platform.oneWay true
| Approach Direction | Blocked? |
|---|---|
| From above (landing) | Yes |
| From below (jumping up) | No — passes through |
| From the side | No — passes through |
One-way platforms are ideal for side-view platformer levels where the player jumps up through platforms.
Sensor
A sensor object detects overlaps without blocking movement. When sensor is true, the object fires onOverlap and onOverlapEnd events when it overlaps another object, but does not prevent passage.
set Coin.sensor true # Detect overlaps, don't block
set DangerZone.sensor true # Overlap detection only
At least one of the two overlapping objects must have sensor: true for overlap events to fire. Use the other keyword inside onOverlap/onOverlapEnd handlers to reference the other object.
Sensor vs Blocking:
| Property | Prevents passage | Fires overlap events |
|---|---|---|
blocking |
Yes | No |
sensor |
No | Yes |
Example: Collectible coins
onEnter:
foreach coin in #coin {
set coin.sensor true
}
# On each coin object:
onOverlap:
if other is #player:
hide self
set score score + 1
Example: Danger zone
# On the zone object:
onOverlap:
if other is #player:
set inZone true
shake other 300
onOverlapEnd:
if other is #player:
set inZone false
You can check by name (other.name == "Player") or by tag (other is #player).
The sensor toggle appears in the Properties Panel alongside One-Way in the dynamics toggles row.
Pullable
A pullable object can be pulled by a movable object when the player holds Shift while moving away from it.
set Crate.pullable true
set Crate.blocking true
How it works:
- Player stands adjacent to a pullable object
- Player holds Shift and moves away from the object
- The pullable object follows the player into their vacated cell
This enables Sokoban-style puzzles where you can both push and pull crates.
| Property | Description |
|---|---|
pullable |
When true, object can be pulled by Shift+move |
Note: If the pull fails (e.g., destination blocked), the player also cannot move (grip behavior).
Collision Events (onCollide)
When a movable object collides with a blocking object, the onCollide event fires on the mover. Use the other keyword to reference the blocker.
# On the player object:
onCollide:
if other.name == "Spike":
goto "GameOver"
if other.name == "Wall":
shake self 200
onCollide vs onOverlap:
| Event | When it fires | Movement |
|---|---|---|
onCollide |
Mover hits blocker | Mover is stopped |
onOverlap |
Objects overlap (sensor) | No blocking |
Use onCollide for:
- Damage from hazards
- Sound effects on wall hits
- Detecting when player reaches a goal
Momentum Transfer
When a movable object collides with another movable blocker, momentum is transferred based on their masses. The pushed object receives an impulse and starts moving.
Setup
Both objects need movable and blocking:
# Player (the pusher)
set Player movable speed 0.3
set Player.input "keyboard"
set Player.blocking true
set Player.mass 2
# Crate (pushable object)
set Crate movable speed 0.3
set Crate.blocking true
set Crate.mass 1
The pushed object does not need keyboard input — it moves purely from physics impulse.
Mass Effects
Momentum splits by mass ratio using conservation of momentum:
| Scenario | Result |
|---|---|
| Heavy pushes light | Light object flies far, heavy barely slows |
| Equal masses | Energy splits evenly |
| Light pushes heavy | Heavy barely moves, light bounces back |
Sliding After Push
After being pushed, the object slows down based on its deceleration value. In top-down mode (no gravity), this acts as floor drag. Lower deceleration = slides farther.
Surface Properties on Push
Friction and restitution affect the collision:
- High restitution on either object makes the push more "bouncy" — objects rebound apart
- Low restitution makes a "sticky" push — objects barely separate
- Friction dampens the tangent (sliding) component of the collision
Example: Slippery Push Puzzle
# Ice floor: objects slide a long way after being pushed
foreach crate in #crate {
set crate movable speed 0.5 deceleration 0.03
set crate.blocking true
set crate.friction 0.05
set crate.restitution 0.3
set crate.mass 1
}
# Player: heavy enough to push crates
set Player movable speed 0.4
set Player.input "keyboard"
set Player.blocking true
set Player.mass 3
set Player.friction 0.1
Camera Following
Make the camera follow a movable object:
set Player.subject true # Camera follows this object
When subject is true, the camera tracks the object in cells larger than the viewport. See Camera for dead zone settings and multi-cell scrolling behavior.
Grid Objects
A Grid is a special object type that defines a coordinate system. Objects can attach to grids for cell-based positioning and movement.
Creating a Grid
Add a Grid from the right-click menu → Add to DOM → Grid. Configure in the properties panel:
| Property | Description |
|---|---|
| Columns | Number of horizontal cells |
| Rows | Number of vertical cells |
| Show Lines | Display grid lines (build mode always shows) |
| Line Color | Grid line color |
Grid orientation
cellX = 0is the left column, increasing rightwardcellY = 0is the top row, increasing downward- For a 7x6 grid: columns 0-6 (left to right), rows 0-5 (top to bottom)
Two Grid Systems: Occupancy vs Cell Data
This is critical to understand. Grids have TWO completely separate systems for tracking state:
| System | What it tracks | How to read | How to write | Cleared when |
|---|---|---|---|---|
| Occupancy | Which objects are in which cells | Player.cellX, isEmpty(), emptyCells() |
Move objects with cellX/cellY |
Objects move/destroyed |
| Cell Data | Arbitrary data per cell | Grid.cell[x][y].property |
set Grid.cell[x][y].prop value |
clear Grid or reset("session") |
When to use which?
| Use Case | System | Why |
|---|---|---|
| Track player position | Occupancy | Player is a visual object that moves |
| Place game pieces | Occupancy | Pieces are objects you see and interact with |
| Mark cells as "owned" (Connect4) | Cell Data | Ownership is metadata, not a visible object |
| Store mine positions (Minesweeper) | Cell Data | Hidden data, not visible objects |
| Count objects in cells | Occupancy | You're counting real objects |
| Check if cell was "revealed" | Cell Data | State data, not an object |
| Track which cells are walkable | Either | Objects for visible walls, data for invisible rules |
Key differences
Occupancy:
- Based on actual objects with
snapToGrid - An object can only be in ONE cell at a time
- Reading
isEmpty()checks for objects - Objects are visible (unless hidden)
Cell Data:
- Pure data storage - no visual representation
- Each cell can have MULTIPLE properties (color, owner, revealed, etc.)
- Reading
Grid.cell[x][y].propreturns stored data - Data is invisible - you must create objects to show it
Common mistake
# WRONG: Trying to use cell data to track pieces
set Board.cell[3][4].hasPiece true # This just stores data!
# The piece object is NOT at (3,4) unless you moved it there
# CORRECT: Move the actual object
set Piece.snapToGrid "Board"
set Piece.cellX 3
set Piece.cellY 4
# Now isEmpty("Board", 3, 4) returns false
Working together
Often you'll use BOTH systems together:
# Connect Four: Pieces fall and stack (occupancy)
# But we also track who owns each cell (data)
onSpawn:
set self.snapToGrid "Board"
set self.cellX col
set self.cellY row
set self.blocking true
# Store ownership in cell data for win checking
set Board.cell[col][row].owner currentPlayer
Grid Occupancy (Object Positions)
Track where objects physically are on the grid.
Grid Attachment
Attach objects to a grid using snapToGrid:
set Player.snapToGrid "Grid1" # Attach to grid
set Bullet.snapToGrid "none" # Detach (free movement)
When attached, the object:
- Snaps to cell centers
- Exposes
cellXandcellYproperties - Respects grid bounds (can't move outside)
Reading Object Position
log "Player at" Player.cellX Player.cellY
if Player.cellX == 3 and Player.cellY == 5:
show Treasure
Setting Object Position
Move objects by setting their cell coordinates:
set Player.cellX 4
set Player.cellY 2
This teleports the object to that cell immediately.
Occupancy Functions
Query which cells have objects in them.
isEmpty(grid, x, y)
Returns true if no object occupies the cell at coordinates (x, y):
if isEmpty("Grid1", 2, 3):
set Piece.cellX 2
set Piece.cellY 3
Note: This checks for objects with snapToGrid set to this grid. It does NOT check cell data.
emptyCells(grid)
Returns an array of {x, y} objects for all cells with no objects:
set empty emptyCells("Grid1")
log "Empty cells:" length(empty)
# Pick a random empty cell
if length(empty) > 0:
set cell pick(empty)
set Piece.cellX cell.x
set Piece.cellY cell.y
Initializing Grid Objects
When using occupancy functions, objects must have their positions tracked first. Set snapToGrid and wait briefly for the dynamics system to calculate positions:
onEnter:
# Initialize blockers first
foreach blocker in #blocker {
set blocker.snapToGrid "Grid1"
set blocker.blocking true
}
# Wait for positions to be calculated
wait 100ms
# Now emptyCells excludes blocker positions
set empty emptyCells("Grid1")
Grid Cell Data (Metadata Storage)
Store arbitrary data per grid cell. This is completely separate from objects - cells can have data even with no object in them.
Writing Cell Data
set Board.cell[0][0].owner "RED" # String
set Board.cell[x][y].revealed true # Boolean
set Board.cell[col][row].mineCount 3 # Number
set Board.cell[i][j].state "FLAGGED" # Any value
Indices can be literals (0), variables (x), or expressions (col + 1).
Reading Cell Data
# In conditions
if Board.cell[0][0].owner == "RED":
log "Red owns top-left"
# As expression
set cellOwner Board.cell[x][y].owner
# Get entire cell's data object
set cellData Board.cell[0][0]
log cellData.owner cellData.revealed
Note: Unset properties return 0 by default.
Clearing Cell Data
clear Board # Clear ALL cells in entire grid
clear Board.cell[2][3] # Clear just one cell
Cell data is also cleared on reset("session").
Querying Cell Data with cellsWhere
Find cells where stored data matches a condition:
# Find all cells owned by RED
set redCells cellsWhere("Board", "owner", "==", "RED")
log "Red owns" length(redCells) "cells"
# Find all revealed cells
set revealed cellsWhere("Board", "revealed", "==", true)
# Find cells with 3+ adjacent mines
set danger cellsWhere("Board", "mineCount", ">=", 3)
Returns array of {x, y} objects.
Cell Data vs Occupancy - Complete Example
Here's a Minesweeper-style game showing both systems:
onEnter:
# CELL DATA: Randomly place mines (invisible data)
set mineCount 10
set placed 0
while placed < mineCount:
set x floor(random() * 10)
set y floor(random() * 10)
if Board.cell[x][y].mine != true:
set Board.cell[x][y].mine true
set placed placed + 1
# OCCUPANCY: Create cover tiles (visible objects)
foreach x in range(10):
foreach y in range(10):
spawn "CoverTile" {col: x, row: y}
# CoverTile template
onSpawn:
set self.snapToGrid "Board" # OCCUPANCY: Object goes to cell
set self.cellX col
set self.cellY row
show self
onClick:
# CELL DATA: Check if this cell has a mine
if Board.cell[self.cellX][self.cellY].mine:
shout "GAME_OVER"
else:
# CELL DATA: Mark as revealed
set Board.cell[self.cellX][self.cellY].revealed true
# OCCUPANCY: Remove the cover object
destroy self
Key points:
- Mines are stored as cell data (invisible, no object)
- Cover tiles are objects with occupancy (visible, clickable)
- Clicking reveals by destroying the object AND setting cell data
- Win condition can check cell data:
if count revealed cells == total - mines
AI with Minimax
For turn-based games like Connect Four or Tic-Tac-Toe, the minimax function finds the best move.
# minimax(grid, property, aiPlayer, humanPlayer, depth, emptyValue?, winLength?)
# Connect Four: Find best column for AI
set bestCol minimax("Board", "owner", "YELLOW", "RED", 5, 0, 4)
# Tic-Tac-Toe: 3x3 grid, 3 in a row wins
set bestCol minimax("Grid", "state", "O", "X", 9, "", 3)
Parameters:
grid: Grid object nameproperty: Cell data property to check (NOT object property!)aiPlayer: Value representing AI (e.g., "YELLOW", "O")humanPlayer: Value representing human (e.g., "RED", "X")depth: Search depth (4-6 recommended)emptyValue: What empty cells contain (default:0)winLength: How many in a row to win (default:4)
Note: Minimax reads from cell data, not object positions. You must set cell data when pieces are placed:
# When placing a piece
action placePiece:
spawn "Piece" {col: col, color: currentPlayer}
# ALSO store in cell data for minimax
set Board.cell[col][row].owner currentPlayer
Movement State
Read-only properties that reflect current movement:
| Property | Type | Description |
|---|---|---|
moving |
boolean | Currently in motion |
direction |
string | "left", "right", "up", "down", "none" |
velocityX |
number | Horizontal velocity |
velocityY |
number | Vertical velocity |
moveAngle |
number | Movement angle in degrees (0=right, 90=down, -1=stopped) |
moveSpeed |
number | Movement speed magnitude |
Movement Events
React to movement state changes with dedicated events:
# Fires when object starts moving in a direction
onMove "down":
set self.state "walkDown"
onMove "left":
set self.state "walkLeft"
set self.flipX true
# Fires when object stops moving
onStop:
set self.state "idle"
# Fires when object jumps (requires jumpable)
onJump:
set self.state "jumping"
# Fires when object lands on a surface
onLanding:
set self.state "idle"
shake self 50
Use onMove without a direction parameter to trigger on any direction change. The cardinal() function converts angles to direction strings:
onMove:
set self.state cardinal(self.moveAngle) # "right", "down", "left", "up"
Polling Movement State
You can also poll movement properties directly:
# Animate while moving
if Player.moving:
set Legs.rotation Legs.rotation + 5
# Face movement direction
if Player.direction == "left":
set Player.flipX true
else if Player.direction == "right":
set Player.flipX false
Component Animation
Children can query parent movement for coordinated animation:
# On a leg component inside Player
if parent.moving:
set self.rotation self.rotation + 10
else:
set self.rotation 0
Wait Movement
Scripts and dynamics run independently. When a key is pressed:
onKeyDownfires immediately- Dynamics processes movement over subsequent frames
- Position properties (
cellX,cellY) update after movement completes
This means reading positions immediately in onKeyDown gives you the previous frame's values.
The Problem
onKeyDown:
# BUG: This reads the OLD position before movement happens
log "Position:" Player.cellX Player.cellY
if Player.cellX == 5:
show WinMessage
The Solution
Use wait movement to pause until all objects have finished moving:
onKeyDown:
wait movement # Pause until dynamics settles
log "Position:" Player.cellX Player.cellY
if Player.cellX == 5:
show WinMessage
When to Use
| Scenario | Use wait movement? |
|---|---|
| Reading current positions | Yes |
| Checking win/lose conditions | Yes |
| Counting empty cells | Yes |
| Playing a sound | No (immediate is fine) |
| Showing/hiding UI | No (immediate is fine) |
Examples
Side-View Platformer
onEnter:
set Cell.gravity 1.5
# Player: movable + jumpable + keyboard
set Player movable speed 0.4
set Player.input "keyboard"
set Player jumpable height 0.6 multijump 2
set Player.subject true
set Player.blocking true
set Player.friction 0.3
# Ground and walls
foreach wall in #wall {
set wall.blocking true
set wall.friction 0.5
}
# Bouncy pad
set BouncePad.blocking true
set BouncePad.restitution 0.9
# One-way platforms (jump through from below)
foreach platform in #platform {
set platform.blocking true
set platform.oneWay true
}
Top-Down Push Puzzle
onEnter:
# No gravity in top-down
set Cell.gravity 0
# Player
set Player movable speed 0.3 deceleration 0.2
set Player.input "keyboard"
set Player.blocking true
set Player.mass 3
# Pushable crates (no keyboard input, just physics)
foreach crate in #crate {
set crate movable speed 0.3 deceleration 0.08
set crate.blocking true
set crate.mass 1
set crate.friction 0.1
}
# Walls (static blockers)
foreach wall in #wall {
set wall.blocking true
}
Random Scatter
Place pieces on random empty grid cells:
onEnter:
set empty emptyCells("Grid1")
set shuffled shuffle(empty)
set i 0
foreach piece in #piece {
set piece.snapToGrid "Grid1"
set piece.blocking true
set cell shuffled[i]
set piece.cellX cell.x
set piece.cellY cell.y
show piece with scale 200
wait 50ms
set i i + 1
}
Win Condition Check
Check game state after each move. Use wait movement to ensure positions are current:
# On the cell (Canvas script)
onKeyDown:
wait movement
self.checkGameState
action checkGameState {
# Win: all cells filled
set empty emptyCells("Grid1")
if length(empty) == 0:
show WinMessage
# Lose: too few empty cells
if length(empty) < 3:
show LoseMessage
}
Color-Based Stacking
Allow same-color pieces to stack, block different colors:
foreach piece in #piece {
set piece.snapToGrid "Grid1"
set piece movable style slide
set piece.blocking "self.color != other.color"
}
The color property can come from a tag or custom property set on each piece.