PyGameExamplesAndAnswers

StackOverflow            reply.it reply.it

“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.

Collision and Intersection

Overview

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:

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:

Collide with frame, window border (boundaries) and restrict to rectangle

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_rectand 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)

    # [...]

Collide with frame with floating point accuracy

Related Stack Overflow questions:

Point and Rectangle - Click in rectangle

Related Stack Overflow questions:

📁 Minimal example - Mouse collide with rectangle

Point and Grid - Click in grid

Related Stack Overflow questions:

Point and line

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.

Point in triangle

Related Stack Overflow questions:

📁 Minimal example - Is point in triangle

Point and hexagon

Related Stack Overflow questions:

📁 Minimal example - Is point in triangle

Point and Circle - Click in circle

Related Stack Overflow questions:

Point and Ellipse

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  

Rectangle and rectangle

Related Stack Overflow questions:

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.

Rectangle and list of rectangles

Related Stack Overflow questions:

Use pygame.Rect.collidelist to test whether a rectangle collides with one of a list of rectangles.

collidelist:

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 Groups can be found by pygame.sprite.spritecollide() respectively pygame.sprite.groupcollide().

Rectangle and diagonal slope (ramp)

Related Stack Overflow questions:

Rectangle and line

Related Stack Overflow questions:

Rectangle and circle

Related Stack Overflow questions:

How to avoid a glitchy collision between circle and rectangle:

There are 2 strategies to a void that.

  1. 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
    
  2. 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

Pong

See also Pong.

Related Stack Overflow questions:

Not axis aligned (rotated) rectangle and circle

Related Stack Overflow questions:

Line and line

Related Stack Overflow questions:

To find the intersection points of 2 rays or line segments in two-dimensional space, I use vector arithmetic and the following algorithm:

Problem with calculating line intersections

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

Line and Circle

Related Stack Overflow questions:

Rectangle and polygon

Related Stack Overflow questions:

Circle and polygon

Related Stack Overflow questions:

Circle and circle

Related Stack Overflow questions:

Circle and ellipse

Related Stack Overflow questions:

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