使用 Pygame 处理精灵和碰撞

Dealing with Sprites and Collisions Using Pygame

我正在学习 python 使用 pygame 并且我正在研究涉及精灵和碰撞的东西。我看过一些例子,但我还是不太明白。我正在尝试做的是能够在用户按下“=”键时添加精灵(一个球),并且能够在按下“-”时删除最后添加的精灵。我不能只删除最后一个,我只能删除所有的。

到目前为止,我已经能够将球添加到 window 并让它们从墙壁上弹回(某种程度上)。当两个球相撞时,它们并没有完全接触但会弹开。有时球会卡住而不会移动,有时球会从框架上弹开,这是它们不应该发生的。

这是我第一次使用 sprite 组,非常感谢任何 help/guidance 参与这项工作 smoothly.Thanks。

代码:

ball.py

import pygame
from pygame.locals import *


class Ball(pygame.sprite.Sprite):
    def __init__(self, x, y, vx, vy):

        super().__init__();
        self.image = pygame.image.load("ball.png").convert()


        self.image.set_colorkey(pygame.Color(0, 0, 0))

        self.rect = self.image.get_rect()

        self.rect.x = x
        self.rect.y = y
        self.vx = vx
        self.vy = vy

    def draw(self, SCREEN):
        SCREEN.blit(self.image, (self.rect.x, self.rect.y))

    def move(self, SCREEN, balls):
        l_collide = self.rect.x + self.image.get_width() + self.vx > SCREEN.get_width()
        r_collide = self.rect.x + self.vx < 0
        t_collide = self.rect.y + self.vy < 0
        b_collide = self.rect.y + self.image.get_height() + self.vy > SCREEN.get_height()

        a = pygame.sprite.spritecollide(self, balls, False, False)

        if len(a) > 1:
            self.vx *= -1
            self.vy *= -1



        if l_collide or r_collide:
            self.vx *= -1


        if t_collide or b_collide:
            self.vy *= -1

        self.rect.x += self.vx
        self.rect.y += self.vy

ball_animation.py

import pygame
import sys
import random
import math
from pygame.locals import *
from ball.ball import Ball
from random import randint

def ball_list(num):
    ball_list = pygame.sprite.Group()

    for x in range(num):
        rand_x = random.randint(0,400)
        rand_y = random.randint(0,400)
        vx = 4
        vy = 5

        ball_list.add(Ball(rand_x, rand_y, vx, vy))

    return ball_list

def main():
    pygame.init()

    FPS = 30
    FPS_CLOCK = pygame.time.Clock()

    # COLOR LIST
    BLACK = pygame.Color(0, 0, 0)

    # Code to create the initial window
    window_size = (500, 500)
    SCREEN = pygame.display.set_mode(window_size)

    # set the title of the window
    pygame.display.set_caption("Bouncing Ball Animation")

    # change the initial background color to white
    SCREEN.fill(BLACK)

    balls = ball_list(0)

    while True:  # <--- main game loop
        for event in pygame.event.get():
            if event.type == QUIT:  # QUIT event to exit the game
                pygame.quit()
                sys.exit()
            if event.type == KEYDOWN:
                if event.key == K_EQUALS:
                    balls.add(Ball(randint(0,400),randint(0,400), 4,5))
                if event.key == K_MINUS:
                    try:
                        balls.remove()
                    except IndexError:
                        print('There is no balls to take!')




        SCREEN.fill(BLACK)
        for x in balls:
            x.move(SCREEN,balls)
            x.draw(SCREEN)

        pygame.display.update()  # Update the display when all events have been processed
        FPS_CLOCK.tick(FPS)

if __name__ == "__main__":
    main()

按下时删除精灵

问题是 sprite.Group.remove(sprites) 希望您指定它应该删除哪些精灵。 sprites 这里应该是 sprite/list 个你想从组中删除的精灵。这意味着要删除在按键时添加的最后一个球,您需要保留一个球精灵列表和 pop() 最近添加的项目,然后使用 pop() 的结果作为精灵从组中删除。 sprite.Group 有一个 .sprites() 方法,它 returns 组中所有精灵的列表,按照它们被添加的顺序。这个列表是从组中生成的,实际上并不是它的接口,所以对这个列表做的事情不会影响组。但是我们仍然可以使用它来获取最后添加的精灵。这是它的样子:

elif event.key == K_0:
    try:
        sprite_list = balls.sprites()
        to_remove = sprite_list[-1] # Get last element of list
        balls.remove(to_remove)
    except IndexError:
        print('There is no balls to take!')

