GameSpark/Tutorials Open editor ↗
GameSpark · Lesson 2

Motion and bouncing

In the last lesson you moved a circle by pressing keys. Now we will make it move on its own and bounce off the edges — the foundation of countless games, from Pong to Breakout.

1. Velocity

The trick is to store not just where something is, but how fast it is moving — its velocity. Add a little to the position every frame and it drifts:

let x = 64;
let y = 40;
let dx = 1.5;  // horizontal speed (delta-x per frame)
let dy = 1;    // vertical speed

function update() {
  x += dx;
  y += dy;
}

function draw() {
  cls(0);
  circfill(x, y, 4, 12);  // color 12 = light blue
}

Save with ⌘S. The circle slides down and to the right… and keeps going, straight off the screen. We need to catch it at the edges.

2. Bouncing off the walls

The screen runs from 0 to 127. When the circle reaches an edge, we flip the matching velocity — moving right (dx positive) becomes moving left (dx negative). Because the circle has a radius of 4, we bounce when its edge (not its center) hits the wall:

let x = 64;
let y = 40;
let dx = 1.5;
let dy = 1;
let r = 4;

function update() {
  x += dx;
  y += dy;

  if (x < r || x > 127 - r) {
    dx = -dx;  // hit left or right wall
  }
  if (y < r || y > 127 - r) {
    dy = -dy;  // hit top or bottom wall
  }
}

function draw() {
  cls(0);
  circfill(x, y, r, 12);
}

Now it ricochets around forever. -dx reverses the direction: 1.5 becomes -1.5, and when it hits the far wall, back to 1.5.

💡

Why check against 127 - r? x is the circle’s center. Its right edge is x + r, so it touches the right wall when x + r > 127, i.e. x > 127 - r. Forgetting the radius makes things sink halfway into the wall before bouncing.

3. Speed it up over time

Games get more exciting when they get faster. Because velocity is just a number, you can nudge it on a bounce. To speed up on the floor only, give the floor its own check — separate from the ceiling — so it can both flip dy and grow dx. Replace update() with this:

function update() {
  x += dx;
  y += dy;

  if (x < r || x > 127 - r) {
    dx = -dx;
  }
  if (y < r) {
    dy = -dy;    // ceiling: just bounce
  }
  if (y > 127 - r) {
    dy = -dy;    // floor: bounce...
    dx *= 1.05;  // ...and get 5% faster across
  }
}

Keeping the ceiling and floor as separate ifs is what lets the floor do something extra. If you had left them as one combined check and added a second floor check, a floor hit would flip dy twice — cancelling the bounce, so the ball would fall straight through.

🎯

Try it: start the circle with a random direction using rnd() (e.g. let dx = rnd(3) - 1.5). Draw a trail by clearing to a dark color only sometimes. Add a second ball with its own x2/y2/dx2/dy2.

What you learned