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

Pygame开发俄罗斯方块:锁定方块时单帧闪烁问题求助

Pygame俄罗斯方块锁定时单帧闪烁问题修复

问题现象

开发Pygame俄罗斯方块克隆版时,方块锁定瞬间会出现单帧黑色闪烁——这一帧里,待锁定的方块精灵已消失,但网格精灵还没把该方块绘制出来,导致对应位置空白。

问题演示

当前实现逻辑

  1. Piece类负责生成自身图像与矩形,参与精灵组绘制
  2. Grid类用Numpy数组存储所有已锁定的小方块(mino),并根据数组内容生成自身图像
  3. 方块锁定触发流程:Tetris类检测到Piece锁定后,调用Grid更新视觉,随后销毁Piece

核心代码片段

Piece类锁定逻辑

if self.lock_delay <= 0:
     self.lock()
     self.kill()

Tetris类锁定处理逻辑

def update_game_state(self):
        if self.current_piece.locked:
            self.score += self.grid.clear_lines()
            self.grid.update_visual()
            self.spawn_piece()

Grid类视觉更新方法

def update_visual(self):
        #vars for where to draw block
        x_draw = 0
        y_draw = 0

        #copy based on cached images
        for row in self.grid:
            for val in row:
                self.update_surface.blit(self.block_cache[val], (x_draw, y_draw))
                x_draw += self.block_width
            y_draw += self.block_width
            x_draw = 0

        self.image, self.rect = self.update_surface, self.update_surface.get_rect()
        self.rect.bottomleft = (self.x,self.y)

游戏循环核心流程

#update gamestate
tetris_game.update_game_state()

#update sprites
allsprites.update()

#draw

screen.blit(background, (0, 0))
allsprites.draw(screen)
pygame.display.flip()

问题根源

闪烁的本质是帧更新顺序导致的绘制真空:

  • Piece调用kill()后会立即从精灵组移除,当前帧绘制时不会再渲染它
  • Grid的update_visual()是在Piece销毁后才执行,新的网格图像要到下一帧才会生效
  • 这就导致当前帧中,待锁定方块的位置既没有Piece绘制,也没有Grid绘制,出现空白闪烁

修复方案

方案1:调整执行顺序(推荐)

把Piece的销毁操作移到Grid视觉更新之后,确保Grid已经包含了该方块的视觉内容,再移除Piece:

  1. 修改Piece类的锁定逻辑,只标记锁定状态,不立即销毁:
if self.lock_delay <= 0:
     self.lock()
     # 移除self.kill(),交给Tetris统一处理
  1. 修改Tetris类的update_game_state方法,先更新Grid视觉,再销毁旧Piece:
def update_game_state(self):
        if self.current_piece.locked:
            self.score += self.grid.clear_lines()
            self.grid.update_visual()
            # 先更新Grid,再销毁方块
            self.current_piece.kill()
            self.spawn_piece()

方案2:延迟销毁Piece

让Piece在锁定后多存活一帧,等Grid完成视觉更新后再销毁:

  1. 在Piece类中添加标记并修改锁定逻辑:
# Piece类初始化时添加
self.pending_kill = False

# 锁定逻辑修改
if self.lock_delay <= 0:
     self.lock()
     self.pending_kill = True

# 在Piece的update方法中添加销毁判断
def update(self):
    # 保留原有update逻辑...
    if self.pending_kill:
        self.kill()

这样Piece会在锁定后的下一帧才被销毁,当前帧绘制时Piece依然存在,Grid的新图像也会在下一帧生效,不会出现空白。

可选优化:减少Grid重绘范围

当前update_visual()每次全量重绘整个网格,效率较低。可以只重绘锁定方块所在的行,减少绘制开销:

修改Grid类的update_visual方法:

def update_visual(self, updated_rows=None):
    # 只重绘指定行,未指定则全量重绘
    if updated_rows is not None:
        for row_idx in updated_rows:
            x_draw = 0
            y_draw = row_idx * self.block_width
            for val in self.grid[row_idx]:
                self.update_surface.blit(self.block_cache[val], (x_draw, y_draw))
                x_draw += self.block_width
    else:
        x_draw = 0
        y_draw = 0
        for row in self.grid:
            for val in row:
                self.update_surface.blit(self.block_cache[val], (x_draw, y_draw))
                x_draw += self.block_width
            y_draw += self.block_width
            x_draw = 0
    self.image, self.rect = self.update_surface, self.update_surface.get_rect()
    self.rect.bottomleft = (self.x,self.y)

调用时传入锁定方块所在的行(需要Piece实现获取自身行的方法):

# Tetris类中
locked_rows = self.current_piece.get_locked_rows()
self.grid.update_visual(locked_rows)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 10:05:23