碰撞

所以这有点复杂,在您的代码中修复起来并不那么简单。要了解问题所在,请查看您的碰撞速度调整对屏幕边框情况的实际影响。

l_collide = self.rect.x + self.image.get_width() + self.vx > SCREEN.get_width()
r_collide = self.rect.x + self.vx < 0
t_collide = self.rect.y + self.vy < 0
b_collide = self.rect.y + self.image.get_height() + self.vy > SCREEN.get_height()

#################

if l_collide or r_collide:
    self.vx *= -1

if t_collide or b_collide:
    self.vy *= -1

考虑代码中的单个时间步长。我们检查精灵是否位于边界的边缘 任意数量。如果它悬空,我们反转速度。在某些情况下,您的边缘检查会给您带来麻烦。如果你的self.vx小于你当前位置X与x维度边界之间的差值,你将反转你的速度,行进self.vx返回 朝向边界,但不要让它过去。在下一个时间步,你会看到你仍然越界,你的程序将再次反转self.vx,实际上将你送走从边界。在这种情况下,您将通过 self.vx 来回绑定每个时间步长。通常这不会发生在你的代码中,除非你在边界上生成一个新的 ball 精灵,比你为那个球的 self.vxself.vy 更远。这可以通过确保你不会从边缘生成球来解决,或者更好的是,只在需要时反转你的速度。

if (l_collide and self.vx>0) or (r_collide and self.vx<0):
    self.vx *= -1


if (t_collide and self.vy<0) or (b_collide and self.vy>0):
    self.vy *= -1

请注意,如果我们越过边缘,我们只会反转速度 AND 速度朝那个方向更深。现在对于你的精灵你有两个选择,就像边界一样:

  • 仅在无法碰撞的空 space 中发起新的 ball
  • 实施某种方法来计算正确的速度调整,并且仅在速度朝向相反方向时应用它。

从我在文档中读到的内容来看,sprite.Group 看起来是为了检查精灵是否重叠,而 不是 用于物理模拟。我建议对 2d 物理模拟进行一些研究,以便对您应该希望在对象之间进行通信的信息有一个很好的概念化。我敢肯定那里有一些不错的教程。

最后,解决你的另一个问题,即为什么它们在看起来没有接触的情况下会发生碰撞。 sprite.spritecollide 正在返回哪些精灵具有 rectangles 相交。如果您的 ball.png 为透明度设置了颜色键控,这不会影响精灵的 rect。 Pygame 似乎在 sprite.spritecollidecollided 关键字中实现了旨在处理此问题的功能:

pygame.sprite.spritecollide()

  • Find sprites in a group that intersect another sprite.

spritecollide(sprite, group, dokill, collided = None) -> Sprite_list

The collided argument is a callback function used to calculate if two sprites >are colliding. it should take two sprites as values, and return a bool value >indicating if they are colliding. If collided is not passed, all sprites must >have a “rect” value, which is a rectangle of the sprite area, which will be >used to calculate the collision. collided callables:

  • collide_rect
  • collide_rect_ratio
  • collide_circle
  • collide_circle_ratio
  • collide_mask

来自 pygame 文档。 collide_circle 函数的文档指出您的 sprite 应该具有 radius 属性,否则将计算一个以适合圆内的整个矩形。因此,在您的 Ball.__init__ 函数中,我建议添加:

self.radius = self.rect.width/2 

这将使 collide_circle 使用近似于您的 ball 图像的半径,假设它是居中的圆形并占据整个图像。接下来,您必须通过更改将碰撞规范添加到碰撞检查中:

a = pygame.sprite.spritecollide(self, balls, False, False)

a = pygame.sprite.spritecollide(self, balls, False, pygame.sprite.collide_circle)

如果你解决了不在彼此内部生成新的 ball 对象的问题,这一切应该都能很好地工作。如果您不能让它们在彼此内部生成,请考虑使用不同的数据结构或不同的碰撞检查方式来获得您想要的结果。祝你好运!

我在你的文字中看到了两个问题

  1. 您只想删除一个精灵,而不是精灵组中的所有精灵

如果您查看 pygame 文档,您会发现 spritegroup.remove 有一个可选参数。您可以通过将所需的精灵作为参数来删除单个精灵,例如 myspritegroup.remove(mysprite)。

  1. 您遇到碰撞问题

只要球在创建时不会相互重叠,您的碰撞就对我有效,您可以简单地检查一下。祝你好运:)