1
votes

I have a game where the bullet is moving so fast that in a single frame it is already across the screen. That being said, it has already collided with multiple walls. Currently, I have a rectangular image that spans from where the bullet currently is, to where the bullet will be in the next frame, in order to not miss any zombies that may be in between.

I also kill the bullet if it collided with any wall, before checking if it collided with any of the zombies because what happened was that if there was a zombie behind the wall and I checked for the zombie collisions first, it would kill the zombie and then kill the bullet.

So basically, I would like to know a way to find the coordinates of where the bullet collided with the wall so that instead of advancing the bullet at its full speed, I will just advance it to just before where the collision is, check for zombie collisions, and then kill the bullet.

I am using mask collision.

2

2 Answers

2
votes

If the bullets travel too fast to collide with the walls or enemies, you need ray casting (or alternatively move the bullets in multiple small steps). Here's a simple ray casting example that returns the closest collision point. I use vectors and pygame.Rect.collidepoint to see if a point along the heading vector collides with an obstacle.

import sys
import pygame as pg
from pygame.math import Vector2


class Wall(pg.sprite.Sprite):

    def __init__(self, x, y, w, h, *groups):
        super().__init__(*groups)
        self.image = pg.Surface((w, h))
        self.image.fill(pg.Color('goldenrod4'))
        self.rect = self.image.get_rect(topleft=(x, y))


def ray_cast(origin, target, obstacles):
    """Calculate the closest collision point.

    Adds the normalized `direction` vector to the `current_pos` to
    move along the heading vector and uses `pygame.Rect.collidepoint`
    to see if `current_pos` collides with an obstacle.

    Args:
        origin (pygame.math.Vector2, tuple, list): Origin of the ray.
        target (pygame.math.Vector2, tuple, list): Endpoint of the ray.
        obstacles (pygame.sprite.Group): A group of obstacles.

    Returns:
        pygame.math.Vector2: Closest collision point or target.
    """
    current_pos = Vector2(origin)
    heading = target - origin
    # A normalized vector that points to the target.
    direction = heading.normalize()
    for _ in range(int(heading.length())):
        current_pos += direction
        for sprite in obstacles:
            # If the current_pos collides with an
            # obstacle, return it.
            if sprite.rect.collidepoint(current_pos):
                return current_pos
    # Otherwise return the target.
    return Vector2(target)


def main():
    screen = pg.display.set_mode((640, 480))
    clock = pg.time.Clock()
    all_sprites = pg.sprite.Group()
    walls = pg.sprite.Group()
    Wall(100, 170, 90, 20, all_sprites, walls)
    Wall(200, 100, 20, 140, all_sprites, walls)
    Wall(400, 60, 150, 100, all_sprites, walls)

    pos = Vector2(320, 440)
    done = False

    while not done:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True

        all_sprites.update()
        collision_point = ray_cast(pos, pg.mouse.get_pos(), walls)
        screen.fill((30, 30, 30))
        all_sprites.draw(screen)
        pg.draw.line(screen, (50, 190, 100), pos, pg.mouse.get_pos(), 2)
        pg.draw.circle(screen, (40, 180, 250), [int(x) for x in collision_point], 5)

        pg.display.flip()
        clock.tick(30)


if __name__ == '__main__':
    pg.init()
    main()
    pg.quit()
    sys.exit()
0
votes

Unfortunately PyGame doesn't give us a built-in means of returning collision points easy way of doing this so there is a bit of leg work to do.

However, before I explain that, you mentioned in your question that the bullet is moving very fast. I'm not sure how fast we are talking so this might not apply, but in my experience collision becomes a hit and miss at high speeds, especially if you're working on a slower computer.

Assuming that ^ isn't applicable:

We can use pygame.Rect.colliderect to trigger an if statement.

if <bullet-rect>.collidepoint(<wall-rect>):
  print(bullet_x, bullet_y)

Simply swap out and for the actual rects and you should be good to go. One thing to note is that if the bullet is moving right to left, you will have to add the bullet's width to the x value, and if the bullet is moving top to bottom, you will have to add the bullet's height to the y value.

Note: Remember to add pygame.Rect(<bullet-rect>) and pygame.Rect(<wall-rect>) to each value or you'll get an error.