34. Moving things
Chapters 32 and 33 covered drawing and input. This chapter connects them: objects that move in response to input, bounce off walls, and collide with each other.
Position as variables
The simplest way to move anything is to store its position in variables and update them each frame. In the draw section, use those variables to position the shape:
x = 100
y = 100
# In the game loop update section:
x += 3 # move 3 pixels right every frame
# In the draw section:
pygame.draw.rect(screen, (255, 100, 0), (x, y, 40, 40))At 60 fps, adding 3 each frame moves the object 180 pixels per second.
Velocity
Velocity is the change in position per frame. Store it in variables
dx (change in x) and dy (change in y):
x = 100
y = 100
dx = 3
dy = 2
# Update section each frame:
x += dx
y += dyTo reverse direction, negate the velocity: dx = -dx.
This is the "bounce" pattern — used for bouncing balls.
pygame.Rect
pygame.Rect(x, y, width, height) is a rectangle object
that pygame provides for convenient positioning and collision
detection.
rect = pygame.Rect(100, 200, 60, 60)Useful attributes:
| Attribute | Meaning |
|---|---|
rect.x |
Left edge x position |
rect.y |
Top edge y position |
rect.width |
Width in pixels |
rect.height |
Height in pixels |
rect.centerx |
x coordinate of the centre |
rect.centery |
y coordinate of the centre |
rect.right |
x coordinate of the right edge |
rect.bottom |
y coordinate of the bottom edge |
Move the rect by assigning to rect.x and
rect.y. Use it in draw calls directly —
pygame.draw.rect accepts a Rect:
rect = pygame.Rect(100, 200, 60, 60)
rect.x += 3
pygame.draw.rect(screen, (255, 0, 0), rect)Setting rect.centerx and rect.centery moves
the rect so its centre is at that position:
rect.centerx = 400
rect.centery = 300
# rect is now centred in an 800x600 windowControlling frame rate
clock.tick(60) limits the loop to 60 frames per second.
Without it, a fast CPU runs thousands of frames per second and objects
fly across the screen far too quickly. With it, movement speed is
predictable: adding 4 to x each frame moves an object
4 * 60 = 240 pixels per second, on any machine.
On a very slow machine clock.tick(60) cannot force the
loop to run faster than it naturally runs. The actual frame rate may be
lower than 60 on slow hardware. For this book's purposes, 60 fps is
reliable enough.
Boundary checking
Objects that move off the screen need to be stopped or bounced back. Check the edges manually each frame.
Stop at the wall:
if rect.left < 0:
rect.left = 0
if rect.right > 800:
rect.right = 800
if rect.top < 0:
rect.top = 0
if rect.bottom > 600:
rect.bottom = 600rect.left < 0 means the left edge has gone past the
left wall. Setting rect.left = 0 pushes it back.
Bounce off the wall:
if rect.left < 0 or rect.right > 800:
dx = -dx
if rect.top < 0 or rect.bottom > 600:
dy = -dyReversing velocity sends the object back the way it came.
Collision detection with Rect.colliderect
Rect.colliderect(other_rect) returns True
if the two rectangles overlap:
player_rect = pygame.Rect(100, 100, 50, 50)
coin_rect = pygame.Rect(120, 110, 30, 30)
if player_rect.colliderect(coin_rect):
print("collected!")Use this to detect when a player touches a coin, an enemy, or any other object.
Full example: arrow-key square that bounces
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Moving Square")
clock = pygame.time.Clock()
rect = pygame.Rect(370, 270, 60, 60)
speed = 4
running = True
while running:
# 1. Events
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# 2. Update
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
rect.x -= speed
if keys[pygame.K_RIGHT]:
rect.x += speed
if keys[pygame.K_UP]:
rect.y -= speed
if keys[pygame.K_DOWN]:
rect.y += speed
# Clamp to window edges
if rect.left < 0:
rect.left = 0
if rect.right > 800:
rect.right = 800
if rect.top < 0:
rect.top = 0
if rect.bottom > 600:
rect.bottom = 600
# 3. Draw
screen.fill((30, 30, 30))
pygame.draw.rect(screen, (255, 100, 50), rect)
# 4. Flip
pygame.display.flip()
clock.tick(60)
pygame.quit()Open exercises/34/01-bouncing-ball.py. Add a ball
(circle) that moves on its own using dx and
dy. Each frame, add dx to x and
dy to y. When the ball hits the left or right edge of the
window, negate dx. When it hits the top or bottom, negate
dy. Start with dx = 3, dy = 2.
Homework
Problem 1 — Clamped mover
Open exercises/34/homework/01-clamped-mover.py. A square
moves with arrow keys. It must never leave the window — clamp it to all
four edges.
Problem 2 — Two-ball bounce
Open exercises/34/homework/02-two-balls.py. Two balls
start at different positions with different velocities. Both bounce off
all four walls independently. Each ball should be a different
colour.
Problem 3 — Follow the mouse
Open exercises/34/homework/03-follow-mouse.py. A square
moves toward the current mouse position each frame. It does not jump to
the cursor; it moves a fixed number of pixels toward it per frame,
stopping when it arrives. Hint: compute the difference in x and y, and
move a fraction of that difference.
Challenge — Collect a target
Open exercises/34/homework/04-collect-target.py. A
player square moves with arrow keys. A stationary target circle is drawn
somewhere on screen. When the player's rect overlaps the target (use
colliderect or a distance check for circles), move the
target to a new random position and print "collected!" to
the terminal.
Stuck or finished? Open the homework solutions page.