Skip to main content

Step 2: Movement

Now let's make the worm move! We'll add automatic movement and learn how to update the worm's position each frame.

Understanding Worm Movement

The worm moves by:

  1. Adding a new head segment in the direction of movement
  2. Removing the tail segment
  3. This creates the illusion of smooth movement

Think of it like a train - the front car moves forward, and the back car disappears!

Direction Variables

Add variables to track which direction the worm is moving:

// Grid settings
let cellSize = 20;
let cols, rows;

// Worm
let worm = [];
let xSpeed = 1; // Horizontal direction (1 = right, -1 = left)
let ySpeed = 0; // Vertical direction (1 = down, -1 = up)

Breaking it down:

  • xSpeed = 1 means moving 1 cell to the right each update
  • ySpeed = 0 means not moving vertically
  • We'll only move in ONE direction at a time (not diagonal)

Possible combinations:

  • Right: xSpeed = 1, ySpeed = 0
  • Left: xSpeed = -1, ySpeed = 0
  • Down: xSpeed = 0, ySpeed = 1
  • Up: xSpeed = 0, ySpeed = -1

Slowing Down the Movement

p5.js draw() runs 60 times per second, which is too fast! We'll use frameRate() to slow it down:

function setup() {
createCanvas(400, 400);
frameRate(10); // Run draw() 10 times per second

// Calculate grid dimensions
cols = width / cellSize;
rows = height / cellSize;

// Initialize worm in the center
let startX = floor(cols / 2);
let startY = floor(rows / 2);
worm.push({x: startX, y: startY});
}

Why frameRate(10)?

  • Default is 60 FPS (frames per second)
  • 10 FPS gives a classic Snake game feel
  • Makes the game playable and visible
  • Try different values to adjust difficulty!

Moving the Worm

Add an updateWorm() function to handle movement:

function updateWorm() {
// Get the current head position
let head = worm[worm.length - 1];

// Calculate new head position
let newHead = {
x: head.x + xSpeed,
y: head.y + ySpeed
};

// Add new head to the front
worm.push(newHead);

// Remove the tail
worm.shift();
}

Breaking it down:

Getting the Head

let head = worm[worm.length - 1];
  • worm.length - 1 gets the last index
  • The last segment is the head (front of the worm)
  • If worm has 1 segment at index 0, head = worm[0]

Calculating New Position

let newHead = {
x: head.x + xSpeed,
y: head.y + ySpeed
};
  • Adds the speed to current position
  • Example: head at (10, 10), moving right (xSpeed=1)
    • New head: (11, 10)

Growing and Shrinking

worm.push(newHead);  // Add to end
worm.shift(); // Remove from beginning
  • push() adds new head segment
  • shift() removes tail segment
  • Net effect: worm moves forward by 1 cell
tip

Array order matters!

  • worm[0] = tail (oldest segment)
  • worm[worm.length-1] = head (newest segment)

We add to the end and remove from the beginning to create movement!

Update draw() Function

Call updateWorm() in your draw() function:

function draw() {
background(50);

// Update worm position
updateWorm();

// Draw grid lines (optional)
stroke(80);
strokeWeight(1);
for (let i = 0; i < cols; i++) {
line(i * cellSize, 0, i * cellSize, height);
}
for (let i = 0; i < rows; i++) {
line(0, i * cellSize, width, i * cellSize);
}

// Draw the worm
noStroke();
fill(0, 255, 0);
for (let i = 0; i < worm.length; i++) {
let segment = worm[i];
rect(segment.x * cellSize, segment.y * cellSize, cellSize, cellSize);
}
}

Complete Code

// Grid settings
let cellSize = 20;
let cols, rows;

// Worm
let worm = [];
let xSpeed = 1;
let ySpeed = 0;

function setup() {
createCanvas(400, 400);
frameRate(10); // 10 updates per second

// Calculate grid dimensions
cols = width / cellSize;
rows = height / cellSize;

// Initialize worm in the center
let startX = floor(cols / 2);
let startY = floor(rows / 2);
worm.push({x: startX, y: startY});
}

function draw() {
background(50);

// Update worm position
updateWorm();

// Draw grid lines (optional)
stroke(80);
strokeWeight(1);
for (let i = 0; i < cols; i++) {
line(i * cellSize, 0, i * cellSize, height);
}
for (let i = 0; i < rows; i++) {
line(0, i * cellSize, width, i * cellSize);
}

// Draw the worm
noStroke();
fill(0, 255, 0);
for (let i = 0; i < worm.length; i++) {
let segment = worm[i];
rect(segment.x * cellSize, segment.y * cellSize, cellSize, cellSize);
}
}

function updateWorm() {
// Get the current head position
let head = worm[worm.length - 1];

// Calculate new head position
let newHead = {
x: head.x + xSpeed,
y: head.y + ySpeed
};

// Add new head to the front
worm.push(newHead);

// Remove the tail
worm.shift();
}

Test It Out!

Run the code. You should see:

  • ✅ The worm moves automatically to the right
  • ✅ Movement happens at a reasonable speed (10 FPS)
  • ✅ The worm disappears off the right edge eventually

The worm can't be controlled yet and goes off screen - we'll fix these in the next steps!

How It Works - Frame by Frame

Let's say the worm starts at (10, 10):

Frame 1:

  • Worm: [{x:10, y:10}]
  • New head: {x:11, y:10}
  • After push: [{x:10, y:10}, {x:11, y:10}]
  • After shift: [{x:11, y:10}]

Frame 2:

  • Worm: [{x:11, y:10}]
  • New head: {x:12, y:10}
  • After push: [{x:11, y:10}, {x:12, y:10}]
  • After shift: [{x:12, y:10}]

And so on! The worm "slides" across the grid.

What You Learned

  • How to use frameRate() to control game speed
  • How to implement grid-based movement
  • How arrays can simulate movement (push + shift)
  • How to use direction variables for movement control
  • The difference between head and tail segments
  • How shift() removes the first element from an array