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

Pygame简易沙盒游戏生成大量方块后帧率过低问题排查

优化你的Powder Game风格沙盒游戏性能

嘿,很高兴看到你在打造自己的沙盒模拟游戏!你怀疑分开的列表存储是卡顿原因,这点没错,但还有几个关键的性能瓶颈咱们一起解决:

1. 优化数据结构:用统一的列表存储方块信息

你现在用cubex、cubey、cubec三个分开的列表来存方块的位置和颜色,每次遍历都要同步操作三个列表,不仅代码繁琐,也会增加内存访问的开销。建议把每个方块的所有信息打包成一个字典,存在一个列表里:

# 初始化方块列表,每个元素是一个字典
blocks = []

# 生成方块时直接添加字典
x = round((mouse[0]/cubew),0)*cubew
y = round((mouse[1]/cubew),0)*cubew
blocks.append({
    'x': x,
    'y': y,
    'color': color,
    'rect': pygame.Rect(x, y, cubew, cubew)  # 提前创建Rect,后续碰撞检测复用
})

这样遍历的时候只需要操作一个列表,代码更清晰,性能也更好。

2. 降低碰撞检测的复杂度(核心瓶颈)

你现在的碰撞检测是**O(n²)**的:每个方块都要和其他所有方块检查碰撞,当方块数量到几百个时,计算量会爆炸式增长。这里有两个优化方向:

2.1 提前缓存方块Rect

每次碰撞检测都创建新的pygame.Rect是不必要的,我们可以把Rect直接存在方块字典里,更新位置时同步更新Rect的坐标:

# 更新方块位置时同步更新Rect
block['y'] -= gravity
block['rect'].y = block['y']

2.2 用空间分区减少检测范围

把游戏区域分成和方块大小一致的网格,每个网格只存储当前在里面的方块。这样检查某个方块的下方/周围时,只需要查询相邻的网格,不用遍历所有方块:

# 初始化网格(按方块大小10px划分)
grid_width = (wnx - 154) // cubew  # 只计算游戏区域的网格
grid_height = floory // cubew
grid = [[[] for _ in range(grid_height)] for _ in range(grid_width)]

# 每次更新方块位置后,更新网格
def update_grid(blocks, grid, cubew):
    # 先清空网格
    for row in grid:
        row.clear()
    # 把每个方块放到对应的网格格子里
    for idx, block in enumerate(blocks):
        # 转换为游戏区域内的网格坐标
        grid_x = (block['x'] - 154) // cubew
        grid_y = block['y'] // cubew
        if 0 <= grid_x < len(grid) and 0 <= grid_y < len(grid[0]):
            grid[grid_y][grid_x].append(idx)

# 检测下方碰撞时,只查当前方块下方的网格格子
current_grid_x = (block['x'] - 154) // cubew
current_grid_y = block['y'] // cubew
collided = False
# 检查下方格子里的方块
if current_grid_y + 1 < len(grid):
    nearby_block_indices = grid[current_grid_y + 1][current_grid_x]
    for idx in nearby_block_indices:
        if idx != current_idx and block['rect'].colliderect(blocks[idx]['rect']):
            collided = True
            break

3. 优化绘制逻辑

你现在逐个调用cube()函数绘制方块,当数量多的时候会很慢。可以用pygame.sprite.Sprite和pygame.sprite.Group来批量绘制,这是pygame专门优化过的绘制方式:

class Block(pygame.sprite.Sprite):
    def __init__(self, x, y, color, size):
        super().__init__()
        self.image = pygame.Surface((size, size))
        self.image.fill(color)
        self.rect = self.image.get_rect(topleft=(x, y))
        self.color = color

# 初始化精灵组
block_group = pygame.sprite.Group()

# 添加方块时创建Sprite并加入组
new_block = Block(x, y, color, cubew)
block_group.add(new_block)

# 绘制时只需要调用一次,pygame会批量处理
block_group.draw(wn)

4. 修复代码里的小bug

你的事件处理有个潜在问题:在toggle_fast == False的分支里,你用了if event.type == pygame.MOUSEBUTTONDOWN,但这个event只在外面的for event in pygame.event.get()循环里有效,循环结束后event变量可能不存在,会导致报错。应该把这个逻辑移到事件循环内部:

while not exit:
    clickt = False
    mouse = pygame.mouse.get_pos()
    click = pygame.mouse.get_pressed()
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            exit = True
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                toggle_fast = not toggle_fast
            if event.key == pygame.K_v:
                erase = not erase
        # 把鼠标点击生成方块的逻辑移到这里
        if event.type == pygame.MOUSEBUTTONDOWN and not toggle_fast:
            if mouse[1] < floory and mouse[0] >= 154:
                x = round((mouse[0]/cubew),0)*cubew
                y = round((mouse[1]/cubew),0)*cubew
                new_block = Block(x, y, color, cubew)
                block_group.add(new_block)

总结

最影响性能的是O(n²)的碰撞检测,其次是零散的数据存储。按照上面的方法优化后,即使几千个方块也能保持流畅。如果你刚学Python,不用一下子全部改完,可以先从统一数据结构和修复事件bug开始,再逐步优化碰撞检测。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 04:46:37