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
相关产品推荐
相关产品推荐

