This tutorial continues on from Part 3
We now need to make the player be able to interact with the particles. To do this we will first need to detect when a collision has occurred. To make things easier to visualise, I will first draw the bounding box around the player.
     pygame.draw.rect(screen, (R,G,B), Rect(left, top, width, height))
To make the actual collision detection code simpler I will center the python graphic and bounding box on the x-y coordinate:
def display(self):
screen.blit(self.img, (self.x self.img.get_width()/2,self.y self.img.get_height()/2)); # Blit image onto the display surface
pygame.draw.rect(screen,(255,255,255),(self.x self.img.get_width()/2,self.y self.img.get_height()/2,self.img.get_width(),self.img.get_height()),1)
We now need to work out when a circle (particle) has collided with our rectangle (player). This turns out to be quite complicated; and in general we will try and cheat by either using:
    1. collisions between two circles
    2. collisions between two rectangles
When bounding box/circle tests are used it is difficult to find
the exact point of collision. If greater accuracy is required (such as in a physics simulation) a third test is generally used:
    3. the ‘pixel overlap test’
If the sprites
always keep the same orientation (they are not rotated), the bounding
box/circle method can give us a good approximation of the angle of
I will continue using rectangular bounding boxes on the particles. I will draw these to make it easier to see what is going on. I will also add a transparent fill to the particles which proved more difficult than expected.
Apparently pygame.draw functions don’t support alpha functionality. Instead you need to create a surface object, manipulate that and then blit() it to the display surface – since blitting supports the alpha channel. In __init__() I added the following:
#create and populate transparent background surface for particles
self.surf1 = pygame.Surface((2*self.radius, 2*self.radius)); #create a new surface the size of the particle
self.surf1.set_colorkey(BACKGROUND_COLOUR); #set the background colour to be transparent
self.surf1.set_alpha(100); #set the transparency to 100/255, (255, 0, 0),(self.radius, self.radius), self.radius); #draw the circle to the transparent surface
If we don’t use set_colorkey() then the surface itself will not be transparent. This wouldn’t be a problem if we drew the player after the particles, but because we draw the particles second, we want the surface background to be transparent. In the display() method we need to blit() this surface to the main display surface. This produces the following result:



You might want to use a pygame.HWSURFACE to create a hardware-accelerated surface.
Another thing you might want to do is set set an alpha level for each .draw(). This will be slower:
s = pygame.Surface((1000,750), pygame.SRCALPHA)   # per-pixel alpha
s.fill((255,255,255,128))                         # notice the alpha value in the color
windowSurface.blit(s, (0,0))
You can read more here.
Rectangular Collisions:
We could calculate when a rectangular collision has occurred, but it turns out that the pygame.rect object has a colliderect() method so I will use that. This will require us to make a rect object for our player and particles. I will do this just after loading the image:
self.rect = pygame.Rect(self.x self.img.get_width()/2, self.y self.img.get_height()/2, self.img.get_width(), self.img.get_height());
Also in the initialisation function but for the particle, we also create a rect:
self.rect = pygame.Rect(self.xself.radius,self.yself.radius,2*self.radius,2*self.radius);
We then need move the rect each step. There are many rect properties/attributes that can be used, in our case rect.centre is the most convenient. This will go in the particle’s move() function (I will put it in the player’s display() function for now): = (self.x, self.y);

We can then check for a collision in the particles move() function. If a collision occurs, I have chosen to change to colour of the drawn circle:



if self.rect.colliderect(Player.rect):
self.colour = GREEN;
self.colour = RED;

After commenting out the bounding boxes, we get the following:

Circular Collisions:

We will now look at circular collisions between the bubbles and get them to bounce off each other. There will be some small differences in the way we structure this code as we are looking for collisions between instances of the same object.

There is no particular function for collisions between circles. The code is relatively simple; all we need to do is check if the distance between the circle centers is less than the sum of their radii.

<this section follows closely to this>

Apparently, “it’s mathematically impossible
to solve the equations describing three or more interacting objects.” We’ll have to make some “simplifications, which we make
it ‘inaccurate’, however, it should still represent a reasonable
approximation to reality.”

The first thing we will do is add where to check for a collision. We will do this in the current for loop, but will enumerate the loop effectively giving the particles an index number:


for i, particle1 in enumerate(particles):
for particle2 in particles[i+1:]:
circular_collision(particle1, particle2);

Note that the nested for loop only searches the remaining particles in the list. This is because the first bubble is compared to all the others, when we compare the second, there is no need to compare it to the first again.

We can now write the circular_collision function, I will change the colour of the particles when they collide (you will need to comment out the else the resets the colour in the player-particle collision):


def circular_collision(p1, p2):
if (math.sqrt((p1.x p2.x)**2 + (p1.y p2.y)**2) <= (p1.radius + p2.radius)):
p1.colour = BLUE;
p2.colour = BLUE;

When you run the program now, the red outlines should change to blue when the particles collide, and change to green when they collide with the player.


Transparent surfaces: