多个子弹在添加到Pygame精灵组并更新时不显示

0 投票
1 回答
526 浏览
提问于 2025-04-18 17:31

我正在做我的第一个Python/Pygame项目,这是一个射击风格的游戏。不过,当我创建多个子弹的实例并把它们添加到精灵组时,屏幕上只显示最新创建的那一个实例。也就是说,任何时候只会显示一颗子弹。

我觉得第175到180行,或者在Bullet类里面的代码可能出了问题。

我的代码:

import pygame, random , sys , time
from pygame.locals import *

# Screen dimensions
SCREEN_WIDTH = 640
SCREEN_HEIGHT = 480

# Global constants
WHITE     = (255, 255, 255)
BLACK     = (  0,   0,   0)
LIGHTBLUE = (  0,   0, 155)
FPS = 60

class Player(pygame.sprite.Sprite):

    # set speed vector of the player
    change_x = 0
    change_y = 0
    moverate = 5
    # Constructor. Pass in x and y position
    def __init__(self, x, y):
        # Call the parent class (Sprite) constructor
        pygame.sprite.Sprite.__init__(self)

        # Create player image
        self.image = pygame.image.load('player.png')
        self.image.set_colorkey(WHITE)

        # Set a referance to the image rect.
        self.rect = self.image.get_rect()
        self.rect.centerx = x
        self.rect.y = y

    def changespeed(self, x, y):
        """ Change the speed of the player"""
        self.change_x += x
        self.change_y += y

    def update(self):
        """ Move the player. """

        # Move left/right

        self.rect.x += self.change_x

        # Move up/down
        self.rect.y += self.change_y


    def stop(self):
        """ Called when the user lets off the keyboard."""
        self.change_x = 0
        self.change_y = 0
        self.image = pygame.image.load('player.png')
        self.image.set_colorkey(WHITE)

class Enemy(pygame.sprite.Sprite):
    """ This class represents the enemy sprites."""
    minmoverate = 1
    maxmoverate = 8

    def __init__(self):
        # Call the parent class (Sprite) constructor
        pygame.sprite.Sprite.__init__(self)

        self.image = pygame.image.load('enemyShip.png')
        self.image = pygame.transform.scale(self.image, (50, 50))
        self.image.set_colorkey(WHITE)

        self.rect = self.image.get_rect()

    def reset_pos(self):
        """ Reset position to the top of the screen, at a random x location.
        Called by update() or the main program loop if there is a collision."""

        self.rect.y = - ( SCREEN_HEIGHT / 4)
        self.rect.x = random.randrange(SCREEN_WIDTH)


    def update(self):
        """ Move the enemies. """
        # Move down, at some speed
        self.rect.y += 2
        # Move left and right, at some speed
        self.rect.x += 0

        # If enemy is too far down, reset to top of screen
        if self.rect.y > SCREEN_HEIGHT:
            self.reset_pos()



class Bullet(pygame.sprite.Sprite):
    """ This class represents the bullet. """
    def __init__(self):
        # Call the parent class (Sprite) constructor
        pygame.sprite.Sprite.__init__(self)

        self.image = pygame.Surface([8, 20])
        self.image.fill(LIGHTBLUE)

        self.rect = self.image.get_rect()

    def update(self):
        """ Move the bullet. """
        self.rect.y -= 10

