2

Sorry for my english and beginner knowledge of python

I am trying to find a way to get, during a collision between two rectangles, (a moving bullet and a box), which side of the box hit the bullet. I am currently using the rect.midtop, midright, midleft, midbottom of the bullet to find it. My problem is the bullet moves multiple pixels at a time and when the bullet hits the box but not enough close to the center, it registers the wrong collision.

Example:

The bullet comes from the bottom of the screen and I can see it should be a collision with the bottom side of the box, but it doesn't touch the rect.midtop and touches the rect.midright instead and it registers the wrong collison event.

Here is my collision code:

if self.bullet.rect.collidelist(self.listbox):
            self.bullet.pretx1 = 1
            self.bullet.pretx2 = 1
            self.bullet.prety = 1
            self.bullet.pret = 1
            for box in self.listbox:
                if box.rect.collidepoint(self.bullet.rect.midtop) or box.rect.collidepoint(self.bullet.rect.midbottom) and self.a == 1:
                    self.a = 0
                    print("vertical")
                    print(self.bullet.angle)
                    self.bullet.angle = abs(360 - self.bullet.angle)
                    print(self.bullet.angle)
                    self.listbox.remove(box)
                elif box.rect.collidepoint(self.bullet.rect.midleft) or box.rect.collidepoint(self.bullet.rect.midright) and self.a == 1:
                    self.a = 0
                    print("horizontal")
                    print(self.bullet.angle)
                    self.bullet.angle = 180 - self.bullet.angle
                    print(self.bullet.angle)
                    if self.bullet.angle < 0:
                        self.bullet.angle += 360
                        print(self.bullet.angle)
                    self.listbox.remove(box)

How would you do it? I tried to have a line rect, comparing angles, using masks but I couldn't get a solution to work.

Thanks for your time!

Larf
  • 31
  • 1
  • rect has 8 point (+ center) and you could check all 9 points: https://github.com/furas/python-examples/tree/master/pygame/collisions – furas Apr 16 '19 at 15:02
  • some games use other method: if bullet moves left then it can collide only on left side. And then target can collide only on right size. The same with other directions. With this assumption you can use standard method to check collision and you don't have to check every point separatelly. – furas Apr 16 '19 at 15:05
  • What is self.a? Also, I'd like to use plain math and it can be done easily if you draw on paper grid, coords, box and bullet. If you now coordinates of each vertex, you can do anything – Sergius Apr 16 '19 at 15:22
  • See also https://stackoverflow.com/a/55412058/142637 – sloth Apr 17 '19 at 06:24

1 Answers1

2

Use the direction vector (dx, dy) from the center of the box to the center of the bullet, to identify if the box is fit left, right, top or bottom:

dx = self.bullet.rect.centerx - box.rect.centerx 
dy = self.bullet.rect.centery - box.rect.centery

The 4 directions which have to be tested are:

dir     = [(-1, 0), (1, 0),  (0, -1), (0, 1)]
dirName = ["left",  "right", "top",   "bottom"]

You've to identify that direction which is closet to (dx, dy). This can be done by comparing the dot product of the vector (dx, dy) and the vectors to th 4 directions, which is similar to Check whether a point exists in circle sector or not with Python

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 ) 

The dot product of 2-dimensional vectors A and B can be calculated by 2 multiplications and 1 addition:

dotAB = Ax * Bx + Ay * By 

The closet direction is the direction, with gives the maximum dot product. The maximum cam be found by max:

max_dir = max([i for i in range(len(dir))], key = lambda i: dx*dir[i][0] + dy*dir[i][1])

e.g.

dir     = [(-1, 0), (1, 0),  (0, -1), (0, 1)]
dirName = ["left",  "right", "top",   "bottom"]

for box in self.listbox:

    if box.rect.colliderect(self.bullet.rect):

        dx = self.bullet.rect.centerx - box.rect.centerx 
        dy = self.bullet.rect.centery - box.rect.centery 

        max_dir = max([i for i in range(len(dir))], key = lambda i: dx*dir[i][0] + dy*dir[i][1])
        print("hit", dirName[max_dir]) 

        # [...]

Rabbid76
  • 202,892
  • 27
  • 131
  • 174