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:
- Adding a new head segment in the direction of movement
- Removing the tail segment
- 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 = 1means moving 1 cell to the right each updateySpeed = 0means 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 - 1gets 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 segmentshift()removes tail segment- Net effect: worm moves forward by 1 cell
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