毕达哥拉斯如何使精灵之间的光环碰撞?

时间:2021-10-14 12:45:45

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

这行得通,但有什么方法可以简化它?我还在想如何去掉100个像素

2 个解决方案

#1


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.

如果您想使用一个缩放的rect来进行碰撞检测,您可以对原始的rect进行膨胀(或者创建一个新的),并将它作为一个单独的属性分配给您的精灵(我在这里称之为hitbox)。原始矩形将只用于存储位置。

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.

创建一个自定义的碰撞检测函数,该函数必须作为碰撞参数传递给pygame.sprite。spritecollide或groupcollide。在这个函数中,您可以使用hitbox rect的colliderect方法来检查它是否与其他精灵的rect发生了碰撞。

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:

然后这样调用spritecollide:

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

这里有一个完整的例子(红色矩形是hitbox,绿色矩形是self。作为blit位置的rect):

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.

如果你想要一个圆形的碰撞区域,你可以通过pygame.sprite。collide_circle函数作为冲突的参数。

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.

在主环路中,将collide_circle转换为spritecollide。

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

#2


1  

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:

这里有一些东西。从你的第二个问题开始(我从你的写作方式中理解),你想要去掉100个像素是正确的。在代码中随机地使用“100”是一种神奇的数字/常量,由于以下几个原因,它是一种糟糕的编程风格:

  1. There is no indication anywhere what that 100 is supposed to represent
  2. 没有迹象表明这100代表什么
  3. 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
  4. 在许多情况下(尤其是你的)魔术数字会在很多地方被复制。当这个数字发生变化时(10次中有9次),您将不得不挖掘所有的代码以及相关的脚本来更新这个数字,并希望您不会错过它
  5. 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.
  6. 与2相关,你可能会发现你想要那个数字(在本例中,是hitbox/aura radius)经常改变。硬编码使这变得困难或不可能。

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.

很有可能,这部分代码作为它自己的函数会更好,所以您可以简单地将“hitradius”作为关键字参数,然后将“hitradius”替换为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.

无论是在旁边还是在这个位置上,您都可以将hitradius作为另一个对象的属性(即:然后以同样的方式(例如-精灵)替换它。x -球员。hitradius >的球员。x(等))。如果你确信它永远不会改变,那就把它作为一个可访问的变量,并使用相同的技术。

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.

要简化逻辑,已经够简单的了;我可以用绝对值来代替,但这并不是一个有意义的改变。相反地,你可能会对用一些基本的三角来检查圆形光环而不是你现在的方形光环感兴趣。

#1


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.

如果您想使用一个缩放的rect来进行碰撞检测,您可以对原始的rect进行膨胀(或者创建一个新的),并将它作为一个单独的属性分配给您的精灵(我在这里称之为hitbox)。原始矩形将只用于存储位置。

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.

创建一个自定义的碰撞检测函数,该函数必须作为碰撞参数传递给pygame.sprite。spritecollide或groupcollide。在这个函数中,您可以使用hitbox rect的colliderect方法来检查它是否与其他精灵的rect发生了碰撞。

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:

然后这样调用spritecollide:

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

这里有一个完整的例子(红色矩形是hitbox,绿色矩形是self。作为blit位置的rect):

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.

如果你想要一个圆形的碰撞区域,你可以通过pygame.sprite。collide_circle函数作为冲突的参数。

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.

在主环路中,将collide_circle转换为spritecollide。

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

#2


1  

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:

这里有一些东西。从你的第二个问题开始(我从你的写作方式中理解),你想要去掉100个像素是正确的。在代码中随机地使用“100”是一种神奇的数字/常量,由于以下几个原因,它是一种糟糕的编程风格:

  1. There is no indication anywhere what that 100 is supposed to represent
  2. 没有迹象表明这100代表什么
  3. 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
  4. 在许多情况下(尤其是你的)魔术数字会在很多地方被复制。当这个数字发生变化时(10次中有9次),您将不得不挖掘所有的代码以及相关的脚本来更新这个数字,并希望您不会错过它
  5. 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.
  6. 与2相关,你可能会发现你想要那个数字(在本例中,是hitbox/aura radius)经常改变。硬编码使这变得困难或不可能。

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.

很有可能,这部分代码作为它自己的函数会更好,所以您可以简单地将“hitradius”作为关键字参数,然后将“hitradius”替换为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.

无论是在旁边还是在这个位置上,您都可以将hitradius作为另一个对象的属性(即:然后以同样的方式(例如-精灵)替换它。x -球员。hitradius >的球员。x(等))。如果你确信它永远不会改变,那就把它作为一个可访问的变量,并使用相同的技术。

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.

要简化逻辑,已经够简单的了;我可以用绝对值来代替,但这并不是一个有意义的改变。相反地,你可能会对用一些基本的三角来检查圆形光环而不是你现在的方形光环感兴趣。