class Game(object):
    """ This class represents an instance of the game. If we need to
        rest the game we'd just need to create a new instance of this class."""

    # --- Class attributes.

    # Sprite lists
    enemy_list = None
    bullet_list = None
    all_sprites_list = None


    # --- Class methods
    # Set up the game
    def __init__(self):
        self.score = 0
        self.game_over = False

        # Create sprite lists
        self.enemy_list = pygame.sprite.Group()
        self.bullet_list = pygame.sprite.Group()
        self.all_sprites_list = pygame.sprite.Group()

        # Create the starting enemy ships
        for i in range(15):
            enemy = Enemy()

            enemy.rect.x = random.randrange(SCREEN_WIDTH)
            enemy.rect.y = random.randrange(-300, 20)

            self.enemy_list.add(enemy)
            self.all_sprites_list.add(enemy)

        # Create the player
        self.player = Player(SCREEN_WIDTH / 2, SCREEN_HEIGHT - (SCREEN_HEIGHT / 6))
        self.all_sprites_list.add(self.player)


    def process_events(self):
        """ Process all of the events. Return "True" if we need to close the window."""

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                return True

            elif event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    return True
                elif event.key == K_RETURN:
                    if self.game_over:
                        self.__init__()
                elif event.key in (K_RIGHT ,K_d):
                    self.player.changespeed( self.moverate ,0)
                elif event.key in (K_LEFT ,K_a):
                    self.player.changespeed( -self.moverate ,0)
                elif event.key in (K_UP , K_w):
                    self.player.changespeed(0, -self.moverate)
                elif event.key in (K_DOWN , K_s):
                    self.player.changespeed(0, self.moverate)
                elif event.key == K_SPACE: # Fire bullet
                    bullet = Bullet(0
                    # Set bullet so it is where the player is
                    bullet.rect.centerx = self.player.rect.centerx 
                    bullet.rect.y = self.player.rect.y

                    # Add bullet to lists
                    self.all_sprites_list.add(bullet)
                    self.bullet_list.add(bullet)

            elif event.type == KEYUP:
                if event.key in (K_RIGHT ,K_d):
                    self.player.changespeed( -self.moverate ,0)
                elif event.key in (K_LEFT ,K_a):
                    self.player.changespeed( self.moverate ,0)
                elif event.key in (K_UP , K_w):
                    self.player.changespeed(0, self.moverate)
                elif event.key in (K_DOWN , K_s):
                    self.player.changespeed(0, -self.moverate)



    def run_logic(self):
        """ This method is run each time through the frame.
            It updates positions and checks for collisions."""
        enemy = Enemy()
        if not self.game_over:
            # Move all the sprites
            self.all_sprites_list.update()

            if len(self.all_sprites_list) < 17:
                self.enemy_list.add(enemy)
                self.all_sprites_list.add(enemy)
                enemy.rect.x = random.randrange(SCREEN_WIDTH)
                enemy.rect.y = random.randrange(-100, -50)

            # Bullet Mechanics
            for bullet in self.bullet_list:
                # See if the bullets has collided with anything.
                self.enemy_hit_list = pygame.sprite.spritecollide(bullet, self.enemy_list, True)

                # For each enemy hit, remove bullet and enemy and add to score
                for enemy in self.enemy_hit_list: 
                    self.bullet_list.remove(bullet)
                    self.all_sprites_list.remove(bullet)
                    self.score += 1

                # Remove the bullet if it flies up off the screen
                if bullet.rect.y < -10:
                    self.bullet_list.remove(bullet)
                    self.all_sprites_list.remove(bullet)

            # Player Mechanics
            for enemy in self.enemy_list:
                # See if player has collided with anything.
                self.player_hit_list = pygame.sprite.spritecollide(self.player, self.enemy_list, True)

                if len(self.player_hit_list) == 1:
                    # If player is hit, show game over.
                    self.game_over = True

    def display_frame(self, screen):
        """ Display everything to the screen for the game. """
        screen.fill(BLACK)

        if self.game_over:
            # font = pygame.font.Font("Serif:, 25)
            font = pygame.font.SysFont("serif", 25)
            text = font.render("Game Over! You scored " + str(self.score) +" points, press Enter to restart", True, WHITE)
            center_x = (SCREEN_WIDTH // 2) - (text.get_width() // 2)
            center_y = (SCREEN_HEIGHT // 2) - (text.get_height() // 2)
            screen.blit(text, [center_x, center_y])

        if not self.game_over:
            self.all_sprites_list.draw(screen)

        pygame.display.flip()

def main():
    """ Main program function. """
    # Initialize Pygame and set up the window
    pygame.init()

    size = [SCREEN_WIDTH, SCREEN_HEIGHT]
    screen = pygame.display.set_mode(size)
    screen_rect = screen.get_rect()
    pygame.display.set_caption("My Game")
    pygame.mouse.set_visible(False)

    # Create our objects and set the data
    done = False
    clock = pygame.time.Clock()

    # Create an instance of the Game class
    game = Game()

    # Main game loop
    while not done:

        # Process events (keystrokes, mouse clicks, etc)
        done = game.process_events()

        # Update object positions, check for collisions
        game.run_logic()

        # Draw the current frame
        game.display_frame(screen)

        # Pause for the next frame
        clock.tick(FPS)

    # Close window and exit
    pygame.quit()

# Call the main function, start up the game
if __name__ == "__main__":
    main()

1 个回答

0

问题在于你只创建了一个 Bullet 实例,并把它存储在 Game.bullet 这个类变量里。每次你开火时,代码只是把这个唯一的子弹移动到玩家的位置,然后从那里更新。

你可能想要为每一次射击创建一个新的子弹。在 process_events 中使用类似下面的代码:

            elif event.key == K_SPACE: # Fire bullet
                bullet = Bullet()
                bullet.rect.centerx = self.player.rect.centerx 
                bullet.rect.y = self.player.rect.y

                # Add bullet to lists
                all_sprites_list.add(self.bullet)
                bullet_list.add(self.bullet)

这样每次按下空格键时就会创建一个新的子弹。如果你的游戏设计规定了同时“活动”的子弹数量上限,你可能需要一些更复杂的逻辑(或者如果创建和销毁很多实例的性能开销太大,你可以写一些对象缓存的代码),但这应该能让你走上正轨。

不过,上面的代码并没有处理后续删除子弹的事情,所以你可能还需要处理这个问题,否则你的游戏会因为计算屏幕外子弹而变得很卡。我建议在 Bullet.update() 中加入一些逻辑,如果子弹在屏幕外,就调用 self.kill()(或者在适合你游戏的情况下)。当没有更多引用指向这个实例时,它会被自动回收。

我还建议你去掉 Game 类中的所有类变量,因为它们要么是多余的(被在 __init__ 中创建的实例变量遮蔽了),要么是有问题的(比如 bullet)。

撰写回答