pygame精灵的碰撞不起作用

5

我正在为我的Python编程课制作一个太空飞船游戏,我想使用sprite.spritecollideany检查我的玩家精灵spaceship是否与spriteGroup组中的任何行星精灵发生碰撞,但不管我做什么,似乎都不起作用。

这是代码:

import pygame
import random
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption("Asteroids and Spaceships")

background = pygame.image.load("background.png")
background = background.convert()

white = 255,255,255

#first I make the asteroids with this class.
class asteroids(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.x = x
        self.y = y
        self.width = 30
        self.height = 30

        self.i1 = pygame.image.load("smallasteroid.png")
        self.i1 = self.i1.convert()
        self.i1.set_colorkey(white)
        self.rect = self.i1.get_rect()
        self.rect.left = x
        self.rect.top = y

        self.i2 = pygame.image.load("smallasteroid2.png")
        self.i2 = self.i2.convert()
        self.i2.set_colorkey(white)
        self.rect = self.i2.get_rect()
        self.rect.left = x
        self.rect.top = y

        self.i3 = pygame.image.load("mediumasteroid.png")
        self.i3 = self.i3.convert()
        self.i3.set_colorkey(white)
        self.rect = self.i3.get_rect()
        self.rect.left = x
        self.rect.top = y

        self.current = 0

    def render(self, image_num):
        if image_num == 1:
            self.current = 1
        if image_num == 2:
            self.current = 2
        if image_num == 3:
            self.current = 3

    def update(self):
        if self.current == 1:
            screen.blit(self.i1, (self.x,self.y))
            self.y += random.randint(7,11)
            if self.y > screen.get_height():
                self.y = 0

        if self.current == 2:
            screen.blit(self.i2, (self.x,self.y))
            self.y += random.randint(5,9)
            if self.y > screen.get_height():
                self.y = 0

        if self.current == 3:
            screen.blit(self.i3, (self.x,self.y))
            self.y += random.randint(3,6)
            if self.y > screen.get_height():
                self.y = 0

#and then this is the class for the spaceship   
class spaceship(pygame.sprite.Sprite):
    def __init__(self, x, y):
        pygame.sprite.Sprite.__init__(self)
        self.x = x
        self.y = y
        self.width = 40
        self.height = 60
        self.image = pygame.image.load("spaceship.png")
        self.image = self.image.convert()
        self.image.set_colorkey(white)
        self.rect = self.image.get_rect()
        self.rect.left = x
        self.rect.top = y

    def update(self):
        screen.blit(self.image,(self.x,self.y))
        if self.y > screen.get_height():
            self.y = 0
        if self.y < screen.get_height()-610:
            self.y = screen.get_height()
        if self.x > screen.get_width():
            self.x = 0
        if self.x < screen.get_width()-610:
            self.x = screen.get_width()

#main is where I have the game run
def main():
    x_ship, y_ship = 0,0
    player = spaceship(300,550)

    spriteGroup = pygame.sprite.Group() #my asteroid sprites are grouped here

    for i in range(25):
        image_num = random.randint(0,3)
        asteroid = asteroids(random.randint(0,500),random.randint(0, 200))
        asteroid.render(image_num)
        spriteGroup.add(asteroid)

    clock = pygame.time.Clock()
    keepGoing = True


    while keepGoing:
        #this is what I tried to use to check for collisions. I know it's not working but I don't know why.
        if pygame.sprite.spritecollideany(player,spriteGroup):
            #the program quits on collision.
            pygame.quit()
        for event in pygame.event.get():
            if (event.type == pygame.QUIT):
                keepGoing = False
            elif event.type == pygame.KEYUP:
                if event.key == pygame.K_ESCAPE:
                    keepGoing = False

            if (event.type == pygame.KEYDOWN):
                if (event.key == pygame.K_LEFT):
                    x_ship = -4
                if (event.key == pygame.K_RIGHT):
                    x_ship = 4
                if (event.key == pygame.K_UP):
                    y_ship = -4
                if (event.key == pygame.K_DOWN):
                    y_ship = 4

            if (event.type==pygame.KEYUP):
                if (event.key==pygame.K_LEFT):
                    x_ship = 0
                if (event.key==pygame.K_RIGHT):
                    x_ship = 0 
                if (event.key==pygame.K_UP):
                    y_ship = 0
                if (event.key==pygame.K_DOWN):
                    y_ship = 0

        player.x += x_ship
        player.y += y_ship
        screen.blit(background, (0,0))
        spriteGroup.clear(screen, background)
        player.update()
        spriteGroup.update()
        clock.tick(50)
        pygame.display.flip()

    pygame.quit()

main()

我弄不清楚为什么碰撞没有生效。


为了以后的参考,你可以简要提及一些你尝试过的事情或者你认为问题可能出在哪里。你也可以发布一些准备好的代码和占位符资产(例如self.i1 = pygame.Surface((30,30)); self.i1.fill((255,25,25))而不是实际图像,因为我们手头没有这些图像),这样我们就可以运行它而不需要太多额外的编辑。我保留了原始的图像引用,这样对你仍然有意义,但下次这可能会帮助你(和我们)更好地理解问题! - Augusta
糟糕,我没有想到那个。谢谢你的建议,下次我发帖的时候会记住的! - user4067584
没问题!:D 如果您想尝试简化代码,也可以考虑清除冗余和已知功能的代码,例如asteroids.__init__中的i2i3数据,只要您确定它是辅助的而不是与错误有关的。 (有时候您可能很匆忙,只想完成它。) - Augusta
1个回答

4
你的问题在于,无论是spaceship类还是asteroids类,它们都没有在update函数中更新自己的rect(即它们的碰撞框),而且它们的xy属性与该rect的位置没有直接或自动的连接关系。
如果你在两个类的update函数末尾添加类似self.rect.topleft = self.x, self.y的代码,它们各自的rect——也就是它们的碰撞框——将会移动到应该在的位置,而不是停留在初始化位置(在这种情况下,player的初始位置是(300,550),每个小行星有一个半随机的偏移量,我不确定具体在哪里;我只是粗略地复制了你的代码,然后测试了一些想法。对于没有找到问题的确切来源,我感到抱歉...)
总之,简短的答案是,虽然你已经检查了每个角色的x和y位置,但你并没有告诉pygame实际上要把这个位置应用到碰撞框上,并且sprite.spritecollideany始终为假,因为碰撞框实际上从未接触过。 在每个角色类的update函数末尾添加self.rect.topleft = self.x, self.y将会解决这个问题。(确保这行代码位于函数的末尾,并且在def update(self)内部的最低缩进级别!) 编辑 或者,你也可以在主循环中用类似下面的代码替换player.x = x_shipplayer.y = y_ship
while keepGoing:
   ...
    player.rect.move_ip(x_ship, y_ship)  # move the ship's rect
    player.x, player.y = player.rect.topleft  # update it's x and y, if you use these elsewhere

    for item in spriteGroup: # update the rects for your asteroid objects
        item.rect.topleft = item.x, item.y

我会使用“更新-改变”解决方案,因为当玩家接近游戏区域的边缘时,这种解决方案很可能会给您带来麻烦。但还有另一种选择。
作为建议,您可以将.x和.y重新定义为property,分别返回self.rect.left和self.rect.top,以便x和y值固定在碰撞框的左上角。还可以设置setter。
我不确定您将来可能如何使用这些参数;如果您愿意,完全可以将它们消除,并改用其rects的定位器。供您参考!
注意:
我还假设您所有精灵的x和y属性都指向其rect框的左上点。如果该点应该在其他位置(例如中心),则如果您决定使用此代码,则可能需要进行调整。

我按照你的建议去做了,现在程序有时能够捕捉到碰撞,但并不总是如此,我也不确定原因。我尝试添加一些检查小行星和飞船是否超出其他边界(底部和右侧)的内容,但这并没有帮助。我真的不知道该怎么办,因为它只能检测到某些碰撞 - 有时你可以从小行星下方飞过去而不会受到影响,然后你将接近一个小行星,游戏就像应该在小行星和飞船最初碰撞时退出一样退出了。 - user4067584
奇怪,我会为您检查一下。您使用了update函数解决方案还是其他替代方案? - Augusta
哦!你的新的self.rect.topleft = self.x, self.y这行代码难道不是在update函数的最低缩进级别吗?如果不是的话,它们可能是if语句块的一部分,并且只会在特定的asteroids.current值(可能是3)和当spaceship位于屏幕最左边时才执行。(我刚刚将这个细节编辑到解决方案中。) - Augusta

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接