This question already has answers here:
Why is my collision test always returning 'true' and why is the position of the rectangle of the image always wrong (0, 0)?
(1 answer)
How do I detect collision in pygame?
(5 answers)
How to detect collisions between two rectangular objects or images in pygame
(1 answer)
Closed 1 year ago.
For a computer science project, I'm making a game in pygame.
I'm working on collision right now with the Mario sprite and that block. However pygame collision works based on two rectangles colliding from what I know. You can easily put a rectangle around the block, but the Mario sprite is a different matter entirely. It becomes even more complicated when you realize you switch images for going left or right or jumping or whatnot.
I've tried looking this up on Google, where none of the answers worked. My CSP teacher also said he's not familiar with pygame and sadly can't help.
import pygame
pygame.init()
#set the background
screen = pygame.display.set_mode((1600, 800))
clock = pygame.time.Clock()
FPS = 45
vel = 3.5
BLACK = (0,0,0)
back = pygame.image.load('background.png')
background = pygame.transform.scale(back, (800,600))
playerimg = pygame.image.load('mariosprite.png').convert_alpha()
playerimgflipped = pygame.image.load('normalmarioflipped.png')
playerimgjump = pygame.image.load('finishedjumpingmario.png')
blockonelol = pygame.image.load('oneblock.png')
blockoneX = 100
blockoneY = 415
blockone = pygame.transform.scale(blockonelol,(40,40))
mario = pygame.transform.scale(playerimg, (35,50))
playerX = 10
playerY = 490
isJump = False
jumpCount = 11
run = True
while run:
clock.tick(FPS)
screen.fill(BLACK)
screen.blit(background, (0,0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and vel < playerX:
playerX = playerX - vel
mario = pygame.transform.scale(playerimgflipped, (35,50))
if keys[pygame.K_RIGHT]:
playerX+=vel
mario = pygame.transform.scale(playerimg, (35,50))
if not(isJump):
if keys[pygame.K_UP]:
isJump = True
if(playerY == 490):
mario = pygame.transform.scale(playerimg, (35, 50))
else:
mario = pygame.transform.scale(playerimgjump, (45,55))
if jumpCount >= -11:
goingdown = 1
if jumpCount < 0:
goingdown = -1
playerY -= (jumpCount ** 2) * 0.25 * goingdown
jumpCount = jumpCount - 1
if(playerY == 490):
mario = pygame.transform.scale(playerimg, (35, 50))
else:
isJump = False
jumpCount = 11
if(playerY == 490):
mario = pygame.transform.scale(playerimg, (35, 50))
screen.blit(blockone, (blockoneX, blockoneY))
screen.blit(mario, (playerX, playerY))
pygame.display.update()
However pygame collision works based on two rectangles colliding
Yes, but you don't need to add rectangles around the sprites.
Use pygame.Rect objects and colliderect() to detect the collision between the bounding rectangles of 2 objects or 2 images:
rect1 = pygame.Rect(x1, y1, w1, h1)
rect2 = pygame.Rect(x2, y2, w2, h2)
if rect1.colliderect(rect2):
# [...]
If you have to images (pygame.Surface objects), the bounding rectangle of can be get by get_rect(), where the location of the Surface has to be set by an keyword argument, since the returned rectangle always starts at (0, 0):
mario_rect = mario.get_rect(topleft = (playerX, playerY))
blockone_rect = blockone.get_rect(topleft = (blockoneX, blockoneY))
if mario_rect.colliderect(blockone_rect):
print("hit")
See also:
How do I detect collision in pygame?
How to detect collisions between two rectangular objects or images in pygame
Why is my collision test always returning 'true' and why is the position of the rectangle of the image always wrong (0, 0)?
Related
I am currently developing a platform game which depends on objects (in the form of platforms) to be their collision in order to prevent the player from exiting the window.
Here's my code for collision with platforms/blocks:
#check for collision
self.in_air = True
for tile in world.tile_list:
#collison x
if tile[1].colliderect(self.rect.x + dx,self.rect.y , self.width, self.height):
dx = 0
# collision y
if tile[1].colliderect(self.rect.x, self.rect.y + dy, self.width, self.height):
# below ground?
if self.vel_y < 0:
dy = tile[1].bottom - self.rect.top
self.vel_y = 0
# above ground?
elif self.vel_y >= 0:
dy = tile[1].top - self.rect.bottom
self.vel_y = 0
self.in_air = False
However, this looks unprofessional and I would like to add code which introduces an invisible barrier which stops the player from leaving the screen.
I have tried different methods but am currently unsure, any advice would be appreciated.
If you only want to limit the player to a bounding rectangle, I recommend using pygame.Rect.clamp_ip
moves the rectangle inside another, in place
Define the bounding respectively border rectangle. If the player is limited to the screen, you can get a rectangle that defines the border from the display Surface with pygame.display.get_surface() and pygame.Surface.get_rect:
border_rect = pygame.display.get_surface().get_rect()
Clamp the player pygame.Rect by border_rect:
self.rect.clamp_ip(border_rect)
You can do this with 1 line of code:
self.rect.clamp_ip(pygame.display.get_surface().get_rect())
Minimal example
import pygame
pygame.init()
window = pygame.display.set_mode((200, 200))
clock = pygame.time.Clock()
rect = pygame.Rect(0, 0, 40, 40)
rect.center = window.get_rect().center
speed = 10
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
rect.x += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * speed
rect.y += (keys[pygame.K_DOWN] - keys[pygame.K_UP]) * speed
rect.clamp_ip(pygame.display.get_surface().get_rect())
window.fill(0)
pygame.draw.rect(window, (255, 0, 0), rect)
pygame.display.flip()
clock.tick(60)
pygame.quit()
exit()
This question already has an answer here:
Setting up an invisible boundary for my sprite
(1 answer)
Closed 11 months ago.
I am looking on how to keep my sprite within the set boundaries of a window in Pygame. Could anyone here please help me keep the car sprite within the lines at all times? Thanks! (please don't come to edit my question, actually help me!)
import pygame
pygame.init()
screen = pygame.display.set_mode((300,208))
pygame.display.set_caption("TinyRacer")
car = pygame.image.load("car.png")
bg = pygame.image.load("bg.png")
run = True
y = 84
while run:
pygame.time.delay(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
key = pygame.key.get_pressed()
if key[pygame.K_UP]:
y -= 16
if key[pygame.K_DOWN]:
y += 16
screen.fill((255,255,255))
screen.blit(car, (0,y))
screen.blit(bg,(0,0))
pygame.display.update()
pygame.quit()
I have tried following Techwithtim's tutorial on this, but to no avail.
If you would keep position and size in pygame.Rect() then you could use special functions to check collisions.
Or simply check
car_rect.top < screen_rect.top and screen_rect.bottom < car_rect.bottom
Or you could use contains to check if one rect is fully inside another.
I create green background which is smaller than window, and I use Rect() to check if car in inside this green region.
car_rect.y -= 16
if car_rect.top < bg_rect.top:
car_rect.top = bg_rect.top
I also use Rect() to put elements in center of screen.
car_rect.centerx = screen_rect.centerx
car_rect.centery = screen_rect.centery
The same way you can put other elements (i.e. text in center of button)
To make it simpler I use surfaces instead of images so everyone can simply copy and run it.
import pygame
pygame.init()
screen = pygame.display.set_mode((300,208))
screen_rect = screen.get_rect()
pygame.display.set_caption("TinyRacer")
#car = pygame.image.load("car.png")
car = pygame.Surface((20,10))
car.fill((255,0,0))
car_rect = car.get_rect()
car_rect.centerx = screen_rect.centerx
car_rect.centery = screen_rect.centery
#bg = pygame.image.load("bg.png")
bg = pygame.Surface((300,100))
bg.fill((0,255,0))
bg_rect = bg.get_rect()
bg_rect.centery = screen_rect.centery
clock = pygame.time.Clock()
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
key = pygame.key.get_pressed()
if key[pygame.K_UP]:
car_rect.y -= 16
if car_rect.top < bg_rect.top:
car_rect.top = bg_rect.top
if key[pygame.K_DOWN]:
car_rect.y += 16
if car_rect.bottom > bg_rect.bottom:
car_rect.bottom = bg_rect.bottom
screen.fill((255,255,255))
screen.blit(bg, bg_rect)
screen.blit(car, car_rect)
pygame.display.update()
clock.tick(25) # 25FPS (it gives 40ms delay)
pygame.quit()
I am working on a little project to get to know Pygame and I was working with the following tutorial to introduce myself:
https://www.101computing.net/pong-tutorial-using-pygame-getting-started/
After following that tutorial and making tweaks of my own (mainly stylistic, nothing functional), every time I run the program the ball just goes back and forth on the same y coordinate and won't go up and down. It seems like everything else works but the variation in the vertical movement of the ball.
I can provide my code if needed as well, but it looks similar to the tutorial above.
edit: here's the code
import pygame
#need random integers for velocity changes
import random
Black = (0,0,0)
#create ball object for the game, wil be a sprite object
class Ball(pygame.sprite.Sprite):
#define the package function and also call the pygame sprite constructor using super()
def __init__(self, color, width, height):
super().__init__()
self.image = pygame.Surface([width, height])
self.image.fill(Black)
self.image.set_colorkey(Black)
#draw the ball
pygame.draw.rect(self.image, color, [0, 0, width, height])
#set velocity
self.velocity = [random.randint(4,8), random.randint(-8,8)]
#get rectangle object from image package
self.rect = self.image.get_rect()
def update(self):
self.rect.x += self.velocity[0]
self.rect.y += self.velocity[1]
#reverse velocity path of ball hits paddle
def bounce(self):
self.velocity[0] = -self.velocity[0]
self.velocity[1] = random.randint(-8,8)
-----Main File-----
import pygame
#import paddle sprites
from paddle import Paddle
#import ball
from ball import Ball
import time
pygame.init()
#set local colors: Black for background, white for text, blue and red for teams
Black = (0,0,0)
White = (255,255,255)
Red = (255,0,0)
Blue = (0,0,255)
#create paddles using paddle class and add them to a list of sprites
paddleLeft = Paddle(Red, 10, 100)
paddleLeft.rect.x = 20
paddleLeft.rect.y = 200
paddleRight = Paddle(Blue, 10, 100)
paddleRight.rect.x = 870
paddleRight.rect.y = 200
ball = Ball(White, 10, 10)
ball.rect.x = 445
ball.rect.y = 195
allSprites = pygame.sprite.Group()
allSprites.add(paddleLeft)
allSprites.add(paddleRight)
allSprites.add(ball)
#set game window
size = (900,500)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Multiplayer Pong")
#to the functionality, we will have a while loop that will listen to user inputs, adding logic to the game (score, boundaries, etc.), and refreshing the program
#global "running" funtion that will control the while loop, simple bool
running = True
#need a clock for refreshing the screen (included in pygame package)
clock = pygame.time.Clock()
#scores for each side
scoreLeft = 0
scoreRight = 0
#start loop
while running:
#--listen for inputs
for event in pygame.event.get():
if event.type == pygame.QUIT: #if quit button is pressed, leave
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_x:
running = False
#keyboard inputs
key = pygame.key.get_pressed()
if key[pygame.K_w]:
paddleLeft.mUp(5)
if key[pygame.K_s]:
paddleLeft.mDown(5)
if key[pygame.K_UP]:
paddleRight.mUp(5)
if key[pygame.K_DOWN]:
paddleRight.mDown(5)
#--logic
allSprites.update()
#--drawing here (paddles, screen, scores, boundaries, etc
screen.fill(Black)
pygame.draw.line(screen, White, [448, 0], [448, 500], 4)
allSprites.draw(screen)
#check for wall bounce
#algorithms for bounce look like
#Hits right or left wall? reverse X-bound velocity
#Hits top or bottom wall? reverse Y-bound velocity
if ball.rect.x >= 890:
scoreLeft += 1
ball.rect.x = 445
ball.rect.y = 195
time.sleep(2)
ball.velocity[0] = -ball.velocity[0]
if ball.rect.x <= 0:
scoreRight += 1
ball.rect.x = 445
ball.rect.y = 195
time.sleep(2)
ball.velocity[0] = -ball.velocity[0]
#reverse ball angle
if ball.rect.y >= 490:
ball.velocity[1] = -ball.velocity[1]
if ball.rect.y >= 0:
ball.velocity[1] = -ball.velocity[1]
#check for paddle hit
if pygame.sprite.collide_mask(ball, paddleLeft) or pygame.sprite.collide_mask(ball, paddleRight):
ball.bounce()
#display scores
font = pygame.font.SysFont("impact.ttf", 50)
text = font.render(str(scoreLeft), 1, Red)
screen.blit(text, (420,10))
text = font.render(str(scoreRight), 1, Blue)
screen.blit(text, (460,10))
#--update screen with drawings
pygame.display.flip()
#--60 fps limit
clock.tick(60)
#stop program once main loop is exited
pygame.quit()
After some debugging I realized that the y values was constantly hopping from some value up then down, the velocity seemed to work but it was constantly changing signs
The problem is on line 103 the:
if ball.rect.y >= 0:
you must of missed typed it as the y value will always be greater then 0, I fixed it by switching it to
if ball.rect.y <= 0:
and it worked.
I'm struggling to work out how collisions work in pygame. I understand it's something to do with pygame.rect.colliderect, and I'm probably pretty close, but I'd appreciate someone who knows a bit more than me having a look! :-)
Here's a simple program I've written to steer a tiny green square onto a larger red square, and I'm trying to implement collision detection when the two meet, but at this stage everything works except collisions.
Thanks in advance!!
pygame.init()
import random #import random
size = (700,500) # set up the screen
screen = pygame.display.set_mode((size))
BLACK = (0,0,0) #define colours
WHITE = (255,255,255)
GREEN = (0,255,0)
RED = (255, 0, 0)
class player(): #assign a player class
def __init__(self):
self.xpos = 450
self.ypos = 250
self.rect = pygame.Rect(self.xpos,self.ypos,5,5)
self.xvel = 0
self.yvel = 0
self.colour = GREEN
def update(self): #define a function to update the payer
#self.xpos +=self.xvel Ignore this bit. I implemented velocity, but it quickly flew off the screen
#self.ypos +=self.yvel
if player.rect.colliderect(obstacle.rect): #<--------- this is the bit I think might be wrong?
print("collision!")
def draw(self): #define a function to draw the player
pygame.draw.rect(screen, self.colour,[self.xpos,self.ypos,5,5])
class obstacle(): #define an obstacle class
def __init__ (self):
self.xpos = random.uniform(0,700)
self.ypos = random.uniform(0,500)
self.rect = pygame.Rect(self.xpos,self.ypos,20,20)
self.colour = RED
def draw(self): #define a function to draw the obstacle
pygame.draw.rect(screen, self.colour,[self.xpos,self.ypos, 20,20])
player = player() #run an instance of the player class
obstacle = obstacle() #run an instance of the obstacle class
clock = pygame.time.Clock()
while True: #game loop
for event in pygame.event.get(): #quit
if event.type == pygame.QUIT:
pygame.display.quit()
#-----Game logic
keys = pygame.key.get_pressed() #check for key presses and do whatever
if keys[pygame.K_LEFT]:
player.xpos -= 1
if keys[pygame.K_RIGHT]:
player.xpos += 1
if keys[pygame.K_UP]:
player.ypos -= 1
if keys[pygame.K_DOWN]:
player.ypos += 1
player.update() #Update the player - obstacle shouldn't need updating
#-----Drawing code
screen.fill(BLACK) #draw screen black
obstacle.draw() #draw the obstacle from the function
player.draw() #draw the player from the function
pygame.display.flip() #update
clock.tick(60)'''
The problem is you are not updating the position of your player's rectangle, which what colliderect looks at when detecting a collision. You were drawing the rectangle as you changed xpos and ypox but the coordinates for your rectangle rect.x and rect.y were not being updated accordingly. I changed the lines
def draw(self): #define a function to draw the player
pygame.draw.rect(screen, self.colour,[self.xpos,self.ypos,5,5])
to
def draw(self): #define a function to draw the player
pygame.draw.rect(screen, self.colour,[self.rect.x,self.rect.y,5,5])
and
if keys[pygame.K_LEFT]:
player.xpos -= 1
if keys[pygame.K_RIGHT]:
player.xpos += 1
if keys[pygame.K_UP]:
player.ypos -= 1
if keys[pygame.K_DOWN]:
player.ypos += 1
to
if keys[pygame.K_LEFT]:
player.rect.x -= 1
if keys[pygame.K_RIGHT]:
player.rect.x += 1
if keys[pygame.K_UP]:
player.rect.y -= 1
if keys[pygame.K_DOWN]:
player.rect.y += 1
so that the player's rectangle coordinates would be updated.
In these cases always print out what is not working, i.e. player.rect and obstacle.rect. You will see that you are not updating player.rect, which is always
<rect(150, 150, 5, 5)>
Since it does not overlap with obstacle.rect, which is always
<rect(34, 204, 20, 20)>
the collision is not detected.
This is my code so far, I can move and it places out a blip to pick up, I just need to know how to register that and move the blip to a new random spot!
I am very new to pygame and not 100% fluent in python either, but I'm decent. If there are pdf:s good for intermediate coders when it comes to python that would be wonderful!
import sys, pygame, os, math
from random import randint
pygame.init()
size = width, height = 800, 600
speed = [2, 2]
black = 1, 1, 1
screen = pygame.display.set_mode(size)
pygame.display.set_caption('Pick up the squares!')
UP='up'
DOWN='down'
LEFT='left'
RIGHT='right'
ball = pygame.image.load("ball.png")
ballrect = ball.get_rect()
ballx = 400
bally = 300
blip = pygame.image.load("blip.png")
bliprect = blip.get_rect()
blipx = randint(1,800)
blipy = randint(1,600)
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((250, 250, 250))
clock = pygame.time.Clock()
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT: sys.exit()
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
ballx -= 5
if keys[pygame.K_RIGHT]:
ballx += 5
if keys[pygame.K_UP]:
bally -= 5
if keys[pygame.K_DOWN]:
bally +=5
screen.fill(black)
screen.blit(ball, (ballx,bally))
screen.blit(blip, (blipx, blipy))
pygame.display.update()
clock.tick(40)
Use the colliderect method of rectangles:
if ballrect.colliderect(bliprect):
print 'Do something here'
A basic collision detection works like this (assuming you are working with two rectangles):
def does_collide(rect1, rect2):
if rect1.x < rect2.x + rect2.width and rect1.x + rect1.width > rect2.x \
and rect1.y < rect2.y + rect2.height and rect1.height + rect1.y > rect2.y:
return True
return False
Fortunately Pygame is packed with such methods, so you should go with #MalikBrahimi's answer - using colliderect function call, which will do the math for you.
You can also try using pygame.sprite.spritecollide():
if pygame.sprite.spritecollide(a, b, False):
pass
#Do something
a here is your variable name for the class for one of the sprites in the collision. b here is the group that your second sprite will be. You can set the last one to True, which will remove the sprite from the group. Setting to False will keep it the way it is. Here is an example:
if pygame.sprite.spritecollide(my_ball, ballGroup, False):
Ball.speed[0] = -Ball.speed[0]
hit.play()
Ball.move()
The variable name for my sprite is my_ball. The group containing the other sprite(s) in this collision is ballGroup. I set the last one to False to keep all the sprites on the surface. If I set to True, the sprite from ballGroup will be removed from the group and screen. I hope this helps you!