Python-pygame How to make an aura collision betwee

2019-07-11 14:32发布

问题:

so, Im trying to get a reaction when the sprite player pass around (not over) another sprite.

So far my idea is something like:

if (sprite.x +100) > player.x > (sprite.x -100) and (sprite.y +100) > player.y > (sprite.y -100):
    print("hit")
# Sprite.x and y are the position of the rect, not the whole image

This works but there is some way to simplify it? Also Im thinking how to get rid of the 100 pixels

回答1:

If you want to use a scaled rect for the collision detection, you can inflate your original rect (or create a new one) and assign it as a separate attribute to your sprite (I call it hitbox here). The original rect will only be used to store the position.

Create a custom collision detection function which has to be passed as the collided argument to pygame.sprite.spritecollide or groupcollide. In this function you can use the colliderect method of the hitbox rect to check if it collides with the rect of the other sprite.

def hitbox_collision(sprite1, sprite2):
    """Check if the hitbox of the first sprite collides with the
    rect of the second sprite.

    `spritecollide` will pass the player object as `sprite1`
    and the sprites in the enemies group as `sprite2`.
    """
    return sprite1.hitbox.colliderect(sprite2.rect)

Then call spritecollide in this way:

collided_sprites = pg.sprite.spritecollide(
    player, enemies, False, collided=hitbox_collision)

Here's a complete example (the red rect is the hitbox and the green rect the self.rect which is used as the blit position):

import pygame as pg


class Player(pg.sprite.Sprite):

    def __init__(self, pos, *groups):
        super().__init__(*groups)
        self.image = pg.Surface((30, 50))
        self.image.fill(pg.Color('dodgerblue1'))
        self.rect = self.image.get_rect(center=pos)
        # Give the sprite another rect for the collision detection.
        # Scale it to the desired size.
        self.hitbox = self.rect.inflate(100, 100)

    def update(self):
        # Update the position of the hitbox.
        self.hitbox.center = self.rect.center


class Enemy(pg.sprite.Sprite):

    def __init__(self, pos, *groups):
        super().__init__(*groups)
        self.image = pg.Surface((30, 50))
        self.image.fill(pg.Color('sienna1'))
        self.rect = self.image.get_rect(center=pos)


def hitbox_collision(sprite1, sprite2):
    """Check if the hitbox of the first sprite collides with the
    rect of the second sprite.

    `spritecollide` will pass the player object as `sprite1`
    and the sprites in the enemies group as `sprite2`.
    """
    return sprite1.hitbox.colliderect(sprite2.rect)


def main():
    screen = pg.display.set_mode((640, 480))
    clock = pg.time.Clock()
    all_sprites = pg.sprite.Group()
    enemies = pg.sprite.Group()

    player = Player((100, 300), all_sprites)
    enemy = Enemy((320, 240), all_sprites, enemies)

    done = False

    while not done:
        for event in pg.event.get():
            if event.type == pg.QUIT:
                done = True
            elif event.type == pg.MOUSEMOTION:
                player.rect.center = event.pos

        all_sprites.update()
        collided_sprites = pg.sprite.spritecollide(
            player, enemies, False, collided=hitbox_collision)
        for enemy_sprite in collided_sprites:
            print(enemy_sprite)

        screen.fill((30, 30, 30))
        all_sprites.draw(screen)
        pg.draw.rect(screen, (0, 255, 0), player.rect, 1)
        pg.draw.rect(screen, (255, 0, 0), player.hitbox, 1)

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


if __name__ == '__main__':
    pg.init()
    main()
    pg.quit()

If you want a circular collision area, you can just pass the pygame.sprite.collide_circle function as the collided argument.

Give your sprites a self.radius attribute,

class Player(pg.sprite.Sprite):

    def __init__(self, pos, *groups):
        super().__init__(*groups)
        self.radius = 100

and in the main loop pass collide_circle to spritecollide.

collided_sprites = pg.sprite.spritecollide(
    player, enemies, False, collided=pg.sprite.collide_circle)


回答2:

So, there's a couple things here. Starting with your second question (as I understand it from how you wrote it), you're correct to want to get rid of the 100 pixels. Having "100" randomly in your code is what's known as a Magic Number/Constant and is bad programming style for a few reasons:

  1. There is no indication anywhere what that 100 is supposed to represent
  2. In many cases (and yours specifically) Magic Numbers get replicated in multiple places. When that number changes (9-out-of-10 times, it does), you'll have to dig through all the code as well as related scripts to update the number and hope you didn't miss it anywhere
  3. Related to 2, you may find out that you want that number (in this case, the hitbox/aura radius) to change often. Hardcoding it makes that difficult or impossible.

Chances are, this section of code is better off as its own function anyway, so you can simply have "hitradius" as a keyword parameter and then substitute "hitradius" for 100.

Either alongside or in place of this, you can track hitradius as an attribute of another object (i.e., the player or other sprite) and then substitute it in the same manner (e.g.- sprite.x - player.hitradius > player.x [etc]). If you're sure that it's never going to change, then have it as an accessible variable somewhere and use the same technique.

def check_aura_collision(player,sprite,hitradius = 100):
    """ Checks if the player sprite is within hitradius (default 100)
        pixels of another sprite.
    """
    if (sprite.x + hitradius > player.x > sprite.x - hitradius )\
        and (sprite.y + hitradius > player.y > sprite.y - hitradius ):

        print("hit")
        return True
    return False

As for simplifying the logic, it's simple enough already; I might have used absolute value instead, but that's not really a meaningful change. Conversely, however, you may be interested in making it slightly more complex by using some basic trig to check a circular aura instead of the square aura you have right now.