how do you add collision to a draw.line to trigger a game over or make a code that detects when the player comes in contact with it (Snake) [duplicate] - pygame

So I am trying to make an among us type game with pygame. I just started, so I don't have much of anything and am working on the map right now. However, one thing I'm struggling with is the collision logic. The map has an elongated octagon shape for now, but I think no matter the shape I will use something like a pygame polygon. When I ran the code I have now, which checks for a collision between my player (pygame rectangle) and the walls (pygame polygon) it says:
TypeError: Argument must be rect style object
I've figured out this is because of the pygame polygon returning a rectangle, but not being classified that way in the collision checker. I have tried a library called collision, and credit to the collision detection for giving a great effort, but the player was still able to glitch through the walls. Sidenote: I saved the code where I used this library if anyone wants to see it and maybe improve upon my faults.
Anyway, to boil it all down:
I need a way to detect collisions (really, really preferably in pygame) between polygons and rectangles
Thank you for any help you can give and if you have a question/request please leave a comment.
Heres my code:
import pygame
pygame.init()
W, H=500, 500
screen = pygame.display.set_mode([500, 500])
running = True
bcg=(200, 200, 200)
red=(255, 0 ,0)
purp=(255, 0, 255)
wall=(100, 100, 100)
class player:
def bg(self):
screen.fill(bcg)
x,y=self.x,self.y
self.outer=(
(x,y),
(x+800, y),
(x+1200, y+200),
(x+1200, y+600),
(x+800, y+800),
(x, y+800),
(x-400, y+600),
(x-400, y+200),
(x,y),
(x, y+50),
(x-350, y+225),
(x-350, y+575),
(x, y+750),
(x+800, y+750),
(x+1150, y+575),
(x+1150, y+225),
(x+800, y+50),
(x, y+50)
)
pygame.draw.polygon(screen, wall, self.outer)
def __init__(self, color, size=20, speed=0.25):
self.x=0
self.y=0
self.col=color
self.size=size
self.speed=speed
def draw(self):
s=self.size
self.rect=pygame.Rect(W/2-s/2, H/2-s/2, self.size, self.size)
pygame.draw.rect(screen, self.col, self.rect)
def move(self, x, y):
x*=self.speed
y*=self.speed
if not self.rect.colliderect(self.outer):
self.x+=x
self.y+=y
p=player(red)
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
p.bg()
keys=pygame.key.get_pressed()
if keys[pygame.K_a]: p.move(1, 0)
if keys[pygame.K_d]: p.move(-1, 0)
if keys[pygame.K_w]: p.move(0, 1)
if keys[pygame.K_s]: p.move(0, -1)
p.draw()
pygame.display.update()
pygame.quit()

