Python Arcade大量Sprite与网格碰撞检测的性能优化求助
50×50网格小球碰撞检测性能优化方案
我用Python开发了一款模拟程序,核心是构建50×50的彩色单元格网格,小球在网格内生成并移动,直至碰撞到不同颜色的单元格——碰撞发生时单元格改变颜色,小球随之消失。目前基础功能已实现,但存在严重性能瓶颈:小球数量低于40个时能维持60FPS,若支持上千个小球运行,帧率会显著下降。
原核心代码片段
GRID_SIZE = 50 CELL_SIZE = 10 self.balls = arcade.SpriteList() self.cells = arcade.SpriteList(use_spatial_hash=True, spatial_hash_cell_size=CELL_SIZE) def on_update(self, delta_time): self.balls.update() class Cell(arcade.SpriteSolidColor): def __init__(self, x, y, color, grid_app): super().__init__(CELL_SIZE, CELL_SIZE, arcade.color.WHITE) self.gridApp = grid_app self.center_x = x self.center_y = y self.update_color(color) def update_color(self, color): self.color = color class Ball(arcade.Sprite): def __init__(self, x, y, velocity, angle, spriteConf, grid_app, scale=1): super().__init__(SPRITE_CONF[spriteConf]["imagePath"], scale=scale) self.center_x = x self.center_y = y self.my_color=SPRITE_CONF[spriteConf]["color"] self.velocity = velocity self.my_angle = angle self.gridApp = grid_app def update(self): self.center_x += self.velocity * REFRESH_RATE * math.cos(math.radians(self.my_angle)) self.center_y += self.velocity * REFRESH_RATE * math.sin(math.radians(self.my_angle)) # 边界碰撞检测 if self.center_x < 0 or self.center_x > WINDOW_WIDTH: self.my_angle = 180 - self.my_angle if self.center_y < 0 or self.center_y > WINDOW_HEIGHT: self.my_angle = 360 - self.my_angle # 性能瓶颈区域 collidedCells = arcade.check_for_collision_with_lists( self, [self.gridApp.cells] ) hasToBeDelete = False for cell in collidedCells: if cell.color != self.my_color: hasToBeDelete = True cell.update_color(self.my_color) if hasToBeDelete: self.gridApp.removeBall(self)
性能问题出现前的运行状态:
针对性优化方案
1. 用网格坐标计算替代Sprite碰撞检测
直接通过小球坐标计算所在单元格,彻底避免遍历SpriteList的开销:
- 初始化时将Cell对象存入二维数组
self.grid_cells(按行列索引存储) - 小球update时,通过坐标计算当前所在的网格行列:
此方法将每个小球的碰撞检测复杂度从O(N)降到O(1),是提升性能的核心。col = int(self.center_x // CELL_SIZE) row = int(self.center_y // CELL_SIZE) # 确保行列在网格范围内 if 0 <= col < GRID_SIZE and 0 <= row < GRID_SIZE: target_cell = self.gridApp.grid_cells[row][col] if target_cell.color != self.my_color: target_cell.update_color(self.my_color) self.gridApp.removeBall(self)
2. 预计算方向向量,减少三角函数调用
原代码每次update都重复计算三角函数,可提前将角度转换为方向向量:
- 在Ball初始化时:
self.dx = math.cos(math.radians(self.my_angle)) self.dy = math.sin(math.radians(self.my_angle)) - 更新移动逻辑:
self.center_x += self.velocity * REFRESH_RATE * self.dx self.center_y += self.velocity * REFRESH_RATE * self.dy - 边界碰撞时直接反转方向向量,无需重新计算角度:
if self.center_x < 0 or self.center_x > WINDOW_WIDTH: self.dx = -self.dx if self.center_y < 0 or self.center_y > WINDOW_HEIGHT: self.dy = -self.dy
3. 优化Cell存储结构
将原SpriteList存储的cells替换为二维数组,初始化时按行列填充:
# 主类初始化时 self.grid_cells = [] for row in range(GRID_SIZE): row_cells = [] for col in range(GRID_SIZE): x = col * CELL_SIZE + CELL_SIZE // 2 y = row * CELL_SIZE + CELL_SIZE // 2 cell = Cell(x, y, initial_color, self) row_cells.append(cell) self.cells.append(cell) # 保留SpriteList用于渲染 self.grid_cells.append(row_cells)
二维数组的直接索引比SpriteList的空间哈希查找更快,尤其在大量小球并发访问时。
4. 降低碰撞检测频率(可选)
如果小球移动速度较慢,可每N帧执行一次碰撞检测,减少计算量:
- 在Ball类中添加计数器:
self.check_counter = 0 - update时:
self.check_counter += 1 if self.check_counter % 2 == 0: # 每2帧检查一次 # 执行网格坐标计算和碰撞逻辑 self.check_counter = 0
5. 批量处理小球更新(进阶优化)
用numpy批量存储小球的坐标、方向向量等数据,通过向量运算批量计算移动,再同步回Sprite对象。此方法可大幅减少Python循环的开销,适合上千个小球的场景。
内容的提问来源于stack exchange,提问作者Benjamin Darras
相关产品推荐
相关产品推荐

