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

Python开发仿Cookie Clickers游戏:解决掉落Cookie闪烁问题

解决PyGame下落饼干闪烁问题

我正在用Python开发一款类似Cookie Clickers的游戏,想要实现获取饼干(通过点击或CPS)时,背景中生成随机X、Y位置的小饼干下落的功能。但当存储掉落饼干的数组中存在5个以上元素时,这些饼干会开始闪烁。我知道原因:重绘屏幕时先将背景填充为黑色,再逐个重绘所有元素,若数组中有100个饼干,最后一个可能会消失约1秒才被绘制。

原代码片段

主循环代码:

fcookies = []

while run:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

        if event.type == pygame.MOUSEBUTTONDOWN:
            if event.button != 1:
                if event.button != 3:
                    continue
            # pygame.mixer.Sound.stop(CRUNCH1)
            pos = pygame.mouse.get_pos()
            clicked_buttons = [s for s in buttons if s.rect.colliderect(pygame.Rect(pos[0], pos[1], 1, 1))]#s.rect.collidepoint(pos)]
            for button in clicked_buttons:
                if button.name == "cookie":
                    pygame.mixer.Sound.play(random.choice(CRUNCHSFX))
                    fookies.append(FallingCookie(WIN.get_width()))
                    cookies += cpc
                if button.name == "upgcookie":
                    if cookies >= upgradeB.price:
                        cookies -= upgradeB.price
                        upgradeB.price *= 2
                        upgrades += 1
                        cpc += 1

                if button.name == "autoclicker":
                    if cookies >= autoclickerB.price:
                        cookies -= autoclickerB.price
                        autoclickerB.price += 50
                        autos += 1
                        cps += .2

                if button.name == "betterauto":
                    if cookies >= betterAutoB.price:
                        cookies -= betterAutoB.price
                        betterAutoB.price *= 4
                        bautos += 1
                        cps += 50

    clock.tick(FPS)

    curtime = time.time()
    
    if curtime - nowtime > 1:
        nowtime = curtime
        cookies += cps

    for i, cookie in enumerate(fookies):
        if cookie.y > WIN.get_height() + 5:
            fookies.pop(i)
            print("popped")
            continue
        cookie.y += cookie.speed
        cookie.rect = display(FALLING, (cookie.x, cookie.y)) # Issue!

    cookt = font(CFONT64, f"{'%.1f' % cookies} {'Cookies' if floor(cookies) != 1 else 'Cookie'}")
    display(cookt, cookt.get_rect(center = (WIN.get_width() // 2, WIN.get_height() * .07)))

    cpst = font(CFONT26, f"{'%.1f' % cps} CPS")
    display(cpst, cpst.get_rect(center = (WIN.get_width() // 2, WIN.get_height() * .15)))

    cookieB.rect = display(COOKIE, ..., True)
    upgradeB.rect = display(UPGCOOKIE, (0,0), True, -450, 300)
    autoclickerB.rect = display(AUTOCOOKIE, (0,0), True, -187, 300)
    betterAutoB.rect = display(BETTERAUTO, (0, 0), True, 75, 300)

    upgt = font(CFONT26, f"{upgradeB.price} cookies (lvl. {upgrades})")
    display(upgt, (0,0), True, -450, 370)

    autot = font(CFONT26, f"{autoclickerB.price} cookies (x{autos})")
    display(autot, (0,0), True, -187, 370)

    bautot = font(CFONT26, f"{betterAutoB.price} cookies (x{bautos})")
    display(bautot, (0,0), True, 75, 370)

    pygame.display.update()
    WIN.fill(pygame.Color("black"))

FallingCookie类代码:

from random import random
import pygame

from tools import clamp
class FallingCookie:
    def __init__(self, width):
        self.x = random() * width
        self.y = 0
        self.speed = clamp(random() * 2, .9, 2)

    rect = pygame.Rect(1, 1, 1, 1)

解决方案

闪烁的核心原因是绘制顺序混乱+局部绘制过多,按以下步骤修改即可解决:

1. 调整背景填充与显示更新顺序

把WIN.fill(pygame.Color("black"))移到所有绘制逻辑之前,确保每一帧先清空画布,再绘制所有内容,最后统一更新显示。

2. 分离更新与绘制逻辑

不要在更新饼干位置的循环里调用display,先完成所有位置计算和逻辑更新,再一次性绘制所有元素。

3. 优化数组清理逻辑

用反向遍历清理超出屏幕的饼干,避免enumerate+pop导致的索引错位问题。

修改后的完整代码

主循环部分:

fcookies = []

while run:
    # 1. 事件处理
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

        if event.type == pygame.MOUSEBUTTONDOWN:
            if event.button != 1 and event.button != 3:
                continue
            pos = pygame.mouse.get_pos()
            clicked_buttons = [s for s in buttons if s.rect.colliderect(pygame.Rect(pos[0], pos[1], 1, 1))]
            for button in clicked_buttons:
                if button.name == "cookie":
                    pygame.mixer.Sound.play(random.choice(CRUNCHSFX))
                    fookies.append(FallingCookie(WIN.get_width()))
                    cookies += cpc
                elif button.name == "upgcookie":
                    if cookies >= upgradeB.price:
                        cookies -= upgradeB.price
                        upgradeB.price *= 2
                        upgrades += 1
                        cpc += 1
                elif button.name == "autoclicker":
                    if cookies >= autoclickerB.price:
                        cookies -= autoclickerB.price
                        autoclickerB.price += 50
                        autos += 1
                        cps += .2
                elif button.name == "betterauto":
                    if cookies >= betterAutoB.price:
                        cookies -= betterAutoB.price
                        betterAutoB.price *= 4
                        bautos += 1
                        cps += 50

    clock.tick(FPS)

    # 2. 游戏逻辑更新(仅计算,不绘制)
    curtime = time.time()
    if curtime - nowtime > 1:
        nowtime = curtime
        cookies += cps

    # 更新下落饼干位置,反向遍历清理超出屏幕的元素
    for i in range(len(fookies)-1, -1, -1):
        cookie = fookies[i]
        cookie.y += cookie.speed
        if cookie.y > WIN.get_height() + 5:
            fookies.pop(i)
            print("popped")
        # 仅更新rect,不绘制
        cookie.rect = pygame.Rect(cookie.x, cookie.y, FALLING.get_width(), FALLING.get_height())

    # 3. 清空画布(必须在所有绘制之前)
    WIN.fill(pygame.Color("black"))

    # 4. 一次性绘制所有元素
    # 绘制下落饼干
    for cookie in fookies:
        display(FALLING, (cookie.x, cookie.y))

    # 绘制文本与按钮
    cookt = font(CFONT64, f"{'%.1f' % cookies} {'Cookies' if floor(cookies) != 1 else 'Cookie'}")
    display(cookt, cookt.get_rect(center = (WIN.get_width() // 2, WIN.get_height() * .07)))

    cpst = font(CFONT26, f"{'%.1f' % cps} CPS")
    display(cpst, cpst.get_rect(center = (WIN.get_width() // 2, WIN.get_height() * .15)))

    cookieB.rect = display(COOKIE, ..., True)
    upgradeB.rect = display(UPGCOOKIE, (0,0), True, -450, 300)
    autoclickerB.rect = display(AUTOCOOKIE, (0,0), True, -187, 300)
    betterAutoB.rect = display(BETTERAUTO, (0, 0), True, 75, 300)

    upgt = font(CFONT26, f"{upgradeB.price} cookies (lvl. {upgrades})")
    display(upgt, (0,0), True, -450, 370)

    autot = font(CFONT26, f"{autoclickerB.price} cookies (x{autos})")
    display(autot, (0,0), True, -187, 370)

    bautot = font(CFONT26, f"{betterAutoB.price} cookies (x{bautos})")
    display(bautot, (0,0), True, 75, 370)

    # 5. 统一更新显示(最后一步)
    pygame.display.update()

优化后的FallingCookie类:

from random import random
import pygame
from tools import clamp

class FallingCookie:
    def __init__(self, width):
        self.x = random() * width
        self.y = 0
        self.speed = clamp(random() * 2, .9, 2)
        # 将rect改为实例属性,避免类属性共享导致的异常
        self.rect = pygame.Rect(1, 1, 1, 1)

效果说明

  • 先清空画布再绘制所有内容,确保每一帧的画面都是完整生成后才展示,不会出现清空屏幕后的空白间隙。
  • 分离更新与绘制逻辑,减少局部绘制次数,大幅提升渲染效率,避免多元素绘制时的闪烁。
  • 反向遍历清理数组,确保所有超出屏幕的饼干都被正确移除,不会出现索引错位导致的漏处理问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 03:55:22