Write a function collideLineLine that test if to line segments are intersecting. The algorithm to this function is explained in detail in the answer to the question pygame, detecting collision of a rotating rectangle:
def collideLineLine(l1_p1, l1_p2, l2_p1, l2_p2):
# normalized direction of the lines and start of the lines
P = pygame.math.Vector2(*l1_p1)
line1_vec = pygame.math.Vector2(*l1_p2) - P
R = line1_vec.normalize()
Q = pygame.math.Vector2(*l2_p1)
line2_vec = pygame.math.Vector2(*l2_p2) - Q
S = line2_vec.normalize()
# normal vectors to the lines
RNV = pygame.math.Vector2(R[1], -R[0])
SNV = pygame.math.Vector2(S[1], -S[0])
RdotSVN = R.dot(SNV)
if RdotSVN == 0:
return False
# distance to the intersection point
QP = Q - P
t = QP.dot(SNV) / RdotSVN
u = QP.dot(RNV) / RdotSVN
return t > 0 and u > 0 and t*t < line1_vec.magnitude_squared() and u*u < line2_vec.magnitude_squared()
Write the function colideRectLine that test if a rectangle and a line segment is intersecting. To test if a line segment intersects a rectangle, you have to test if it intersect any of the 4 sides of the rectangle:
def colideRectLine(rect, p1, p2):
return (collideLineLine(p1, p2, rect.topleft, rect.bottomleft) or
collideLineLine(p1, p2, rect.bottomleft, rect.bottomright) or
collideLineLine(p1, p2, rect.bottomright, rect.topright) or
collideLineLine(p1, p2, rect.topright, rect.topleft))
The next function collideRectPolygon tests if a polygon and a rectangle are intersecting. This can be achieved by testing each line segment on the polygon against the rectangle in a loop:
def collideRectPolygon(rect, polygon):
for i in range(len(polygon)-1):
if colideRectLine(rect, polygon[i], polygon[i+1]):
return True
return False
Finally you can use collideRectPolygon for the collision test. Note, however, that for the test you need to use the polygon as if the player were moving:
class player:
def bg(self):
screen.fill(bcg)
self.outer = self.createPolygon(self.x, self.y)
pygame.draw.polygon(screen, wall, self.outer)
def createPolygon(self, x, y):
return [
(x,y), (x+800, y), (x+1200, y+200), (x+1200, y+600),
(x+800, y+800), (x, y+800), (x-400, y+600), (x-400, y+200),
(x,y), (x, y+50), (x-350, y+225), (x-350, y+575),
(x, y+750), (x+800, y+750), (x+1150, y+575), (x+1150, y+225),
(x+800, y+50),(x, y+50)]
# [...]
def move(self, x, y):
x *= self.speed
y *= self.speed
polygon = self.createPolygon(self.x + x, self.y + y)
if not collideRectPolygon(self.rect, polygon):
self.x += x
self.y += y
See also Collision and Intersection - Rectangle and polygon
Minimal example:
repl.it/#Rabbid76/PyGame-CollisionPolygonRectangle
Complete example:
import pygame
pygame.init()
W, H=500, 500
screen = pygame.display.set_mode([500, 500])
running = True
bcg=(200, 200, 200)
red=(255, 0 ,0)
purp=(255, 0, 255)
wall=(100, 100, 100)
def collideLineLine(l1_p1, l1_p2, l2_p1, l2_p2):
# normalized direction of the lines and start of the lines
P = pygame.math.Vector2(*l1_p1)
line1_vec = pygame.math.Vector2(*l1_p2) - P
R = line1_vec.normalize()
Q = pygame.math.Vector2(*l2_p1)
line2_vec = pygame.math.Vector2(*l2_p2) - Q
S = line2_vec.normalize()
# normal vectors to the lines
RNV = pygame.math.Vector2(R[1], -R[0])
SNV = pygame.math.Vector2(S[1], -S[0])
RdotSVN = R.dot(SNV)
if RdotSVN == 0:
return False
# distance to the intersection point
QP = Q - P
t = QP.dot(SNV) / RdotSVN
u = QP.dot(RNV) / RdotSVN
return t > 0 and u > 0 and t*t < line1_vec.magnitude_squared() and u*u < line2_vec.magnitude_squared()
def colideRectLine(rect, p1, p2):
return (collideLineLine(p1, p2, rect.topleft, rect.bottomleft) or
collideLineLine(p1, p2, rect.bottomleft, rect.bottomright) or
collideLineLine(p1, p2, rect.bottomright, rect.topright) or
collideLineLine(p1, p2, rect.topright, rect.topleft))
def collideRectPolygon(rect, polygon):
for i in range(len(polygon)-1):
if colideRectLine(rect, polygon[i], polygon[i+1]):
return True
return False
class player:
def bg(self):
screen.fill(bcg)
self.outer = self.createPolygon(self.x, self.y)
pygame.draw.polygon(screen, wall, self.outer)
def createPolygon(self, x, y):
return [
(x,y), (x+800, y), (x+1200, y+200), (x+1200, y+600),
(x+800, y+800), (x, y+800), (x-400, y+600), (x-400, y+200),
(x,y), (x, y+50), (x-350, y+225), (x-350, y+575),
(x, y+750), (x+800, y+750), (x+1150, y+575), (x+1150, y+225),
(x+800, y+50),(x, y+50)]
def __init__(self, color, size=20, speed=0.25):
self.x=0
self.y=0
self.col=color
self.size=size
self.speed=speed
def draw(self):
s=self.size
self.rect=pygame.Rect(W/2-s/2, H/2-s/2, self.size, self.size)
pygame.draw.rect(screen, self.col, self.rect)
def move(self, x, y):
x *= self.speed
y *= self.speed
polygon = self.createPolygon(self.x + x, self.y + y)
if not collideRectPolygon(self.rect, polygon):
self.x += x
self.y += y
p=player(red)
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
p.bg()
keys=pygame.key.get_pressed()
if keys[pygame.K_a]: p.move(1, 0)
if keys[pygame.K_d]: p.move(-1, 0)
if keys[pygame.K_w]: p.move(0, 1)
if keys[pygame.K_s]: p.move(0, -1)
p.draw()
pygame.display.update()
pygame.quit()

