“I’m a programmer. I like programming. And the best way I’ve found to have a positive impact on code is to write it.”
Robert C. Martin, Clean Architecture
For the computation of a reflection vector see Vector - Reflection.
Related Stack Overflow questions:
In PyGame, basic collision detection can be done using pygame.Rect
objects. The Rect
object offers various methods for detecting collisions between objects. Note that even the collision of a rectangular object with a circular object such as a paddle and a ball in Pong game can be roughly detected by a collision between two rectangular objects, the paddle and the bounding rectangle of the ball.
Some examples:
Test if a point is inside a rectangle
📁 Minimal example - collidepoint
repl.it/@Rabbid76/PyGame-collidepoint
import pygame
pygame.init()
window = pygame.display.set_mode((250, 250))
rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(100, 100)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
point = pygame.mouse.get_pos()
collide = rect.collidepoint(point)
color = (255, 0, 0) if collide else (255, 255, 255)
window.fill('black')
pygame.draw.rect(window, color, rect)
pygame.display.flip()
pygame.quit()
exit()
Test if two rectangles overlap
📁 Minimal example - collidepoint
repl.it/@Rabbid76/PyGame-colliderect
import pygame
pygame.init()
window = pygame.display.set_mode((250, 250))
rect1 = pygame.Rect(*window.get_rect().center, 0, 0).inflate(75, 75)
rect2 = pygame.Rect(0, 0, 75, 75)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
rect2.center = pygame.mouse.get_pos()
collide = rect1.colliderect(rect2)
color = (255, 0, 0) if collide else (255, 255, 255)
window.fill('black')
pygame.draw.rect(window, color, rect1)
pygame.draw.rect(window, (0, 255, 0), rect2, 6, 1)
pygame.display.flip()
pygame.quit()
exit()
Furthermore pygame.Rect.collidelist
and pygame.Rect.collidelistall
can be used for the collision test between a rectangle and a list of rectangles. pygame.Rect.collidedict
and pygame.Rect.collidedictall
can be used for the collision collision test between a rectangle and a dictionary of rectangles.
The collision of pygame.sprite.Sprite
and pygame.sprite.Group
objects, can be detected by pygame.sprite.spritecollide()
, pygame.sprite.groupcollide()
or pygame.sprite.spritecollideany()
. When using these methods, the collision detection algorithm can be specified by the collided
argument:
The collided argument is a callback function used to calculate if two sprites are colliding.
Possible collided
callables are collide_rect
, collide_rect_ratio
, collide_circle
, collide_circle_ratio
, collide_mask
Some examples:
📁 Minimal example - collidepoint
repl.it/@Rabbid76/PyGame-spritecollide
import pygame
pygame.init()
window = pygame.display.set_mode((250, 250))
sprite1 = pygame.sprite.Sprite()
sprite1.image = pygame.Surface((75, 75))
sprite1.image.fill((255, 0, 0))
sprite1.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(75, 75)
sprite2 = pygame.sprite.Sprite()
sprite2.image = pygame.Surface((75, 75))
sprite2.image.fill((0, 255, 0))
sprite2.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(75, 75)
all_group = pygame.sprite.Group([sprite2, sprite1])
test_group = pygame.sprite.Group(sprite2)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
sprite1.rect.center = pygame.mouse.get_pos()
collide = pygame.sprite.spritecollide(sprite1, test_group, False)
window.fill('black')
all_group.draw(window)
for s in collide:
pygame.draw.rect(window, (255, 255, 255), s.rect, 5, 1)
pygame.display.flip()
pygame.quit()
exit()
pygame.sprite.spritecollide()
/ collide_circle
📁 Minimal example - collidepoint
repl.it/@Rabbid76/PyGame-spritecollidecollidecircle
import pygame
pygame.init()
window = pygame.display.set_mode((250, 250))
sprite1 = pygame.sprite.Sprite()
sprite1.image = pygame.Surface((80, 80), pygame.SRCALPHA)
pygame.draw.circle(sprite1.image, (255, 0, 0), (40, 40), 40)
sprite1.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(80, 80)
sprite1.radius = 40
sprite2 = pygame.sprite.Sprite()
sprite2.image = pygame.Surface((80, 89), pygame.SRCALPHA)
pygame.draw.circle(sprite2.image, (0, 255, 0), (40, 40), 40)
sprite2.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(80, 80)
sprite2.radius = 40
all_group = pygame.sprite.Group([sprite2, sprite1])
test_group = pygame.sprite.Group(sprite2)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
sprite1.rect.center = pygame.mouse.get_pos()
collide = pygame.sprite.spritecollide(sprite1, test_group, False, pygame.sprite.collide_circle)
window.fill('black')
all_group.draw(window)
for s in collide:
pygame.draw.circle(window, (255, 255, 255), s.rect.center, s.rect.width // 2, 5)
pygame.display.flip()
pygame.quit()
exit()
Related Stack Overflow questions:
📁 Minimal example - Restrict circle to frame
📁 Minimal example - Let a ball bounce off floor
📁 Minimal example - ball bounce and change size
PyGame has a feature that does exactly what you want it to do. Use pygame.Rect
objects and pygame.Rect.clamp()
respectively pygame.Rect.clamp_ip()
:
Returns a new rectangle that is moved to be completely inside the argument Rect.
With this function, an object can be kept completely in the window. Get the window rectangle with get_rect
and clamp the object in the window:
while run:
# [...]
key = pygame.key.get_pressed()
if key[pygame.K_w]:
paddle1.rect.y += -paddle_speed
# [...]
winRect = win.get_rect()
paddle1.rect.clamp_ip(winRect)
paddle2.rect.clamp_ip(winRect)
paddle3.rect.clamp_ip(winRect)
paddle4.rect.clamp_ip(winRect)
# [...]
Related Stack Overflow questions:
Related Stack Overflow questions:
📁 Minimal example - Mouse collide with rectangle
Related Stack Overflow questions:
Related Stack Overflow questions:
📁 Minimal example - Is point on line
computes the shortest distance of a point to a line:
dist = abs(dot(normalized(NV), P - LP))
, where NV
is the normal vector to the line, LP
is a point on the line and P
is the point whose distance needs to be calculated.
import math
def distance_point_line(pt, l1, l2):
nx, ny = l1[1] - l2[1], l2[0] - l1[0]
nlen = math.hypot(nx, ny)
nx /= nlen
ny /= nlen
vx, vy = pt[0] - l1[0], pt[1] - l1[1]
dist = abs(nx*vx + ny*vy)
return dist
The same function with the use of pygame.math.Vector2
:
def distance_point_line(pt, l1, l2):
NV = pygame.math.Vector2(l1[1] - l2[1], l2[0] - l1[0])
LP = pygame.math.Vector2(l1)
P = pygame.math.Vector2(pt)
return abs(NV.normalize().dot(P -LP))
The algorithm used the Dot product distance from the point to the line.. In general The Dot product of 2 vectors is equal the cosine of the angle between the 2 vectors multiplied by the magnitude (length) of both vectors.
dot(A, B) == | A | * | B | * cos(angle_A_B)
This follows, that the Dot product of 2 Unit vectors is equal the cosine of the angle between the 2 vectors, because the length of a unit vector is 1.
uA = normalize( A )
uB = normalize( B )
cos(angle_A_B) == dot(uA, uB)
Therefore the Dot product of the normalized normal vector to the line (NV) and a vector from a point on the line (LP) to the point whose distance must be calculated (P) is the shortest distance of the point to the line.
Related Stack Overflow questions:
📁 Minimal example - Is point in triangle
Related Stack Overflow questions:
📁 Minimal example - Is point in triangle
Related Stack Overflow questions:
Related Stack Overflow questions:
The collision of an ellipse and a point can be reduced to the collision of a circle and a point by scaling the ellipse to appear as a circle and scaling the distance vector of the point to the center of the ellipse in the same way. Since the ellipses are axis-aligned in PyGame, this can easily be achieved by scaling one of the coordinates by the ratio of the ellipse axis length.
Define the bounding rectangle (pygame.Rect
) of the ellipse (ellipse_rect
) and get the semi-axis (a
, b
):
a = ellipse_rect.width // 2
b = ellipse_rect.height // 2
Compute the ratio of the semi-axis
scale_y = a / b
Define an point (test_x
, test_y
) and calculate the vector of the point to the center of the ellipse (cpt_x
, cpt_y
). Scale the y-coordinate of the vector with the ratio of semi-x-axis and semi-y-axis:
cpt_x, cpt_y = ellipse_rect.center
dx = test_x - cpt_x
dy = (test_y - cpt_y) * scale_y
The point lies in the ellipse if the square of the Euclidean distance (dx*dx + dy*dy
) is smaller than the square of the semi-x axis (a*a
):
collide = dx*dx + dy*dy <= a*a
Related Stack Overflow questions:
Animation glitch when simulating the collision of two blocks for the calculation of PI
I recommend to use a pygame.Rect
object and either .collidepoint()
or colliderect()
to find a collision between a rectangle and an object.
rect1 = pygame.Rect(x1, y1, w1, h1)
rect2 = pygame.Rect(x2, y2, w2, h2)
if rect1.colliderect(rect2):
# [...]
rect = pygame.Rect(x1, y1, w1, h1)
if rect1.collidepoint((x2, y2)):
# [...]
The method colliderect
evaluates, if a pygame.Rect
object intersects, with a rectangle. hbox1
and hbox2
are rectangle objects, then the result of hbox1.colliderect(hbox2)
is equal to the result of hbox2.colliderect(hbox1)
. The operation is Commutative.
But note, that the argument to colliderect
does not need to be a pygame.Rect
object. The argument is allowed to be a tuple, with 4 components (x, y, width, height), too.
If the rectangles (x1
, y1
, w1
, h1
) and (x2
, y2
, w2
, h2
) are intersection can be evaluated by:
intersect = x1 < x2+w2 and x2 < x1+w1 and y1 < y2+h2 and y2 < y1+h1
It’s easy to see that the two rectangles can be swapped and the result will be the same.
Related Stack Overflow questions:
Use pygame.Rect.collidelist
to test whether a rectangle collides with one of a list of rectangles.
Test whether the rectangle collides with any in a sequence of rectangles. The index of the first collision found is returned. If no collisions are found an index of -1 is returned.
if player_rect.colliderect(tile_rects) >= 0:
# [...]
pygame.Rect.collidelist
and pygame.Rect.collidelistall
can be used for the collision test between a rectangle and a list of rectangles.
📁 Minimal example - Mouse collide with list of rectangles
pygame.Rect.collidedict
and pygame.Rect.collidedictall
can be used for the collision collision test between a rectangle and a dictionary of rectangles.
Use pygame.Rect
and colliderect()
to detect the collision between the bounding rectangles of 2 images (pygame.Surface
objects). The bounding rectangle of a Surface can be get by get_rect()
, where the location has to be set by an keyword argument
rect = surface.get_rect(topleft = (x, y))
Note, a collision of a Sprite
object and a Group
or event 2 Group
s can be found by pygame.sprite.spritecollide()
respectively pygame.sprite.groupcollide()
.
Related Stack Overflow questions:
Related Stack Overflow questions:
Related Stack Overflow questions:
How do I avoid an glitchy collision between circle and rectangle in PyGame?
📁 Minimal example - Avoid glitchy collision between circle and rectangle
how do i make the ball bounce off of the all the rectangles sides
Issue finding side of collision for Circle-Rectangle collision
📁 Minimal example - Find the intersection side between the circle and the rectangle
Detect collision between textbox and circle in pygame
📁 Minimal example - Find the intersection of a small rectangle with the outline of a large circle
How can I know if a circle and a rect is touched in Pygame?
📁 Minimal example - Find the intersection of a rectangle and a circle
How to avoid a glitchy collision between circle and rectangle:
There are 2 strategies to a void that.
Move the ball in the way, that it is touching the player but not intersecting the player once a collision is detected. e.g.:
dx = ballposx - player.rect.centerx
dy = ballposy - player.rect.centery
if abs(dx) > abs(dy):
ballposx = player.rect.left-ballrad if dx < 0 else player.rect.right+ballrad
else:
ballposy = player.rect.top-ballrad if dy < 0 else player.rect.bottom+ballrad
Reflect the movement of the ball only if its movement vector points in a direction “against” the ball. e.g.:
if abs(dx) > abs(dy):
if (dx < 0 and v[0] > 0) or (dx > 0 and v[0] < 0):
v.reflect_ip(pygame.math.Vector2(1, 0))
else:
if (dy < 0 and v[1] > 0) or (dy > 0 and v[1] < 0):
v.reflect_ip(pygame.math.Vector2(0, 1))
📁 Minimal example - Avoid glitchy collision between circle and rectangle
See also Pong.
Related Stack Overflow questions:
Related Stack Overflow questions:
Related Stack Overflow questions:
Problem with calculating line intersections
Problem with finding the closest intersection
I’m having a problem with determining the intersection of two lines in this python code
To find the intersection points of 2 rays or line segments in two-dimensional space, I use vector arithmetic and the following algorithm:
P ... point on the 1. line
R ... direction of the 1. line
Q ... point on the 2. line
S ... direction of the 2. line
alpha ... angle between Q-P and R
beta ... angle between R and S
gamma = 180° - alpha - beta
h = | Q - P | * sin(alpha)
u = h / sin(beta)
t = | Q - P | * sin(gamma) / sin(beta)
t = dot(Q-P, (S.y, -S.x)) / dot(R, (S.y, -S.x)) = determinant(mat2(Q-P, S)) / determinant(mat2(R, S))
u = dot(Q-P, (R.y, -R.x)) / dot(R, (S.y, -S.x)) = determinant(mat2(Q-P, R)) / determinant(mat2(R, S))
X = P + R * t = Q + S * u
See also Line–line intersection
If t == 1
, then X = P + R
. This can be used to assess whether the intersection is on a line segment.
If a line is defined through the 2 points L1
and L2
, it can be defined that P = L1
and R = L2-L1
. Therefore the point of intersection (X
) lies on the line segment from L1
to L2
if 0 <= t <= 1
.
The same relation applies to u
and S
.
The following function implements the above algorithm using pygame.math.Vector2
objects of the pygame.math
module:
def intersect_line_line_vec2(startObs, endObs, origin, endpoint):
P = pygame.Vector2(startObs)
R = (endObs - P)
Q = pygame.Vector2(origin)
S = (endpoint - Q)
d = R.dot((S.y, -S.x))
if d == 0:
return None
t = (Q-P).dot((S.y, -S.x)) / d
u = (Q-P).dot((R.y, -R.x)) / d
if 0 <= t <= 1 and 0 <= u <= 1:
X = P + R * t
return (X.x, X.y)
return None
The same algorithm without the use of the pygame.math
module, less readable but more or less the same:
def intersect_line_line(P0, P1, Q0, Q1):
d = (P1[0]-P0[0]) * (Q1[1]-Q0[1]) + (P1[1]-P0[1]) * (Q0[0]-Q1[0])
if d == 0:
return None
t = ((Q0[0]-P0[0]) * (Q1[1]-Q0[1]) + (Q0[1]-P0[1]) * (Q0[0]-Q1[0])) / d
u = ((Q0[0]-P0[0]) * (P1[1]-P0[1]) + (Q0[1]-P0[1]) * (P0[0]-P1[0])) / d
if 0 <= t <= 1 and 0 <= u <= 1:
return P1[0] * t + P0[0] * (1-t), P1[1] * t + P0[1] * (1-t)
return None
Related Stack Overflow questions:
📁 Minimal example - Find the intersection of an endless line and a circle
📁 Minimal example - Find the intersection of a line segment and a circle
Related Stack Overflow questions:
Detecting collisions between polygons and rectangles in Pygame
📁 Minimal example - Find the intersection of a rectangle and a polygon
How to give a warning when a moving object deviates from a path by a specific margin?
Related Stack Overflow questions:
Related Stack Overflow questions:
How to detect the collision of circles or balls in Pygame?
pygame Get the balls to bounce off each other
Pygame: How to make two objects stop moving once they collide
Related Stack Overflow questions:
Collision detection between an ellipse and a circle
📁 Minimal example - Find the intersection of a circle and a rectangle
from math import pi, sin, cos, atan2, radians, copysign, sqrt
class Ellipse:
# [...]
def pointFromAngle(self, a):
c = cos(a)
s = sin(a)
ta = s / c ## tan(a)
tt = ta * self.rx / self.ry ## tan(t)
d = 1. / sqrt(1. + tt * tt)
x = self.centre[0] + copysign(self.rx * d, c)
y = self.centre[1] - copysign(self.ry * tt * d, s)
return x, y