使用Pygame处理精灵和碰撞

时间:2015-04-24 21:57:39

标签: python pygame sprite

我正在使用pygame学习python,我正在研究涉及精灵和碰撞的事情。我看过一些例子,但我还是不太了解它。我试图做的是当用户按下" ="时能够添加精灵(球)。键,也可以删除按下" - "时添加的最后一个精灵。我无法删除最后一个,我只能删除所有这些。

到目前为止,我已经能够将球添加到窗口并让它们从墙壁和彼此(有点)反弹。当2个球碰撞时,它们不会完全接触但它们会反弹。有时球会卡住并且不会移动,有时球会从框架上反弹,而这些框架并不是他们想要的。

这是我第一次与精灵小组合作,并希望得到任何帮助/指导,使这项工作顺利进行。谢谢。

代码:

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

2 个答案:

答案 0 :(得分:1)

删除精灵

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

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 ,我们只会反转速度,速度朝这个方向更深。现在,对于你的精灵,你有两个选择,就像边界一样:

  • 仅在无法碰撞的空白处发起新的ball
  • 采用某种方式计算正确的速度调整,只有当速度向相反方向移动时才应用它。

根据我在文档中看到的内容,sprite.Group看起来像是用于检查精灵是否重叠,而不是用于物理模拟。我建议对2d物理模拟进行一些研究,以便对要在对象之间进行通信的信息进行很好的概念化。我确定那里有一些不错的教程。

最后,要解决你的另一个问题,即当他们看起来没有碰到时,他们为什么会发生碰撞。 sprite.spritecollide返回哪些精灵有rectangles相交。如果ball.png为透明度设置了颜色键,则不会影响精灵的rect。 Pygame似乎具有专门用于处理collided sprite.spritecollide关键字中此问题的功能:

  

pygame.sprite.spritecollide()

     
      
  • 在与另一个精灵相交的组中查找精灵。
  •   
     

spritecollide(sprite,group,dokill,collided = None) - &gt; Sprite_list

     

碰撞参数是一个回调函数,用于计算两个精灵是否正在碰撞。它应该将两个精灵作为值,并返回一个bool值&gt;表示它们是否发生碰撞。如果未通过碰撞,则所有精灵必须具有“矩形”值,该值是精灵区域的矩形,其将用于计算碰撞。   碰撞的callables:

     
      
  • collide_rect
  •   
  • collide_rect_ratio
  •   
  • collide_circle
  •   
  • collide_circle_ratio
  •   
  • collide_mask
  •   

来自pygame文档的内容。 collide_circle函数的文档指出您的精灵应该具有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 :(得分:0)

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

  1. 您只想删除一个精灵,而不是删除精灵组中的所有精灵
  2. 如果查看pygame文档,可以看到spritegroup.remove有一个可选参数。您可以通过将所需的精灵作为参数来删除单个精灵,例如myspritegroup.remove(mysprite)。

    1. 您遇到了碰撞问题
    2. 只要在创作过程中球不会彼此产生,你可以简单地检查,你的碰撞对我有效。祝你好运:)