Alternatively define the rect with 4 lines and detect if you are above or bellow those lines, and in the 2 frames where the rect is perfectly aligned you just use normal rect collision.

Related

Is there a way to find if a circle is in a complex polygon? [duplicate]

So I am trying to make an among us type game with pygame. I just started, so I don't have much of anything and am working on the map right now. However, one thing I'm struggling with is the collision logic. The map has an elongated octagon shape for now, but I think no matter the shape I will use something like a pygame polygon. When I ran the code I have now, which checks for a collision between my player (pygame rectangle) and the walls (pygame polygon) it says:
TypeError: Argument must be rect style object
I've figured out this is because of the pygame polygon returning a rectangle, but not being classified that way in the collision checker. I have tried a library called collision, and credit to the collision detection for giving a great effort, but the player was still able to glitch through the walls. Sidenote: I saved the code where I used this library if anyone wants to see it and maybe improve upon my faults.
Anyway, to boil it all down:
I need a way to detect collisions (really, really preferably in pygame) between polygons and rectangles
Thank you for any help you can give and if you have a question/request please leave a comment.
Heres my code:
import pygame
pygame.init()
W, H=500, 500
screen = pygame.display.set_mode([500, 500])
running = True
bcg=(200, 200, 200)
red=(255, 0 ,0)
purp=(255, 0, 255)
wall=(100, 100, 100)
class player:
def bg(self):
screen.fill(bcg)
x,y=self.x,self.y
self.outer=(
(x,y),
(x+800, y),
(x+1200, y+200),
(x+1200, y+600),
(x+800, y+800),
(x, y+800),
(x-400, y+600),
(x-400, y+200),
(x,y),
(x, y+50),
(x-350, y+225),
(x-350, y+575),
(x, y+750),
(x+800, y+750),
(x+1150, y+575),
(x+1150, y+225),
(x+800, y+50),
(x, y+50)
)
pygame.draw.polygon(screen, wall, self.outer)
def __init__(self, color, size=20, speed=0.25):
self.x=0
self.y=0
self.col=color
self.size=size
self.speed=speed
def draw(self):
s=self.size
self.rect=pygame.Rect(W/2-s/2, H/2-s/2, self.size, self.size)
pygame.draw.rect(screen, self.col, self.rect)
def move(self, x, y):
x*=self.speed
y*=self.speed
if not self.rect.colliderect(self.outer):
self.x+=x
self.y+=y
p=player(red)
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
p.bg()
keys=pygame.key.get_pressed()
if keys[pygame.K_a]: p.move(1, 0)
if keys[pygame.K_d]: p.move(-1, 0)
if keys[pygame.K_w]: p.move(0, 1)
if keys[pygame.K_s]: p.move(0, -1)
p.draw()
pygame.display.update()
pygame.quit()
Write a function collideLineLine that test if to line segments are intersecting. The algorithm to this function is explained in detail in the answer to the question pygame, detecting collision of a rotating rectangle:
def collideLineLine(l1_p1, l1_p2, l2_p1, l2_p2):
# normalized direction of the lines and start of the lines
P = pygame.math.Vector2(*l1_p1)
line1_vec = pygame.math.Vector2(*l1_p2) - P
R = line1_vec.normalize()
Q = pygame.math.Vector2(*l2_p1)
line2_vec = pygame.math.Vector2(*l2_p2) - Q
S = line2_vec.normalize()
# normal vectors to the lines
RNV = pygame.math.Vector2(R[1], -R[0])
SNV = pygame.math.Vector2(S[1], -S[0])
RdotSVN = R.dot(SNV)
if RdotSVN == 0:
return False
# distance to the intersection point
QP = Q - P
t = QP.dot(SNV) / RdotSVN
u = QP.dot(RNV) / RdotSVN
return t > 0 and u > 0 and t*t < line1_vec.magnitude_squared() and u*u < line2_vec.magnitude_squared()
Write the function colideRectLine that test if a rectangle and a line segment is intersecting. To test if a line segment intersects a rectangle, you have to test if it intersect any of the 4 sides of the rectangle:
def colideRectLine(rect, p1, p2):
return (collideLineLine(p1, p2, rect.topleft, rect.bottomleft) or
collideLineLine(p1, p2, rect.bottomleft, rect.bottomright) or
collideLineLine(p1, p2, rect.bottomright, rect.topright) or
collideLineLine(p1, p2, rect.topright, rect.topleft))
The next function collideRectPolygon tests if a polygon and a rectangle are intersecting. This can be achieved by testing each line segment on the polygon against the rectangle in a loop:
def collideRectPolygon(rect, polygon):
for i in range(len(polygon)-1):
if colideRectLine(rect, polygon[i], polygon[i+1]):
return True
return False
Finally you can use collideRectPolygon for the collision test. Note, however, that for the test you need to use the polygon as if the player were moving:
class player:
def bg(self):
screen.fill(bcg)
self.outer = self.createPolygon(self.x, self.y)
pygame.draw.polygon(screen, wall, self.outer)
def createPolygon(self, x, y):
return [
(x,y), (x+800, y), (x+1200, y+200), (x+1200, y+600),
(x+800, y+800), (x, y+800), (x-400, y+600), (x-400, y+200),
(x,y), (x, y+50), (x-350, y+225), (x-350, y+575),
(x, y+750), (x+800, y+750), (x+1150, y+575), (x+1150, y+225),
(x+800, y+50),(x, y+50)]
# [...]
def move(self, x, y):
x *= self.speed
y *= self.speed
polygon = self.createPolygon(self.x + x, self.y + y)
if not collideRectPolygon(self.rect, polygon):
self.x += x
self.y += y
See also Collision and Intersection - Rectangle and polygon
Minimal example:
repl.it/#Rabbid76/PyGame-CollisionPolygonRectangle
Complete example:
import pygame
pygame.init()
W, H=500, 500
screen = pygame.display.set_mode([500, 500])
running = True
bcg=(200, 200, 200)
red=(255, 0 ,0)
purp=(255, 0, 255)
wall=(100, 100, 100)
def collideLineLine(l1_p1, l1_p2, l2_p1, l2_p2):
# normalized direction of the lines and start of the lines
P = pygame.math.Vector2(*l1_p1)
line1_vec = pygame.math.Vector2(*l1_p2) - P
R = line1_vec.normalize()
Q = pygame.math.Vector2(*l2_p1)
line2_vec = pygame.math.Vector2(*l2_p2) - Q
S = line2_vec.normalize()
# normal vectors to the lines
RNV = pygame.math.Vector2(R[1], -R[0])
SNV = pygame.math.Vector2(S[1], -S[0])
RdotSVN = R.dot(SNV)
if RdotSVN == 0:
return False
# distance to the intersection point
QP = Q - P
t = QP.dot(SNV) / RdotSVN
u = QP.dot(RNV) / RdotSVN
return t > 0 and u > 0 and t*t < line1_vec.magnitude_squared() and u*u < line2_vec.magnitude_squared()
def colideRectLine(rect, p1, p2):
return (collideLineLine(p1, p2, rect.topleft, rect.bottomleft) or
collideLineLine(p1, p2, rect.bottomleft, rect.bottomright) or
collideLineLine(p1, p2, rect.bottomright, rect.topright) or
collideLineLine(p1, p2, rect.topright, rect.topleft))
def collideRectPolygon(rect, polygon):
for i in range(len(polygon)-1):
if colideRectLine(rect, polygon[i], polygon[i+1]):
return True
return False
class player:
def bg(self):
screen.fill(bcg)
self.outer = self.createPolygon(self.x, self.y)
pygame.draw.polygon(screen, wall, self.outer)
def createPolygon(self, x, y):
return [
(x,y), (x+800, y), (x+1200, y+200), (x+1200, y+600),
(x+800, y+800), (x, y+800), (x-400, y+600), (x-400, y+200),
(x,y), (x, y+50), (x-350, y+225), (x-350, y+575),
(x, y+750), (x+800, y+750), (x+1150, y+575), (x+1150, y+225),
(x+800, y+50),(x, y+50)]
def __init__(self, color, size=20, speed=0.25):
self.x=0
self.y=0
self.col=color
self.size=size
self.speed=speed
def draw(self):
s=self.size
self.rect=pygame.Rect(W/2-s/2, H/2-s/2, self.size, self.size)
pygame.draw.rect(screen, self.col, self.rect)
def move(self, x, y):
x *= self.speed
y *= self.speed
polygon = self.createPolygon(self.x + x, self.y + y)
if not collideRectPolygon(self.rect, polygon):
self.x += x
self.y += y
p=player(red)
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
p.bg()
keys=pygame.key.get_pressed()
if keys[pygame.K_a]: p.move(1, 0)
if keys[pygame.K_d]: p.move(-1, 0)
if keys[pygame.K_w]: p.move(0, 1)
if keys[pygame.K_s]: p.move(0, -1)
p.draw()
pygame.display.update()
pygame.quit()
Alternatively define the rect with 4 lines and detect if you are above or bellow those lines, and in the 2 frames where the rect is perfectly aligned you just use normal rect collision.

