PAYATHON 2026

如何优化 Pygame 中 3D 顶点的显示与动画?

数据小陈

结论

先调整游戏循环结构。当前代码处理每个顶点时都会调用 pygame.display.flip() 和 time.sleep(dt),还会重复读取按键状态,并对同一个顶点调用两次 project()。这些操作会随顶点数量增加,容易造成卡顿和帧率异常。

dt = 1/fps 只是固定的时间间隔,不能加快逐顶点绘制和等待。通常每帧读取一次输入,更新所有顶点并绘制完整画面,最后再刷新屏幕一次。

把刷新和等待移到顶点循环外

可以按下面的结构整理主循环。animate_vertex 仅作示意,需要根据现有 animate 的参数和返回值调整。如果动画不需要时间参数,也可以删掉 elapsed。

clock = pygame.time.Clock()
running = True
elapsed = 0.0

while running:
    dt = clock.tick(60) / 1000.0
    elapsed += dt

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

    keys = pygame.key.get_pressed()
    angle = 0.0
    if keys[pygame.K_RIGHT]:
        angle += 2 * dt
    if keys[pygame.K_LEFT]:
        angle -= 2 * dt

    screen.fill((0, 0, 0))

    for i, vertex in enumerate(base_vertices):
        point = animate_vertex(vertex, elapsed)
        if angle:
            point = rotate_3d(point, [0, angle, 0])

        screen_pos = project(point)
        pygame.draw.circle(screen, (0, 160, 20), screen_pos, 2)

    pygame.display.flip()

pygame.quit()

这里假设 base_vertices 保存导入后的原始顶点,动画和旋转都从这些顶点计算。如果每帧都基于上一帧的结果修改 vertices,旋转可能逐帧累积。是否适用要看现有动画逻辑。若动画本来就依赖上一帧状态,应保留原有状态更新方式,再测量计算开销。

减少每帧重复工作

  • 每帧只读取一次按键。示例代码会在每个顶点上重复调用 pygame.key.get_pressed()。读取一次后,根据按键状态计算本帧的旋转角度即可。
  • 每个顶点只投影一次。原代码绘制圆点和文字时,分别调用 project(vertices[i])。如果还要显示索引,可以复用 screen_pos。
  • 减少逐顶点文字渲染。font.render(str(i), ...) 会在每帧为每个顶点创建文字表面,开销可能较大。调试时可以只显示少量标签;如果标签文本固定,也可以缓存渲染后的表面。
  • 根据需要减少顶点数量或绘制细节。模型包含大量顶点时,降低导入模型的细节,或只绘制部分顶点,都能减少更新、投影和绘制次数。

检查循环顺序和计时

每帧先清屏,再绘制当前帧的内容,最后调用一次 pygame.display.flip()。如果每画完一个顶点就刷新并等待,顶点越多,一帧内重复等待的次数也越多。pygame.time.Clock().tick(60) 可以限制循环帧率,并根据实际经过的时间计算 dt。

优化后如果仍然卡顿,可以分别统计动画计算、project() 和绘制所花的时间,找出主要耗时环节。仅凭这段代码无法判断 .obj 模型有多少顶点,也无法确定瓶颈是否只在渲染。顶点数量较少时,耗时也可能来自 animate 或 rotate_3d 的实现。

备注:内容仅供参考。