How to write it
a.touches(b)
- a
- Any sprite: something made with
game.sprite(),game.box(),game.circle()orgame.label(). - b
- The other sprite to check against. Just one sprite, not a list (see lots of enemies below).
It gives back True if the two overlap right now, and False if they don't. It works both ways round: a.touches(b) and b.touches(a) always give the same answer.
A full example
Move the chicken with the arrow keys. Grab all the coins, and dodge the spinning rocks. The red boxes are switched on with game.debug(True) so you can see exactly what is being checked:
import game
import random
game.window(480, 360, background="#0b1020")
game.debug(True) # red boxes: what touches() checks
player = game.sprite("chicken", 240, 300, size=50)
board = game.label("Coins: 0", 70, 24)
coins = []
for i in range(8):
coin = game.sprite("coin", random.randint(30, 450), random.randint(60, 200), size=30)
coins.append(coin)
rocks = []
for i in range(2):
rock = game.sprite("asteroid", random.randint(30, 450), -40 - i * 200, size=44)
rocks.append(rock)
score = 0
while game.playing():
if game.pressed("left"):
player.x = player.x - 6
if game.pressed("right"):
player.x = player.x + 6
if game.pressed("up"):
player.y = player.y - 6
if game.pressed("down"):
player.y = player.y + 6
for coin in coins[:]: # [:] loops over a COPY of the list
if player.touches(coin):
score = score + 1
board.content = "Coins: " + str(score)
game.sound("coin")
coin.remove() # gone from the screen
coins.remove(coin) # and gone from the list
for rock in rocks:
rock.y = rock.y + 4
rock.angle = rock.angle + 5
if rock.y > 400:
rock.x = random.randint(30, 450)
rock.y = -40
if player.touches(rock):
game.game_over("Bonk! Coins: " + str(score), retry=True)
if len(coins) == 0:
game.game_over("You got them all!", retry=True)
game.frame()
Happy with the game? Delete the game.debug(True) line (or change it to game.debug(False)) and the red boxes disappear.
How it checks
touches() does not look at the picture itself. Every sprite has an invisible collision box around it, and touches() checks whether two boxes overlap.
- Emoji and built-in sprites get a box a little smaller than their
size(about 80%), so a near miss feels fair. Some built-in sprites get a shape that fits them better: the car is wide and low, the egg is tall. game.box()andgame.circle()use their own width and height. A circle still checks a square box around it.- Turning and stretching count. The box spins with
.angleand grows with.size,.scale_xand.scale_y. That is why the rocks in the example have tilted red boxes. - Sprites from the Asset studio get a box that hugs the drawing, not the empty space around it.
Turn on game.debug(True) right after game.window(...) any time a collision feels wrong. Each sprite gets a red outline, and the outline is exactly the box touches() uses.
Make the box fit better
If a sprite gets hit when it clearly wasn't, its box is too big. Give it your own size with .hitbox, as (width, height):
player.hitbox = (30, 36) # a smaller, fairer box player.hitbox = None # back to the automatic size
Your box still spins and stretches with the sprite.
Lots of coins, lots of enemies
Keep many sprites in a list, then check each one with a for loop, like the rocks in the example. When something should disappear after it's touched (a coin, an egg, a dead alien), do two things:
- loop over a copy of the list, written
coins[:], and - call both
coin.remove()(off the screen) andcoins.remove(coin)(out of the list).
for coin in coins[:]:
if player.touches(coin):
coin.remove()
coins.remove(coin)
Why the copy? Removing things from a list while a for loop is walking through it makes the loop skip the next item. Walking through a copy, while removing from the real list, keeps every coin checked.
Why isn't my touches() working?
- The score goes up by loads at once.
touches()isTrueon every frame the two overlap, not just the first one. Move the coin away, or remove it, as soon as it's caught. - A hidden or removed sprite still gets hit.
hide()andremove()only take it off the screen. The sprite still has itsxandy, sotouches()can still sayTrue. Take it out of your list too, so you stop checking it. - Only some coins get collected. You removed from the list while looping over the same list. Loop over
coins[:]instead. - You wrote
player.touches(coins).touches()takes one sprite, not a whole list. Loop over the list and check each one. - It's hit before the sprites even meet, or they overlap and nothing happens. Turn on
game.debug(True)and look at the red boxes, then fix the size with.hitbox. - A long label doesn't seem to collide. A label's box is a small square in the middle of the text (about 80% of its
size), not the whole sentence. Use agame.box()for big targets.