How do I use keyboard inputs in pygame?

so I am making a game where this character(a circle) has to pop balloons falling from the sky to get points. But I am having trouble making my character move in the first place.
Code:
import pygame
from pygame.locals import *
pygame.init()
#Variables
white = (255, 255, 255)
blue = (70,130,180)
black = (0,0,0)
x = 400
y = 450
#screen stuff
screenwidth = 800
screenheight = 600
screen = pygame.display.set_mode((screenwidth, screenheight))
pygame.display.set_caption("Balloon Game!")
#end of screen stuff
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
exit()
# Draw Character
character = pygame.draw.circle(screen, (blue), (x, y), 50, 50)
#End of Drawing Character
# Making Arrow Keys
keyPressed = pygame.key.get_pressed()
if keyPressed[pygame.K_LEFT]:
character.x -= 1
if keyPressed[pygame.K_RIGHT]:
character.x += 1
pygame.display.update()
I would appreciate it if someone could tell me why it wasn't
working with a fixed code. Thanks!
pygame.draw.circle returns the bounding rectangle (pygame.Rect object) of the circle. However the center of the circle is always (x, y). Therefore you need to change x instead of character.x:
while True:
# [...]
pygame.draw.circle(screen, (blue), (x, y), 50, 50)
keyPressed = pygame.key.get_pressed()
if keyPressed[pygame.K_LEFT]:
x -= 1
if keyPressed[pygame.K_RIGHT]:
x += 1
This code can even be simplified:
while True:
# [...]
pygame.draw.circle(screen, (blue), (x, y), 50, 50)
keyPressed = pygame.key.get_pressed()
x += (keyPressed[pygame.K_RIGHT] - keyPressed[pygame.K_LEFT])

