Hello again. Your dungeon room currently draws, but it does not yet change: playerX and playerY stay at the same values every time p5.js renders the screen.
In this lesson, you will turn that static player into a moving dungeon explorer. The key idea is simple: store the player’s position in variables, change one position slightly each time draw() runs, and redraw the whole scene. By the end, you will understand the separate roles of setup() and draw() and have a player that moves across the room automatically. Plan on about 35–40 minutes.
Two special functions run your sketch
You have already used these functions:
function setup() {
createCanvas(600, 400);
}
function draw() {
background(18, 21, 31);
}
They look similar, but p5.js treats them very differently:
setup()runs once, when you press Play.draw()runs repeatedly, normally about 60 times per second, until you stop the sketch.
Think of setup() as preparing the game before play begins: it creates the canvas and can establish one-time settings. Think of draw() as the game’s repeating moment of action: it updates the game world and shows its current appearance.
Read “Get Started” from p5.js for a concise explanation of the two functions that form the structure of a sketch.
In the “Default Code” section, find the paragraph beginning “When the code in sketch.js is executed:”. Read the setup and draw explanation. Focus on the contrast between the one-time canvas creation in setup() and the repeating code in draw().
Here is a visual example of that familiar p5.js editor layout: code on the left and the running sketch on the right.

The braces are important:
function draw() {
// Everything between these braces repeats.
}
Any instruction inside draw() happens again and again. That repetition is what makes animation possible.
Animation is changing state, then drawing it
Your player’s x-position is stored here:
let playerX = 50;
This number is part of the game’s state: information the sketch remembers from one frame to the next. If playerX becomes larger, the player moves right because larger x-coordinates are farther right on the canvas.
To change its stored value, write:
playerX = playerX + 1.5;
Read this from right to left:
- Take the current value of
playerX. - Add
1.5. - Store the new value back in
playerX.
The = sign here means assign this new value, not “these two things are permanently equal.”
If the value begins at 50, the first few repeated updates look like this:
| Time draw runs | playerX after updating |
|---|---|
| First time | 51.5 |
| Second time | 53 |
| Third time | 54.5 |
| Later | Continues increasing |
A difference of only 1.5 pixels is hard to notice in a single image. But because draw() repeats quickly, those small changes become visible motion.
Watch this short segment before coding. It demonstrates the same idea with a ball: a variable stores a position, and a separate value determines how much that position changes each repeated frame.
Simple Bouncing Ball Tutorial: Processing Javascript
In “Simple Bouncing Ball Tutorial: Processing Javascript,” TokyoEdtech demonstrates the p5.js editor, then uses a position variable to animate a circle.
Watch the sketch structure to reinforce what the editor, canvas, setup(), and repeating draw() do. Then watch the movement update. Focus on the line that repeatedly adds a vertical speed value to the ball’s position; your dungeon player will use the same pattern horizontally.
Build an automatically moving dungeon player
Open the room sketch from the previous lesson. Replace it with the version below, then press Play.
// Dungeon game state
let playerName = "Quest Runner";
let playerHealth = 3;
let hasDungeonKey = false;
// Player position and automatic movement
let playerX = 50;
let playerY = 200;
let playerSpeed = 1.5;
function setup() {
createCanvas(600, 400);
}
function draw() {
// Update the player's game state
playerX = playerX + playerSpeed;
// Redraw the dungeon floor
background(18, 21, 31);
// Stone walls
fill(63, 54, 75);
stroke(109, 92, 126);
strokeWeight(3);
rect(0, 0, 600, 28);
rect(0, 372, 600, 28);
rect(0, 0, 28, 400);
rect(572, 0, 28, 400);
// Draw the player at its updated position
fill(95, 204, 180);
stroke(220, 255, 245);
strokeWeight(2);
circle(playerX, playerY, 30);
// Draw the player's sword
stroke(245, 215, 120);
strokeWeight(4);
line(playerX + 8, playerY, playerX + 21, playerY - 13);
}
Your player should travel from left to right across the dungeon. Eventually it will leave the screen; that is expected for now. Keeping it inside the room is a later game-rule problem. For this lesson, the goal is to see that changing a variable in draw() produces motion.
Notice that the sword also moves, even though its code never changes playerX. Its endpoints are calculated from playerX and playerY:
line(playerX + 8, playerY, playerX + 21, playerY - 13);
Because the sword’s coordinates depend on the player’s coordinates, it remains attached to the player. This is a useful game-design pattern: draw related visual pieces using the same underlying position variables.
Why playerSpeed is its own variable
You could have written this:
playerX = playerX + 1.5;
But the version below is easier to tune:
let playerSpeed = 1.5;
// Later, inside draw()
playerX = playerX + playerSpeed;
Now the number controlling movement has a meaningful name. Change only this line to experiment:
let playerSpeed = 4;
The player will move much faster. Try a value such as 0.5 afterward for a slow walk. This small separation between position and speed will be useful once you control the player with the keyboard.
Why the old player disappears every frame
At first it may seem wasteful to redraw the dungeon walls and floor repeatedly. But this is exactly what prevents the player from leaving copies of itself behind.
Each time draw() runs, it does these jobs in order:
- Updates
playerX. - Draws a fresh background, covering the previous frame.
- Draws a fresh set of walls.
- Draws the player at its new position.
The result is like a fast flipbook: each page shows the player in a slightly different place. Your eyes combine those still images into movement.
The line that clears the previous image is:
background(18, 21, 31);
It belongs inside draw(), before the room and player are rendered.
To see why, make a temporary experiment: comment out the background(...) line by putting // in front of it.
// background(18, 21, 31);
Press Play. Instead of one moving player, you should see a trail of many circles. Each frame is still being drawn, but nothing covers the old frame. Restore the original background(...) line afterward.
This p5.js tutorial connects variable changes directly to animation and explains why background() is usually redrawn each frame.
First, in the “Using Variables for Animation” subsection, read the movement principle. Then scroll to the “Animation and draw()” subsection. Read the flipbook explanation, paying particular attention to how the repeated background covers a previous frame before a new object position is drawn.
Put variables where the sketch can remember them
playerX, playerY, and playerSpeed are declared above setup() and draw():
let playerX = 50;
let playerY = 200;
let playerSpeed = 1.5;
This placement matters. Variables declared there can be used by both functions, and their values persist while the sketch runs.
A common beginner mistake is putting the starting value inside draw():
function draw() {
let playerX = 50;
playerX = playerX + 1.5;
}
This does not create sustained motion. Every time draw() begins, playerX is made new again and reset to 50. It can only reach 51.5 in that frame, then it is reset on the next frame.
For a changing game value:
- Declare it once outside
draw(). - Update it inside
draw(). - Use it when drawing the object.
That three-part pattern will appear throughout your game: player positions, enemy positions, projectiles, health, score, and timers all need values that the sketch can remember.
A quick animation checklist
Before moving on, run your sketch and confirm these visual facts:
- The room remains visible rather than accumulating old drawings.
- The player circle and sword move together.
- A larger positive
playerSpeedmakes the player move right faster. - A negative speed, such as
let playerSpeed = -1.5;, makes the player move left. - Pressing Stop and then Play starts the sketch again from
playerX = 50.
You can also replace the longer update with this shorter JavaScript form:
playerX += playerSpeed;
It has the same meaning as:
playerX = playerX + playerSpeed;
For now, prefer the longer version whenever it helps you see clearly what the code is doing. You will encounter the shorter form often in game code.
Key takeaways
You now have the core loop behind a moving game object:
setup()runs once and is the right place to create the canvas.draw()repeats continuously while the sketch is running.- Animation happens when a remembered value such as
playerXchanges in each pass throughdraw(). playerSpeedcontrols how much the player’s position changes each frame.- Redrawing
background()insidedraw()clears the previous frame, producing clean movement rather than a trail. - Position variables must be declared outside
draw()so they do not reset every frame.
Next, you will replace the automatic movement with keyboard input, so the player moves only when you press movement keys.
Can't find a good explanation? Sign up and we'll make it for you
Sign up