Tutorial: A Bouncing Ball
A ball that falls, bounces off the floor and walls, and slows down a little with every bounce. Tap or click to throw it again. Then a version with many balls. This is the pattern behind almost every moving picture: keep numbers in Python, change them in update(dt), and paint them in draw().
1. A ball that moves
import math
x, y = 100, 100 # where the ball is
vx, vy = 180, 0 # how fast it moves, in points per second
r = 24 # its radius
def update(dt):
global x, y
x += vx * dt
y += vy * dt
def draw():
ctx.fill_style = "#16213e"
ctx.fill_rect(0, 0, width, height)
ctx.fill_style = "orange"
ctx.begin_path()
ctx.arc(x, y, r, 0, math.tau)
ctx.fill()
Run it and the ball slides off to the right and away. Each frame, update(dt) moves it by its speed times dt, the time since the last frame. global x, y lets the function change the variables made at the top.
2. Gravity and bounces
Gravity is a steady push downwards: add to vy every frame. A bounce is a speed turned round: when the ball reaches the floor, put it back on the floor and flip vy. Multiplying by 0.8 rather than 1 loses a little speed on each bounce.
# Tap or click to throw the ball.
import math
x, y = 100, 100
vx, vy = 180, 0
r = 24
gravity = 900 # points per second, per second
bounce = 0.8 # how much speed is kept after a bounce
def update(dt):
global x, y, vx, vy
vy += gravity * dt
x += vx * dt
y += vy * dt
if y > height - r: # the floor
y = height - r
vy = -vy * bounce
vx *= 0.99 # a little friction
if x < r: # the left wall
x = r
vx = -vx * bounce
if x > width - r: # the right wall
x = width - r
vx = -vx * bounce
def on_mouse_down(mx, my):
global x, y, vx, vy
x, y = mx, my
vx, vy = 300, -500 # throw it up and to the right
def draw():
ctx.fill_style = "#16213e"
ctx.fill_rect(0, 0, width, height)
ctx.fill_style = "orange"
ctx.begin_path()
ctx.arc(x, y, r, 0, math.tau)
ctx.fill()
on_mouse_down(mx, my) is called when you click, or touch the preview with a finger. It moves the ball to that spot and throws it.
3. Lots of balls
With many balls, each one needs its own numbers. A small class keeps them together.
# Tap or click to add ten more balls.
import math, random
class Ball:
def __init__(self, x, y):
self.x, self.y = x, y
self.vx = random.uniform(-250, 250)
self.vy = random.uniform(-600, -200)
self.r = random.uniform(8, 26)
self.colour = f"hsl({random.randint(0, 360)}, 85%, 60%)"
def update(self, dt):
self.vy += 900 * dt
self.x += self.vx * dt
self.y += self.vy * dt
if self.y > height - self.r:
self.y = height - self.r
self.vy *= -0.8
if self.x < self.r or self.x > width - self.r:
self.x = min(max(self.x, self.r), width - self.r)
self.vx *= -0.8
def draw(self):
ctx.fill_style = self.colour
ctx.begin_path()
ctx.arc(self.x, self.y, self.r, 0, math.tau)
ctx.fill()
balls = [Ball(random.uniform(50, 350), 100) for _ in range(20)]
def on_mouse_down(x, y):
for _ in range(10):
balls.append(Ball(x, y))
def update(dt):
for ball in balls:
ball.update(dt)
def draw():
ctx.fill_style = "#16213e"
ctx.fill_rect(0, 0, width, height)
for ball in balls:
ball.draw()
ctx.fill_style = "white"
ctx.font = "16px sans-serif"
ctx.fill_text(f"{len(balls)} balls", 12, 24)
Turn on Frame rate in the footer and keep adding balls to see how many your device draws smoothly.
Try this
- Change
gravityto 200 for the Moon, or make it negative. - Let the balls bounce off the ceiling too.
- Give each ball a trail: paint the background with
"rgba(22, 33, 62, 0.3)"instead of a solid colour, so the last few frames fade out slowly.
Related
- How a program is shaped:
update,drawand theon_functions - Next: a simple game