Why is the sprite not moving? [duplicate]

This question already has answers here:
Why is my PyGame application not running at all?
(2 answers)
Closed 1 year ago.
I am currently programming a pygame game where you move a spaceship around the screen. Currently, I have got to the part where I made the spaceship and tried to make it move. However, when I try to move the spaceship around, the spaceship doesn't move!
Here is my current code:
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 500))
screen.fill((255, 255, 255))
class Spaceship(pygame.sprite.Sprite):
def __init__(self, s, x, y):
pygame.sprite.Sprite.__init__(self)
self.screen = s
self.x, self.y = x, y
self.image = pygame.image.load("C:/eqodqfe/spaceship.png")
self.image = pygame.transform.scale(self.image, (175, 175))
self.rect = self.image.get_rect()
self.rect.center = (self.x, self.y)
def update(self):
self.rect.center = (self.x, self.y)
spaceship = Spaceship(screen, 400, 400)
screen.blit(spaceship.image, spaceship.rect)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
key = pygame.key.get_pressed()
if key[pygame.K_a]:
spaceship.x -= 5
elif key[pygame.K_d]:
spaceship.x += 5
elif key[pygame.K_w]:
spaceship.y += 5
elif key[pygame.K_s]:
spaceship.y -= 5
spaceship.update()
pygame.display.update()
What is wrong with my current code?
You have to draw the Sprties in the application loop:
clock = pygame.time.Clock()
running = True
while running:
# handle events
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
key = pygame.key.get_pressed()
if key[pygame.K_a]:
spaceship.x -= 5
elif key[pygame.K_d]:
spaceship.x += 5
elif key[pygame.K_w]:
spaceship.y -= 5
elif key[pygame.K_s]:
spaceship.y += 5
# update the position of the object
spaceship.update()
# clear the display
screen.fill((255, 255, 255))
# draw the object
screen.blit(spaceship.image, spaceship.rect)
# update the display
pygame.display.update()
# limit frames per second
clock.tick(60)
The typical PyGame application loop has to:
handle the events by either pygame.event.pump() or pygame.event.get().
update the game states and positions of objects dependent on the input events and time (respectively frames)
clear the entire display or draw the background
draw the entire scene (blit all the objects)
update the display by either pygame.display.update() or pygame.display.flip()
limit frames per second to limit CPU usage
Furthermore I suggest to use a pygame.sprite.Group:
pygame.sprite.Group.draw() and pygame.sprite.Group.update() are methods which are provided by pygame.sprite.Group.
The former delegates the to the update mehtod of the contained pygame.sprite.Sprites - you have to implement the method. See pygame.sprite.Group.update():
Calls the update() method on all Sprites in the Group [...]
The later uses the image and rect attributes of the contained pygame.sprite.Sprites to draw the objects - you have to ensure that the pygame.sprite.Sprites have the required attributes. See pygame.sprite.Group.draw():
Draws the contained Sprites to the Surface argument. This uses the Sprite.image attribute for the source surface, and Sprite.rect. [...]
spaceship = Spaceship(screen, 400, 400)
all_sprites = pygame.sprite.Group()
all_sprites.add(spaceship)
clock = pygame.time.Clock()
running = True
while running:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
key = pygame.key.get_pressed()
if key[pygame.K_a]:
spaceship.x -= 5
elif key[pygame.K_d]:
spaceship.x += 5
elif key[pygame.K_w]:
spaceship.y += 5
elif key[pygame.K_s]:
spaceship.y -= 5
all_sprites.update()
screen.fill((255, 255, 255))
all_sprites.draw(screen)
pygame.display.update()

