You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Pygame Sprite问题:关卡4的Box移除列表后仍显示在屏幕

Pygame关卡切换后Box Sprite未消失问题修复

问题描述

我正在开发一款带关卡(lvl)的2D游戏,设定为lvl4时显示一个Box Sprite,通关lvl4后该Box需从boxes列表移除并消失。目前Box已从列表中移除,但仍显示在屏幕上,切换到lvl5时boxes列表为空,但Box Sprite未消失。

相关代码

import pygame

pygame.init()

clock = pygame.time.Clock()

win_res = [800, 600]

window = pygame.display.set_mode(win_res)
pygame.display.set_caption("Mini Golf")

bg_color = (120, 200, 255)
bg_image = pygame.image.load("data/images/background.png")
ground = pygame.image.load("data/images/ground.png").convert_alpha()
ground.set_colorkey((255, 255, 255))


class Sling_Shot(pygame.sprite.Sprite):
    def __init__(self, x, y, vel):
        super().__init__()
        self.image = pygame.image.load("data/images/sling_shot.png").convert_alpha()
        self.image.set_colorkey((255, 255, 255))
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.vel = vel


class Stone(pygame.sprite.Sprite):
    def __init__(self, x, y, vel):
        super().__init__()
        self.image = pygame.image.load("data/images/stone.png")
        self.image.set_colorkey((255, 255, 255))
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.vel = vel
        self.fired = False

    def update(self):
        if self.fired:
            self.rect.y -= self.vel


class Bird(pygame.sprite.Sprite):
    def __init__(self, x, y, vel, moving, direction):
        super().__init__()
        self.image = pygame.image.load("data/images/bird.png").convert_alpha()
        self.image.set_colorkey((255, 255, 255))
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.vel = vel
        self.moving = moving
        self.dir = direction

    def update(self):
        if self.moving:
            self.rect.x += self.vel * self.dir
            if self.rect.x <= 100 or self.rect.x + self.rect.width >= 700:
                self.vel = -self.vel


def lvl_selector(lvl):
    birds = []

    match lvl:
        case 1:
            bird = Bird(365, 85, 2, False, 1)
            birds.append(bird)

        case 2:
            bird = Bird(365, 85, 2, True, 1)
            birds.append(bird)
            bird2 = Bird(365, 85, 2, True, 1)
            birds.append(bird2)

        case 3:
            bird = Bird(365, 85, 2, True, 1)
            bird2 = Bird(365, 205, 2, True, -1)
            birds.append(bird)
            birds.append(bird2)

        case 4:
            bird = Bird(365, 85, 2, True, 1)
            birds.append(bird)

        case 5:
            bird = Bird(365, 85, 2, True, -1)
            birds.append(bird)

    return birds


class Box(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        self.image = pygame.image.load("data/images/box.png").convert_alpha()
        self.image.set_colorkey((255, 255, 255))
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y


def box_selector(lvl):
    boxes = []

    match lvl:
        case 4:
            box = Box(590, 119)
            boxes.append(box)

        case _:
            boxes = []

    return boxes


def game_loop():
    sling_shot = Sling_Shot(375, 500, 5)
    stone = Stone(387.5, 485, 5)

    lvl = 3

    boxes = []

    all_sprites = pygame.sprite.Group()
    all_sprites.add(sling_shot, stone)
    birds = lvl_selector(lvl)
    for bird in birds:
        all_sprites.add(bird)

    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            if event.type == pygame.MOUSEBUTTONUP:
                stone.fired = True

        x = pygame.mouse.get_pos()[0]  # Get only the x-coordinate of the mouse

        if 120 <= x <= 670:
            sling_shot.rect.x = x - 25

        if not stone.fired:
            stone.rect.x = sling_shot.rect.x + 17.5

        if pygame.sprite.collide_rect(stone, sling_shot) and stone.vel < 0:
            stone.vel = -stone.vel  # Stop the stone when it hits the slingshot
            stone.rect.y = sling_shot.rect.y - 15
            stone.fired = False

            # Reset the stone's position if needed
            if not stone.fired:
                stone.rect.x = sling_shot.rect.x + 17.5

        for bird in birds.copy():
            if pygame.sprite.collide_rect(stone, bird):
                bird.kill()
                birds.remove(bird)
                if not birds:
                    stone.rect.y = sling_shot.rect.y-12.5
                    stone.rect.x = sling_shot.rect.x
                    stone.fired = False
                    lvl += 1
                    birds = lvl_selector(lvl)
                    boxes = box_selector(lvl)

        print(len(boxes))

        for bird in birds:
            all_sprites.add(bird)

        for box in boxes:
            if pygame.sprite.collide_rect(stone, box):
                stone.vel = -stone.vel

        if lvl == 4:
            for box in boxes:
                all_sprites.add(box)
            for bird in birds:
                if bird.rect.x + bird.rect.width >= 580 and bird.rect.y == 85:
                    bird.vel = -bird.vel

        if stone.rect.y <= 0 or stone.rect.y + stone.rect.width >= win_res[1]:
            stone.rect.y = sling_shot.rect.y - 12.5
            stone.rect.x = sling_shot.rect.x
            stone.fired = False

            # Clear the old birds
            for bird in birds:
                bird.kill()
            birds.clear()

            # Add new birds for the current level
            birds.extend(lvl_selector(lvl))
            boxes = box_selector(lvl)

        window.blit(bg_image, (0, 0))
        window.blit(ground, (0, 40))

        all_sprites.draw(window)
        all_sprites.update()

        pygame.display.update()
        clock.tick(60)

    pygame.quit()


game_loop()

问题原因

核心问题是:切换关卡时,仅清空了boxes列表,但之前添加到all_sprites组中的Box Sprite实例并未被移除。all_sprites.draw()会绘制组内所有存在的Sprite,所以即使列表为空,组里的Box还在,就会继续显示。

修复方案

1. 通关升级关卡时清理旧Box

找到通关后升级关卡的代码块,添加移除旧Box的逻辑:

if not birds:
    stone.rect.y = sling_shot.rect.y-12.5
    stone.rect.x = sling_shot.rect.x
    stone.fired = False
    # 移除所有旧的Box Sprite
    for box in boxes:
        box.kill()
    lvl += 1
    birds = lvl_selector(lvl)
    boxes = box_selector(lvl)

2. 石头飞出边界重置关卡时清理旧Box

在石头飞出边界重置关卡的代码中,同样添加Box清理逻辑:

# Clear the old birds
for bird in birds:
    bird.kill()
birds.clear()
# 清理旧的Box Sprite
for box in boxes:
    box.kill()
# Add new birds for the current level
birds.extend(lvl_selector(lvl))
boxes = box_selector(lvl)

3. 优化Box添加逻辑(可选)

当前代码在lvl == 4的判断里每次循环都执行all_sprites.add(box),会重复添加同一个Box到组中。可以改为在获取新关卡的boxes后统一处理:

# 替换原来lvl==4的判断代码
# 先移除所有旧的Box
for sprite in all_sprites.sprites():
    if isinstance(sprite, Box):
        sprite.kill()
# 添加当前关卡的Box
for box in boxes:
    all_sprites.add(box)
# lvl4的bird逻辑单独保留
if lvl == 4:
    for bird in birds:
        if bird.rect.x + bird.rect.width >= 580 and bird.rect.y == 85:
            bird.vel = -bird.vel

修复原理

Pygame的Sprite.kill()方法会将Sprite从所有它所属的Group中移除,这样all_sprites.draw()就不会再绘制这个Box,从而实现切换关卡后Box消失的效果。

内容的提问来源于stack exchange,提问作者Ibtasaam Mughal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 11:54:53