Why do bodies in pymunk keep colliding without actually touching each other even after the position and body shape is correctly set?
You will see from my code below that the bird keeps colliding with the ball without actually touching the ball.
import pygame, pymunk
pygame.init()
WIDTH, HEIGHT = 900, 500
win = pygame.display.set_mode((WIDTH,HEIGHT))
pygame.display.set_caption("Flappy OBJ")
color = (255,255,255)
win.fill(color)
fps = 120
fb_img = pygame.image.load("images/flappy-bird.png")#.convert()
fb_img = pygame.transform.rotate(pygame.transform.scale(fb_img, (30,20)), 0)
ball_img = pygame.image.load("images/ball.png")#.convert()
ball_img = pygame.transform.rotate(pygame.transform.scale(ball_img, (60,60)), 0)
def bird_body():
body = pymunk.Body(1, 100, body_type=pymunk.Body.DYNAMIC)
body.position = (50, 0)
shape = pymunk.Poly.create_box(body, (30,20))
space.add(body, shape)
return shape
def ball_body():
body = pymunk.Body(1, 100, body_type = pymunk.Body.KINEMATIC)
body.position = (500, 300)
body.velocity = (-25,0)
shape = pymunk.Circle(body, 60)
shape.color = (0,255,255, 128)
space.add(body, shape)
return shape
space = pymunk.Space()
space.gravity = (0, 20)
fb_body = bird_body()
b_body = ball_body()
def draw_window():
global scroll
win.fill(color)
space.step(0.03)
win.blit(ball_img, (b_body.body.position.x, b_body.body.position.y))
win.blit(fb_img, (fb_body.body.position.x, fb_body.body.position.y))
pygame.display.update()
def main():
w_was_down = True
clock = pygame.time.Clock()
run = True
while run:
clock.tick(fps)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys_pressed = pygame.key.get_pressed()
if keys_pressed[pygame.K_w]:
if not w_was_down:
fb_body.body.apply_impulse_at_local_point((0,-50))
w_was_down = True
else:
w_was_down = False
draw_window()
pygame.quit()
main()
Tap w to move the bird up. some more details some more details some more details
The 2nd argument of pymunk.Circle is the radius, but not the diameter:
shape = pymunk.Circle(body, 60)
shape = pymunk.Circle(body, 30)
And the position of the pymuk object is the center of the object:
win.blit(ball_img, (b_body.body.position.x, b_body.body.position.y))
win.blit(fb_img, (fb_body.body.position.x, fb_body.body.position.y))
win.blit(ball_img, (b_body.body.position.x-30, b_body.body.position.y-30))
win.blit(fb_img, (fb_body.body.position.x-15, fb_body.body.position.y-10))
Related
Right now, my game blits all the images in random positions correctly and also gets the rect of the images correctly, but I can´t figure out how to use colliderect to make sure the images don´t overlap. How could it work for my code?
Also I´m trying to make the first text fade out and I don´t know why it doesn´t work for me.
Here is the code:
class GAME1:
def __init__(self, next_scene):
self.background = pygame.Surface(size)
# Create an array of images with their rect
self.images = []
self.rects = []
self.imagenes1_array = ['autobus.png','coche.png','barco.png','autobus2.png','grua.png','bici.png']
for i in self.imagenes1_array:
# We divide in variables so we can then get the rect of the whole Img (i2)
i2 = pygame.image.load(i)
self.images.append(i2)
s = pygame.Surface(i2.get_size())
r = s.get_rect()
# Trying to use colliderect so it doesnt overlap
if pygame.Rect.colliderect(r,r) == True:
x = random.randint(300,1000)
y = random.randint(200,700)
self.rects.append(r)
def start(self, gamestate):
self.gamestate = gamestate
for rect in self.rects:
# Give random coordinates (we limit the dimensions (x,y))
x = random.randint(300,1000)
y = random.randint(200,700)
rect.x = x
rect.y = y
def draw(self,screen):
self.background = pygame.Surface(size)
font = pygame.font.SysFont("comicsansms",70)
# First half (Show image to remember)
text1 = font.render('¡A recordar!',True, PURPLE)
text1_1 = text1.copy()
# This surface is used to adjust the alpha of the txt_surf.
alpha_surf = pygame.Surface(text1_1.get_size(), pygame.SRCALPHA)
alpha = 255 # The current alpha value of the surface.
if alpha > 0:
alpha = max(alpha-4, 0)
text1_1 = text1.copy()
alpha_surf.fill((255, 255, 255, alpha))
text1_1.blit(alpha_surf, (0,0), special_flags = pygame.BLEND_RGBA_MULT)
screen.blit(text1_1, (600,50))
# Second half (Show all similar images)
text2 = font.render('¿Cuál era el dibujo?',True, PURPLE)
#screen.blit(text2, (500,50))
for i in range(len(self.images)):
#colliding = pygame.Rect.collidelistall(self.rects)
screen.blit(self.images[i], (self.rects[i].x, self.rects[i].y))
def update(self, events, dt):
for event in events:
if event.type == pygame.MOUSEBUTTONDOWN:
for rect in self.rects:
if rect.collidepoint(event.pos):
print('works!')
Use collidelist() to test test if one rectangle in a list intersects:
for i in self.imagenes1_array:
s = pygame.image.load(i)
self.images.append(s)
r = s.get_rect()
position_set = False
while not position_set:
r.x = random.randint(300,1000)
r.y = random.randint(200,700)
margin = 10
rl = [rect.inflate(margin*2, margin*2) for rect in self.rects]
if len(self.rects) == 0 or r.collidelist(rl) < 0:
self.rects.append(r)
position_set = True
See the minimal example, that uses the algorithm to generate random not overlapping rectangles:
import pygame
import random
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
def new_recs(rects):
rects.clear()
for _ in range(10):
r = pygame.Rect(0, 0, random.randint(30, 40), random.randint(30, 50))
position_set = False
while not position_set:
r.x = random.randint(10, 340)
r.y = random.randint(10, 340)
margin = 10
rl = [rect.inflate(margin*2, margin*2) for rect in rects]
if len(rects) == 0 or r.collidelist(rl) < 0:
rects.append(r)
position_set = True
rects = []
new_recs(rects)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == pygame.KEYDOWN:
new_recs(rects)
window.fill(0)
for r in rects:
pygame.draw.rect(window, (255, 0, 0), r)
pygame.display.flip()
pygame.quit()
exit()
This question already has answers here:
How do I detect collision in pygame?
(5 answers)
Collision between masks in pygame
(1 answer)
Closed 1 year ago.
So, I'm making an app/game in python using pygame module, and my problem is that, I cannot find a way to check if a sprite is entirely touching another sprite or not.
What I mean is
if (sprite is touching anything else that some white rectangle):
do some code
Here is my messy code :
main.py :
import pygame, Classes, Groups, Images, random, time
pygame.init()
pygame.font.init()
# App Variables
run_bool = False
# Fonts
ARAL_20_ITALIC = pygame.font.SysFont('Arial' , 20, italic=True)
# Colors
BG = (30, 30, 30)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
# Window
size = (800, 600)
scr = pygame.display.set_mode(size)
pygame.display.set_caption('Simulation by Cold Fire (0.1)')
# App Loop
while True:
time.sleep(0.01)
# Graphics
scr.fill(BG)
"""TERRAIN"""
terrain_hitbox = pygame.draw.rect(scr, WHITE, (300, 100, size[0] - 350, size[1] - 150))
pygame.draw.rect(scr, BLACK, (300, 100, size[0] - 350, size[1] - 150), width=10)
"""SUBJECTS"""
for subject in Groups.G_Subject:
if run_bool == True:
subject.update__()
scr.blit(subject.img, subject.rect)
"""GUI"""
pygame.draw.line(scr, WHITE, (200, 0), (200, size[1]), width=3)
scr.blit(ARAL_20_ITALIC.render('Subjects' ,False, WHITE), (30, 10))
add_hitbox = scr.blit(Images.add_img.convert_alpha(), (30, 50))
remove_hitbox = scr.blit(Images.remove_img.convert_alpha(), (30, 90))
scr.blit(ARAL_20_ITALIC.render(f'Subjects: {len(Groups.G_Subject)}' , False, WHITE), (30, 130))
if run_bool == False:
run_hitbox = scr.blit(Images.run_img.convert_alpha(), (210, size[1] - 40))
else:
run_hitbox = scr.blit(Images.stop_img.convert_alpha(), (210, size[1] - 40))
# Updating Screen
pygame.display.flip()
# Events
for event in pygame.event.get():
# Quitting App
if event.type == pygame.QUIT:
pygame.quit()
quit()
# Clicking
if event.type == pygame.MOUSEBUTTONDOWN:
mouse = pygame.mouse.get_pos()
if add_hitbox.collidepoint(mouse[0], mouse[1]):
rand_x = random.randint(terrain_hitbox[0], terrain_hitbox[0] + terrain_hitbox[2])
rand_y = random.randint(terrain_hitbox[1], terrain_hitbox[1] + terrain_hitbox[3])
Classes.Subject(rand_x, rand_y, Images.subject0_img.convert_alpha())
if remove_hitbox.collidepoint(mouse[0], mouse[1]) and not 1 > len(Groups.G_Subject):
Groups.G_Subject.remove(random.choice(Groups.G_Subject.sprites()))
if run_hitbox.collidepoint(mouse[0], mouse[1]):
if run_bool == True:
run_bool = False
else:
run_bool = True
Classes.py :
import pygame, Groups, random, math
class Subject(pygame.sprite.Sprite):
def __init__(self, x, y, img=pygame.image.load('assets/img/subject0.png').convert_alpha(pygame.display.set_mode((800, 600))), speed=5):
super().__init__()
self.img = img
self.rect = self.img.get_rect()
self.rect.x = x
self.rect.y = y
self.oldx = self.rect.x
self.oldy = self.rect.y
self.speed = speed
Groups.G_Subject.add(self)
def calculat_new_xy(self, old_xy, speed, angle_in_radians):
new_x = old_xy.x + (speed * math.cos(angle_in_radians))
new_y = old_xy.y + (speed * math.sin(angle_in_radians))
return new_x, new_y
def update__(self):
self.oldx, self.oldy = self.rect.x, self.rect.y
self.rect.x, self.rect.y = self.calculat_new_xy(self.rect, self.speed, random.randint(1, 360))
Images.py :
import pygame
add_img = pygame.transform.scale(pygame.image.load('assets/img/gui/add.png'), (90, 30))
remove_img = pygame.transform.scale(pygame.image.load('assets/img/gui/remove.png'), (90, 30))
subject0_img = pygame.image.load('assets/img/subject0.png')
run_img = pygame.transform.scale(pygame.image.load('assets/img/gui/run.png'), (90, 30))
stop_img = pygame.transform.scale(pygame.image.load('assets/img/gui/stop.png'), (90, 30))
Groups.py :
import pygame
G_Subject = pygame.sprite.Group()
I do know my code is a mess, so if you don't wanna help, it's ok ! Thx in advance :D
For some reason, this program crashes after only a second or so of running this. Funnily enough, changing the turnAngle or fps affects how long it lasts before crashing. The error it throws is a
ValueError: subsurface rectangle outside of surface area on line 29 / line 9. Any ideas?
import pygame as pg
def rot_center(image, angle):
orig_rect = image.get_rect()
rot_image = pg.transform.rotate(image, angle)
rot_rect = orig_rect.copy()
rot_rect.center = rot_image.get_rect().center
rot_image = rot_image.subsurface(rot_rect).copy()
return rot_image
pg.init()
mainDisplay = pg.display.set_mode([600, 376])
imgSplashBG = pg.image.load('guiAssets/splash/BG.png')
imgSplashFG = pg.image.load('guiAssets/splash/FG.png')
imgSplashRedCircle = pg.image.load('guiAssets/splash/redCircle.png')
imgSplashGreenCircle = pg.image.load('guiAssets/splash/greenCircle.png')
turnAngle = 10
frame = 0
clock = pg.time.Clock()
crashed = False
while not crashed:
frame += 1
mainDisplay.blit(imgSplashBG, (0, 0))
mainDisplay.blit(rot_center(imgSplashGreenCircle, ((frame % 360) * 10)), (-189, -189))
mainDisplay.blit(rot_center(imgSplashRedCircle, ((frame % 360) * 10)), (453, 230))
mainDisplay.blit(imgSplashFG, (0, 0))
pg.display.flip()
clock.tick(30)
See How do I rotate an image around its center using PyGame?
Furthermore you have to handle the events in the application loop. See pygame.event.get() respectively pygame.event.pump():
For each frame of your game, you will need to make some sort of call to the event queue. This ensures your program can internally interact with the rest of the operating system.
For instance:
import pygame as pg
def rot_center(image, angle, center):
rotated_image = pg.transform.rotate(image, angle)
new_rect = rotated_image.get_rect(center = center)
return rotated_image, new_rect
pg.init()
mainDisplay = pg.display.set_mode([600, 376])
imgSplashBG = pg.image.load('guiAssets/splash/BG.png')
imgSplashFG = pg.image.load('guiAssets/splash/FG.png')
imgSplashRedCircle = pg.image.load('guiAssets/splash/redCircle.png')
imgSplashGreenCircle = pg.image.load('guiAssets/splash/greenCircle.png')
turnAngle = 10
frame = 0
clock = pg.time.Clock()
run = True
crashed = False
while not crashed and run:
for event in pg.event.get():
if event.type == pg.QUIT:
run = False
frame += 1
angle = (frame*turnAngle) % 360
centerGreen = (189, 189)
centerRed = (453, 230)
mainDisplay.blit(imgSplashBG, (0, 0))
mainDisplay.blit( *rot_center(imgSplashGreenCircle, angle, centerGreen) )
mainDisplay.blit( *rot_center(imgSplashRedCircle, angle,centerRed) )
mainDisplay.blit(imgSplashFG, (0, 0))
pg.display.flip()
clock.tick(30)
pygame.quit()
Can you try to add a time variable before your loop 'while not'
Like this :
.....
frame = 0
clock = pg.time.Clock()
time = pg.time.get_ticks() #here
crashed = False
while not .....
is it possible to get the coordinates of the place where the sprites are colliding?, and if it more than one is it possible to get both?
Thanks a lot for the solvers
You can use the Pymunk physics library to get the contact points. Of course that means you'll have to familiarize yourself with this library first (it can be a bit difficult for beginners). You especially need to know how collision handlers, arbiters and callback functions work.
So you create a collision handler which checks if there were collisions between shapes of two specified collision types, e.g. handler = space.add_collision_handler(1, 1). When a collsion occurs the handler calls some callback functions (in the example I set handler.post_solve to the callback function) which receive an arbiter object as an argument that holds the collision data. Then you can extract the needed information from this arbiter, add it to a list or other data structure and use it in your main loop.
import sys
import random
import pygame as pg
import pymunk as pm
from pymunk import Vec2d
def flipy(p):
"""Convert chipmunk coordinates to pygame coordinates."""
return Vec2d(p[0], -p[1]+600)
class Ball(pg.sprite.Sprite):
def __init__(self, space, pos, mass=5, radius=30, elasticity=0.9):
super().__init__()
self.image = pg.Surface((60, 60), pg.SRCALPHA)
pg.draw.circle(self.image, pg.Color('royalblue'), (30, 30), radius)
self.rect = self.image.get_rect(center=pos)
# Set up the body and shape of this object and add them to the space.
inertia = pm.moment_for_circle(mass, 0, radius, (0, 0))
self.body = pm.Body(mass, inertia)
self.body.position = flipy(pos)
self.shape = pm.Circle(self.body, radius, (0, 0))
self.shape.elasticity = elasticity
# This type will be used by the collision handler.
self.shape.collision_type = 1
self.space = space
self.space.add(self.body, self.shape)
def update(self):
pos = flipy(self.body.position)
self.rect.center = pos
if pos.y > 600:
self.space.remove(self.body, self.shape)
self.kill()
def main():
screen = pg.display.set_mode((800, 600))
clock = pg.time.Clock()
all_sprites = pg.sprite.Group()
done = False
contact_points = []
def contact_callback(arbiter, space, data):
"""Append the contact point to the contact_points list."""
if arbiter.is_first_contact:
for contact in arbiter.contact_point_set.points:
contact_points.append(contact.point_a)
# Pymunk stuff.
space = pm.Space()
space.gravity = Vec2d(0, -900)
# This collision handler will be used to get the contact points.
# It checks if shapes with `collision_type` 1 collide with others
# that also have type 1.
handler = space.add_collision_handler(1, 1)
# After a collision is solved, the callback funtion will be called
# which appends the contact point to the `contact_points` list.
handler.post_solve = contact_callback
# Create some static lines.
static_lines = [
pm.Segment(space.static_body, (170, 200), (0, 300), .1),
pm.Segment(space.static_body, (170, 200), (500, 200), .1),
pm.Segment(space.static_body, (500, 200), (600, 260), .1),
]
for line in static_lines:
line.elasticity = 0.9
space.add(static_lines)
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.MOUSEBUTTONDOWN:
all_sprites.add(Ball(space, event.pos))
contact_points = []
space.step(1/60) # Update the physics space.
all_sprites.update()
screen.fill((60, 70, 80))
all_sprites.draw(screen) # Draw the sprite group.
# Draw static_lines.
for line in static_lines:
body = line.body
p1 = flipy(body.position + line.a.rotated(body.angle))
p2 = flipy(body.position + line.b.rotated(body.angle))
pg.draw.line(screen, pg.Color('gray68'), p1, p2, 5)
# Draw contact_points.
for point in contact_points:
x, y = flipy(point)
x, y = int(x), int(y)
pg.draw.circle(screen, pg.Color('orange'), (x, y), 8)
pg.display.flip()
clock.tick(60)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
sys.exit()
I have been trying to create movement for squares in pygame without controlling it, but I can't seem to do it. My goal was to make the red squares "bounce" the walls. I have cut out the collision in my game to prevent complications.
My code is:
import pygame
r=(255,0,0)
b=(0,0,255)
sort =(0,0,0)
class Block(pygame.sprite.Sprite):
def __init__(self, color=b, width= 40, height= 40):
super(Block, self).__init__()
self.image = pygame.Surface((width, height))
self.image.fill(color)
self.rect = self.image.get_rect()
self.origin_x=self.rect.centerx
self.origin_y=self.rect.centery
def set_position(self, x, y):
self.rect.x=x-self.origin_x
self.rect.y=y-self.origin_y
pygame.init()
pygame.display.set_caption("EsKappa!")
window_size =window_width, window_height = 400, 400
window = pygame.display.set_mode(window_size)
a=(0,201,0)
clock = pygame.time.Clock()
frames_per_second = 60
block_group = pygame.sprite.Group()
a_block= Block(b)
a_block.set_position(window_width/2 -30, window_height/2)
block1=Block()
block2=Block()
block3=Block()
block4=Block()
wall1 = Block(sort,20, 400 )
wall1.set_position(0,200)
wall2= Block(sort,400, 20 )
wall2.set_position(200,0)
wall3=Block(sort,20, 400 )
wall3.set_position(400,200)
wall4=Block(sort,400, 20 )
wall4.set_position(200,400)
block1= Block(r,50, 50 )#here draw
block1.set_position(80, 70)#here place
block2 = Block(r, 50, 40)
block2.set_position(270, 70)
block3 = Block(r,80, 20)
block3.set_position(280, 300)
block4 = Block(r, 30, 70)
block4.set_position(70, 250)
block_group.add(a_block,block1, block2, block3, block4, wall1,wall2,wall3,wall4)
h=pygame.time.get_ticks()/1000
font = pygame.font.SysFont("Times New Roman", 20)
font2 = pygame.font.SysFont("Times New Roman", 50)
text = font.render("Time", True, sort)
speed=[2,0] # here speed
#lukke funktion
running=True
while running:
event = pygame.event.wait ()
if event.type == pygame.QUIT or (event.type== pygame.KEYDOWN) and (event.key == pygame.K_ESCAPE): # Giver lukke funktion fra ikon og ESCAPE
running = False
if event.type == pygame.MOUSEMOTION :
mouse_position = pygame.mouse.get_pos()
a_block.set_position(mouse_position[0],mouse_position[1])
clock.tick(frames_per_second)
window.fill(a)
block1.move_ip(speed) #here error is here
screen.blit(block1) #here
pygame.display.update()
if targetpos[0]+target.get_width()>width or targetpos[0]<0: #here
speed[0]=-speed[0] #here
if pygame.sprite.collide_rect(a_block, block1) or pygame.sprite.collide_rect(a_block, block2) or pygame.sprite.collide_rect(a_block, block3) or pygame.sprite.collide_rect(a_block, block4)or pygame.sprite.collide_rect(a_block, wall1) or pygame.sprite.collide_rect(a_block, wall2) or pygame.sprite.collide_rect(a_block, wall3) or pygame.sprite.collide_rect(a_block, wall4):
time_string = "Du overlevede {} sekunder".format(pygame.time.get_ticks()/1000)
text = font.render(time_string, True, sort)
window.blit(text, (window_width/2-100, window_height/2-100))
if pygame.time.get_ticks()/1000>10:
time_string1 = "Flot!"
text = font2.render(time_string1, True, sort)
window.blit(text, (20, 20))
else:
time_string2 = "Ikke så godt :c"
text = font2.render(time_string2, True, sort)
window.blit(text, window.blit(text, (20, 20)))
pygame.display.update()
pygame.time.wait(5000)
running = False
block_group.draw(window)
pygame.display.update()#opdater skærmen
pygame.quit ()
And the error is:
Traceback (most recent call last):
File "C:\Python33\eskappa1.py", line 81, in <module>
block1.move_ip(speed) #here error is here
AttributeError: 'Block' object has no attribute 'move_ip'
Your Block subclasses Sprite, which per the documentation, doesn't have a move_ip method; this explains the error you are seeing.
I think what you need to do is move the Rect belonging to the Block, which does have that method. Try:
block1.rect.move_ip(speed)