Pygame, making pong

Can anyone help me out, I can't figure out why the code does not run. I feel like it is a really stupid mistake any maybe a better, second pair of eyes could lend me a hand?
It's saying my post is mainly code so I need to add some 'description', so you don't have to read this, I'm just doing this so it will let me post it.
#my pong game
import pygame, sys
pygame.init()
#global variables
screen_width = 1000
screen_height = 800
game_over = False
ball_speed_x = 15
ball_speed_y = 15
ball_width = 15
ball_height = 15
ball_color = (255,0,0)
ball_posx = int(screen_width/2 - (ball_width / 2))
ball_posy = int(screen_height/2 - (ball_width / 2))
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption('PONG')
#player blueprint
class Player:
def __init__(self, x, y):
self.x = x
self.y = y
self.height = 100
self.width = 20
self.vel = 15
self.color = (255,0,0)
self.player = pygame.Rect(self.x, self.y, self.width, self.height)
def draw(self):
pygame.draw.rect(screen, self.color, self.player)
#creating objects
player1 = Player(10, int(screen_height/2 - 5))
player2 = Player(screen_width - 30, int(screen_height/2 - 5))
ball = pygame.Rect(ball_posx, ball_posy, ball_height, ball_width)
def player_animation():
keys = pygame.key.get_pressed()
if keys[pygame.K_UP]:
player2.y -= player2.vel
if keys[pygame.K_DOWN]:
player2.y += player2.vel
if keys[pygame.K_w]:
player1.y -= player1.vel
if keys[pygame.K_s]:
player1.y += player1.vel
def ball_animation():
global ball_posx, ball_width, ball_height, ball_posy, ball_posx, ball_speed_x, ball_speed_y, screen_width, screen_height
if ball.right >= screen_width - 5:
ball_speed_x *= -1
if ball.left <= 10:
ball_speed_x *= -1
if ball.bottom >= screen_height - 5:
ball_speed_y *= -1
if ball.top <= 5:
ball_speed_y *= -1
if player1.player.colliderect(ball):
ball_speed_x *= -1
if player2.player.colliderect(ball):
ball_speed_x *= -1
ball_posx += ball_speed_x
ball_posy += ball_speed_y
while not game_over:
pygame.time.delay(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_over = True
screen.fill((0,0,0))
ball_animation()
player_animation()
pygame.draw.ellipse(screen, (255,0,0), ball)
player1.draw()
player2.draw()
pygame.display.update()
pygame.quit()
sys.exit()
Everything in your code works fine apart from the draw functions. In the player class, you create the player's rectangle at the beginning and it's x and y values aren't changed throughout the game, you're simply changing the variable that was used to create the rectangle when instead you should be changing the rectangle's actual x and y variables. This can be fixed by adding these two lines in the player class:
def draw(self):
self.player.y = self.y
self.player.x = self.x
pygame.draw.rect(screen, self.color, self.player)
The self.player.y will update the rectangle's y value to the player's current value so the rectangle is drawn in the right place.
The ball has the same problem, the eclipse is created once but it's x and y values are never changed.
Instead of writing:
ball_posx += ball_speed_x
ball_posy += ball_speed_y
Do :
ball.x += ball_speed_x
ball.y += ball_speed_y
which directly access the eclipses x and y values so it can be redrawn in the right place. I made the changes stated here and everything started moving fine.

colliderect() triggers unexpectedly

I am making a classic Snake game. I want to draw a green rectangle (green_rect), and whenever my snake touches it, that green rectangle moves to a new, random location. When I run colliderect() on the Worm's surface, it always returns True for some reason, though, even if the worm doesn't touch the rectangle.
So far I've tried:
width = 640
height = 400
screen = pygame.display.set_mode((width, height))
green = pygame.Surface((10,10)) #it's the rectangle
green.fill((0,255,0))
green_rect = pygame.Rect((100,100), green.get_size())
# Our worm.
w = Worm(screen, width/2, height/2, 100) # I have a class called Worm, described later...
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
w.key_event(event) # direction control method in the Worm class
if green_rect.colliderect(w.rect): # w.rect is coming from Worm class: self.rect=self.surface.get_rect()
# surface variable is 100 coming from: w = Worm(screen, width/2, height/2, 100) -- it describes the worm's length in segments / pixels.
green_rect.x, green_rect.y = (random.randrange(420),random.randrange(300))
screen.fill((0, 0, 0))
screen.blit(green, green_rect)
w.move() #function for movement in Worm class
w.draw() #function for drawing the snake in Worm class
But this doesn't work, the green rectangle is moving uncontrollably, even if I don't touch it with the snake. colliderect must not be working since the x-y coordinates of the green rectangle are changing without the snake actually touching it. It should only change location when the snake touches the green rectangle.
I didn't show all of my code, because its little long. If it necessary I can
write out Class Worm as well. Well rect method is not working on
lists, so I couldn't figure how to fix this problem.
Edit: Also I want to know how to resize the snake's segments, since my default size is a pixel (dot). I want it bigger so if it hits a big rectangle, even on a corner of the rectangle or along a side, rectangle will still move.
Edit-2: Here is my Worm class:
class Worm():
def __init__(self, surface, x, y, length):
self.surface = surface
self.x = x
self.y = y
self.length = length
self.dir_x = 0
self.dir_y = -1
self.body = []
self.crashed = False
def key_event(self, event):
""" Handle key events that affect the worm. """
if event.key == pygame.K_UP:
self.dir_x = 0
self.dir_y = -1
elif event.key == pygame.K_DOWN:
self.dir_x = 0
self.dir_y = 1
elif event.key == pygame.K_LEFT:
self.dir_x = -1
self.dir_y = 0
elif event.key == pygame.K_RIGHT:
self.dir_x = 1
self.dir_y = 0
def move(self):
""" Move the worm. """
self.x += self.dir_x
self.y += self.dir_y
if (self.x, self.y) in self.body:
self.crashed = True
self.body.insert(0, (self.x, self.y))
if len(self.body) > self.length:
self.body.pop()
def draw(self):
for x, y in self.body:
self.surface.set_at((int(x),int(y)), (255, 255, 255))
I think I understand what's going on here:
Your class Worm consists of an object that contains a list of xy-tuples, one for each pixel it occupies. The 'worm' creeps across the playfield, adding the next pixel in its path to that list and discarding excess pixel locations when it's reached its maximum length. But Worm never defines its own rect and Worm.surface is actually the surface of the display area.
The reason why your target green_rect hops around all the time is because w.surface refers to screen, not the Worm's own pixel area. w.surface.get_rect() is therefore returning a rectangle for the entire display area, as suspected. The green rectangle has nowhere to go that doesn't collide with that rect.
With that in mind, here's a solution for you:
# in class Worm...
def __init__(self, surface, x, y, length):
self.surface = surface
self.x = x
self.y = y
self.length = length
self.dir_x = 0
self.dir_y = -1
self.body = []
self.crashed = False
self.rect = pygame.Rect(x,y,1,1) # the 1,1 in there is the size of each worm segment!
# ^ Gives the 'head' of the worm a rect to collide into things with.
#...
def move(self):
""" Move the worm. """
self.x += self.dir_x
self.y += self.dir_y
if (self.x, self.y) in self.body:
self.crashed = True
self.body.insert(0, (self.x, self.y))
if len(self.body) > self.length:
self.body.pop()
self.rect.topleft = self.x, self.y
# ^ Add this line to move the worm's 'head' to the newest pixel.
Remember to import pygame, random, sys at the top of your code and throw in pygame.display.flip() (or update, if you have a list of updating rects) at the end and you should be set.
As it stands, your code for a worm that crashes into itself runs, but has no effect.
One more suggestion: you may want to rename Worm.surface to something like Worm.display_surface, since pygame has a thing called Surface as well, and it might make it a bit easier for others to understand if the two were more clearly defined.
If you're looking for a more sophisticated worm consisting of a list of Rects, that's also possible. Just replace the xy-tuples in the Worm.body list with rects and replace if (self.x, self.y) in self.body: with if self.rect.collidelist(self.body) != -1: and it ought to work the same way. (collidelist(<list of Rects>) returns the index of the first colliding rect in that list, or -1 if there are none.)
Just make sure you also change the self.rect in class Worm so that it's more like
`self.rect = pygame.Rect(x,y, segment_width, segment_height)`
...and adjust self.dir_x and self.dir_y so that they're equal to +/- segment_width and segment_height, respectively. You'll need to fill each segment or use pygame.draw.rects instead of using set_at in Worm.draw(), as well.
UPDATE: Variable-size worm segments
Here's some code that uses Rects for the worm's body instead of xy-tuples. I have it set up so that the worm will define the size of each segment during initialization, then move forward in steps of that size (depending the axis of travel). Be aware the worm moves very quickly, has nothing stopping it from escaping the play area, and is quite long at 100 units length!
Here's my solution, in any case-- First is the revised class Worm code. I've marked altered lines with comments, including the lines that are new as of the original answer.
class Worm(pygame.sprite.Sprite): # MODIFIED CONTENTS!
def __init__(self, surface, x, y, length, (seg_width, seg_height)): # REVISED
self.surface = surface
self.x = x
self.y = y
self.length = length
self.dir_x = 0
self.dir_y = -seg_width # REVISED
self.body = []
self.crashed = False
self.rect = pygame.Rect(x,y,seg_width,seg_height) # NEW/REVISED
self.segment = pygame.Rect(0,0,seg_width,seg_height) # NEW
def key_event(self, event):
""" Handle key events that affect the worm. """
if event.key == pygame.K_UP:
self.dir_x = 0
self.dir_y = -self.segment.height # REVISED
elif event.key == pygame.K_DOWN:
self.dir_x = 0
self.dir_y = self.segment.height # REVISED
elif event.key == pygame.K_LEFT:
self.dir_x = -self.segment.width # REVISED
self.dir_y = 0
elif event.key == pygame.K_RIGHT:
self.dir_x = self.segment.width # REVISED
self.dir_y = 0
def move(self):
""" Move the worm. """
self.x += self.dir_x
self.y += self.dir_y
self.rect.topleft = self.x, self.y # NEW/MOVED
if self.rect.collidelist(self.body) != -1: # REVISED
self.crashed = True
print "CRASHED!"
new_segment = self.segment.copy() # NEW
self.body.insert(0, new_segment.move(self.x, self.y)) # REVISED
if len(self.body) > self.length:
self.body.pop()
def draw(self): # REVISED
for SEGMENT in self.body: # REPLACEMENT
self.surface.fill((255,255,255), SEGMENT) # REPLACEMENT
...you'll have to revise the arguments when you assign w, too, to include your desired worm segment dimensions. In this case, I've closen 4 pixels wide by 4 pixels tall.
w = Worm(screen, width/2, height/2, 100, (4,4)) # REVISED
That's what I'd do. Note that you can assign whatever segment size you like (as long as each dimension is an integer greater than zero), even using single-pixel or rectangular (which is, 'non-square') segments if you like.