I have a protective wall of stacked rectangles that the player is behind. If the protective wall collides with a bomb, I not only want to destroy the one rectangle but also the side and bottom neighbors. Does anyone have an idea how to get the coordinates of the neighbors?
i create the wall with this code:
for j in range(int(bodenebenen)):
for i in range(int(bodenspalten)):
m = Boden(int(i)*bodenbreite,(int(j)*bodenhoehe) ,int(bodenbreite),int(bodenhoehe),620,schutzcolor[random.randint(0,len(schutzcolor) - 1)])
protectivewall.add(m)
alle_sprites.add(m)
hits = pygame.sprite.groupcollide(bombs,protectivewall,True,True)
I suggest to inflate the rectangles of the bombs before collision detection. A larger rectangle, hits more objects. Use inflate_ip to inflate the rectangles in place and shrink (inverse inflate) the remaining bombs after collision detection. You just need to find a good size by which you want to enlarge the rectangles. I use 10 here just as an example:
for b in bombs:
b.rect.inflate_ip(10, 10)
hits = pygame.sprite.groupcollide(bombs, protectivewall, True, True)
for b in bombs:
b.rect.inflate_ip(-10, -10)
Related
I am implementing collision detection and want to check if a rectangular object is touching the player. My wall uses .set_colorkey(background) where background is the specified background colour. The problem is that when I get my wall's rectangle with .get_rect(), it gets the full image's size which includes the transparent parts instead of just the opaque parts.
I thought about making the wall image file smaller in size to remove the background but that would be inconvenient as I would need to do this for each partially transparent image I have. I also thought about using arrays to get the colour and checking if it matches the background colour and getting the rectangle's size from there but that would be slow and cumbersome.
for x, y in ((i, j) for i in land_x for j in land_y):
# land_x, land_y hold the tiles to be checked
try:
tx1, ty1, tx2, ty2 = \
texture[land[y][x]].get_rect()
# tx1, ty1 coordinates of top-left corner
# tx2, ty2 width and height respectively
if tx2 == 0 and ty2 == 0:
continue # skip to other objects
tx1 = x*64 - tx2/2
ty1 = y*64 - ty2/2
px1, py1, px2, py2 = \
PLAYER.get_rect()
px1 = player_x - px2/2
py1 = -player_y - py2/2
if p.Rect(px1, py1, px2, py2).colliderect(
p.Rect(tx1, ty1, tx2, ty2)
):
player_x -= direction_x
break # go outside loop to start checking y
except IndexError: # incase player is outside map
pass # skip to other objects
The .get_rect() outputs a rectangle the size of the whole image whereas I want a rectangle that doesn't include the transparent parts.
Example:
texture is a 64x64 image with a 48x48 block in the centre.
The background colour is removed and a 48x48 solid coloured block is left (even though the image size is still 64x64).
Expected Output:
texture.get_rect() should output a rectangle of size 48x48.
Actual Output:
texture.get_rect() instead outputs a rectangle of size 64x64.
Any help on this would be appreciated :D
If you want to ignore the transparent pixels in your collision detection, you're talking about pixel-perfect collision.
To do this in pygame, pygame offers the Mask class. You usually create your masks with pygame.mask.from_surface and use it together with pygame.sprite.spritecollide and pygame.sprite.collide_mask.
Maybe think about using pygame's Sprite class to make use of all the features it offers.
Even if you don't want to use pygame's build-in collision detection, you can take a look at the source to see how it works.
You are making this too hard. You know the size of your objects. Add a smaller collision rect to each of your objects at creation time and use that for collision. Or use a circle if that is better for the object.
tile.crect = Rect(whatever)
Or just multiply the existing rect dimensions by some scale factor for your collision rect. Don't do all of these calculations. Store a Rect for each collideable object and have a rect for the player.
tx1 = x*64 - tx2/2
ty1 = y*64 - ty2/2
px1, py1, px2, py2 = \
PLAYER.get_rect()
px1 = player_x - px2/2
py1 = -player_y - py2/2
Then just test collision directly:
for t in tiles:
if player.rect.colliderect( t.rect ):
If the player is a sprite its rect moves around. Look at the example code in the doc.
https://www.pygame.org/docs/ref/sprite.html
hey I wanted to know if someone knew a code for checking collision between a rect and a sector of a circle, I know how to do it but the way I would do it is unefficient, here is the basic theory:
def is_inside_sector(rect, circle_center, radius, sector_point_A, sector_point_B):
#the rect is a tuple with the coordinate of the 4 corners
#the sector_point_A is the point on the edge of the sector and the circle
#the sector_point_B is the other one
angle_A = sector_point_A.angle_to(vec(radius, 0)) % 360
#calculate the angle of the first sector point
angle_B = sector_point_B.angle_to(vec(radius, 0)) % 360
#same for the second one
for corner in rect:
if circle_center.distance_to(corner) <= radius:
if corner.angle_to(vec(radius, 0)) <= angle_A and corner.angle_to(vec(radius, 0)) >= angle_B:
return True
else:
return False
#this checks if any corner is in the sector, if yes, the rect and the sector collide
aight now I know the code has many flaws but I didnt complete it.
there are possibility that the rect and the sector collide without any corner of the rect being inside the sector, I would fix that by checking if any of the 3 corners of the sector (circle_center, sector_point_A, sector_point_B) would be inside the rect.
simple stuff and it would be a really good collision checking BUT! you gotta check 7 point! this is insanly slow, you gotta check if any of the corners of the rect is inside the sector and you gotta check if any of the corners of the sector is inside the rect, it will give you 100% accuracy but it is so slow.
is there any way I can check the collision of a sector and a rect without making an absurde function?
thanks alot if you help me on that, if you need I will make a real function testing the 7 points as I explained, the one I wrote was made on the go
Presumably you mean, by sector S of a circle, the Wikipedia definition: ⌔.
You need three functions:
Is a given point p in the sector S?
Do two segments intersect?
Does a segment intersect a circular arc?
I don't see how you can cover all cases without these three. All three have been
explored extensively, and code can be found all over the web.
I'm making a basic car game, and would like the car to move faster on the track and slower on the grass. I have two separate images to create the background, one is the track, and the other is the rest of the image.
I have a car sprite that can move around the screen, but when testing for collision I don't know what to do. I tried:
track = track = SCREEN.blit(track, (0, 0))
if track.colliderect(car):
speed += 1.5
else:
speed += 0.5
But I'm told this only works for rect objects, what do I need to do?
I've not been able to find anything related to image collision, that would help my situation, so if there are any other questions answering this please tell me.
Basically like this:
def isOnTrack(track,car):
#one corner; a is alpha, the transparency
#If a is 0 that means completely transparent
if track.get_at(car.x,car.y).a==0:
return false
#do this for all four corners of car
return true
The Image you use for this collision has to ONLY containt the track
track.get_at(x,y) returns a color value if track is a surface
Color has 4 fields, r g b a
I check a; the transparency
I'm working on a breakout clone and I've been trying to figure out how to get the intersection rect of two colliding rects so I can measure how deep the ball entered the block in both x and y axis and decide which component of the velocity I'll reverse.
I figured I could calculate the depth for each case like this:
But if I had the intersection rect than I woudn't have to worry if the ball hits the block from the left/right or top/bottom (since I would be only reversing the x and y axis respectively), thus saving me a lot of typing.
I've looked on Pygame's docs but seems it doesn't have a function for that. How would I go about solving this problem?
Assuming you have rectangles r1 and r2, with .left, .right, .top, and .bottom edges, then
left = max(r1.left, r2.left);
right = min(r1.right, r2.right);
top = max(r1.top, r2.top);
bottom = min(r1.bottom, r2.bottom);
(with the usual convention that coordinates increase top to bottom and left to right). Finally, check that left<right and top<bottom, and compute the area:
Area = (right - left) * (top - bottom);
Alternatively, you can use the clip() function. From the docs you linked in your question:
clip(Rect) -> Rect Returns a new rectangle that is cropped to be
completely inside the argument Rect. If the two rectangles do not
overlap to begin with, a Rect with 0 size is returned.
I am a begginner at python and I'm trying to make a circle game. So far it draws a circle at your mouse with a random color and radius when you click.
Next, I would like the circle to fly off the screen in a random direction. How would I go about doing this? This is the main chunk of my code so far:
check1 = None
check2 = None
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit
if event.type == MOUSEBUTTONDOWN:
last_mouse_pos = pygame.mouse.get_pos()
if last_mouse_pos:
global check
color1 = random.randint(0,255)
color2 = random.randint(0,255)
color3 = random.randint(0,255)
color = (color1,color2,color3)
radius = random.randint (5,40)
posx,posy = last_mouse_pos
if posx != check1 and posy != check2:
global check1, check2
screen.lock()
pygame.draw.circle(screen, color, (posx,posy), radius)
screen.unlock()
check1,check2 = posx,posy
pygame.display.update()
Again, I want the circle to fly off the screen in a random direction.
I have made a few attempts but no successes yet.
Also, thanks to jdi who helped me s
When you create the circle (on click), generate 2 random numbers. These will be your (x,y) components for a two dimensional Euclidean velocity vector:
# interval -1.0 to 1.0, adjust as necessary
vx, vy = ( (random.random()*2) -1, (random.random()*2) - 1 )
Then after the ball has been created, on each iteration of the game loop, increment your ball's position by the velocity vector:
posx, posy = posx + vx, posy + vy
Note that the overall speed might be variable. If you want to always have a speed of 1.0 per seconds, normalize the vector:
To normalize the vector, you divide it by its magnitude:
So in your case:
And hence:
So in Python, after importing math with import math:
mag = math.sqrt(vx*vx + vy*vy)
v_norm = vx/mag, vy/mag
# use v_norm instead of your (vx, vy) tuple
Then you can multiply this with some speed variable, to get reliable velocity.
Once you progress to having multiple objects moving around and potentially off screen, it is useful to remove the offscreen objects which have no chance of coming back, and have nothing to do with your program anymore. Otherwise, if you keep tracking all those offscreen objects while creating more, you get essentially a memory leak, and will run out of memory given enough time/actions.
While what you are doing right now is quite simple, assuming you haven't already, learning some basic vector math will pay itself off very soon. Eventually you may need to foray into some matrix math to do certain transformations. If you are new to it, its not as hard as it first looks. You can probably get away with not studying it, but you will save yourself effort later if you start attempting to do more ambitious things.
Right now, you are doing the following (drastically simplifying your code)...
while True:
if the mouse was clicked:
draw a circle on the screen where the mouse was clicked
Let's make things a little easier, and build up gradually.
Start with the circle without the user clicking
To keep things simple, let's make the circle near the top left of the screen, that way we can always assume there will be a circle (making some of the logic easier)
circle_x, circle_y = 10,10
while True:
draw the circle at circle_x, circle_y
pygame.display.update()
Animate the circle
Before going into too much about "random directions", let's just make it easy and go in one direction (let's say, always down and to the right).
circle_x, circle_y = 0,0
while True:
# Update
circle_x += 0.1
circle_y += 0.1
# Draw
draw the circle at circle_x, circle_y
update the display
Now, every time through the loop, the center of the circle moves a bit, and then you draw it in its new position. Note that you might need to reduce the values that you add to circle_x and y (in my code, 0.1) in case the circle moves too fast.
However, you'll notice that your screen is now filling up with circles! Rather than one circle that is "moving", you're just drawing the circle many times! To fix this, we're going to "clear" the screen before each draw...
screen = ....
BLACK = (0,0,0) # Defines the "black" color
circle_x, circle_y = 0,0
while True:
# Update
circle_x += 0.1
circle_y += 0.1
# Draw
screen.fill(BLACK)
draw the circle at circle_x, circle_y
update the display
Notice that we are "clearing" the screen by painting the entire thing black right before we draw our circle.
Now, you can start work the rest of what you want back into your code.
Keep track of multiple circles
You can do this by using a list of circles, rather than two circle variables
circles = [...list of circle positions...]
while True:
# Update
for circle in circles:
... Update the circle position...
# Draw
screen.fill(BLACK)
for circle in circles:
draw the circle at circle position # This will occur once for each circle
update the display
One thing to note is that if you keep track of the circle positions in a tuple, you won't be able to change their values. If you're familiar with Object Oriented Programming, you could create a Circle class, and use that to keep track of the data relating to your circles. Otherwise, you can every loop create a list of updated coordinates for each circle.
Add circle when the user clicks
circles = []
while True:
# event handling
for event in pygame.event.get():
if event.type == MOUSEBUTTONDOWN:
pos = pygame.mouse.get_pos()
circles.append( pos ) # Add a new circle to the list
# Update all the circles
# ....
# Draw
clear the screen
for circle_position in circles:
draw the circle at circle_position # This will occur once for each circle
update the display
Have the circle move in a random direction
This is where a good helping of math comes into play. Basically, you'll need a way to determine what to update the x and y coordinate of the circle by each loop. Keep in mind it's completely possible to just say that you want it to move somewhere between -1 and 1 for each axis (X, y), but that isn't necessarily right. It's possible that you get both X and Y to be zero, in which case the circle won't move at all! The next Circle could be 1 and 1, which will go faster than the other circles.
I'm not sure what your math background is, so you might have a bit of learning to do in order to understand some math behind how to store a "direction" (sometimes referred to as a "vector") in a program. You can try Preet's answer to see if that helps. True understanding is easier with a background in geometry and trigonometry (although you might be able to get by without it if you find a good resource).
Some other thoughts
Some other things you'll want to keep in mind:
Right now, the code that we're playing with "frame rate dependent". That is, the speed in which the circles move across the screen is entirely dependent on how fast the computer can run; a slower computer will see the circles move like snails, while a faster computer will barely see the circles before they fly off the screen! There are ways of fixing this, which you can look up on your own (do a search for "frame rate dependence" or other terms in your favorite search engine).
Right now, you have screen.lock() and screen.unlock(). You don't need these. You only need to lock/unlock the screen's surface if the surface requires it (some surfaces do not) and if you are going to manually access the pixel data. Doing things like drawing circles to the screen, pygame in lock/unlock the surfaces for you automatically. In short, you don't need to deal with lock/unlock right now.