NEAT AI驱动Pygame赛车模拟器卡顿崩溃问题优化求助
2D赛车AI模拟器性能问题排查与修复
问题描述
用Pygame开发2D俯视视角赛车模拟器,基于NEAT算法进化驾驶AI。每辆赛车配备5个射线投射(左、左偏、前、右偏、右),以检测墙体的距离作为神经网络输入;输出包含加速、减速、右转、左转4项。程序初始运行正常,但仅10个个体的小种群在几代后就会大幅变慢,最终崩溃。已排除硬件问题(高性能台式机测试结果一致)和NEAT本身限制(同类复杂项目可稳定运行),推测卡顿源于核心eval_genomes函数。
核心代码
def remove(id): nets.pop(id) ge.pop(id) cars.pop(id) def eval_genomes(genomes, config): setup((700, 700)) setup('fullscreen') track.createTrack() global nets, ge, cars nets = [] ge = [] cars = [] for id, g in genomes: g.fitness = 0 net = neat.nn.FeedForwardNetwork.create(g, config) nets.append(net) pos = size(9, 90, screenSize) cars.append(Car(screen, pos[0], pos[1], track)) ge.append(g) for car in cars: car.rays = [Ray(car, 0), Ray(car, 45), Ray(car, -45), Ray(car, 90), Ray(car, -90)] frame = 0 run = True while run and frame < 420: for event in pygame.event.get(): if event.type == pygame.QUIT: run = False break if event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: run = False break screen.fill((20, 20, 20)) track.display() #display the map # track.drawReference() for id, car in enumerate(cars): car.update(cars, ge) # all vehicle updates like vel, pos, rotation car.display() # renders vehicle. has no effect on the code it just renders. ge[id].fitness += 0.1 output = nets[id].activate(car.data) if output[0] > 0.75: #inputs car.inputs[0] = True else: car.inputs[0] = False if output[1] > 0.75: car.inputs[1] = True else: car.inputs[1] = False if output[2] > 0.75: car.inputs[2] = True else: car.inputs[2] = False if output[3] > 0.75: car.inputs[3] = True else: car.inputs[3] = False for line in track.racetrack: if car.colliding(line): #checks collisions remove(id) #removes that car from all lists so it doesn't matter anymore break if 0 == len(cars): #if all cars are dead it will automatically start the nex gen run = False pygame.display.flip() frame += 1 clock.tick(60)
问题分析与修复建议
1. 列表遍历与删除的逻辑错误
在碰撞检测循环中,直接通过remove(id)删除列表元素会导致后续遍历的索引混乱:删除索引i的元素后,原索引i+1的元素会移到i的位置,但循环id会递增,导致跳过该元素,甚至引发索引越界。
修复方案:收集需要删除的ID,在遍历结束后反向删除(避免索引偏移):
# 替换原碰撞检测部分 to_remove = [] for id, car in enumerate(cars): # ... 其他逻辑 ... collided = False for line in track.racetrack: if car.colliding(line): collided = True break if collided: to_remove.append(id) # 反向删除 for id in reversed(to_remove): nets.pop(id) ge.pop(id) cars.pop(id)
2. 重复初始化窗口资源
eval_genomes每次调用都执行setup((700, 700))和setup('fullscreen'),会重复创建窗口或切换全屏模式,导致资源泄漏。
修复方案:将窗口初始化移到函数外部,仅执行一次:
# 全局窗口初始化(仅执行一次) screen = pygame.display.set_mode((700, 700)) pygame.display.set_caption("Racing AI") clock = pygame.time.Clock() def eval_genomes(genomes, config): # 移除重复的setup调用 track.createTrack() # ... 其他逻辑 ...
3. 不必要的帧率限制
clock.tick(60)强制每帧等待到60FPS,但进化过程不需要实时渲染帧率限制,会浪费CPU资源并拖慢进化速度。
修复方案:移除clock.tick(60),让CPU全速处理进化逻辑。
4. 全局变量滥用
使用全局变量nets、ge、cars会导致内存管理混乱,多代进化后可能残留无效引用。
修复方案:将这些变量改为eval_genomes内部局部变量,避免全局副作用。
5. 渲染开销优化
每代实时渲染所有赛车和赛道会累积性能消耗,可通过以下方式优化:
- 添加开关控制渲染,测试阶段关闭渲染验证性能问题
- 简化
car.display()和track.display()的绘制逻辑,减少不必要的图形操作
修正后的核心函数示例
def eval_genomes(genomes, config): global screen, track # 窗口和赛道对象已在外部初始化 track.createTrack() nets = [] ge = [] cars = [] for id, g in genomes: g.fitness = 0 net = neat.nn.FeedForwardNetwork.create(g, config) nets.append(net) pos = size(9, 90, screenSize) cars.append(Car(screen, pos[0], pos[1], track)) ge.append(g) for car in cars: car.rays = [Ray(car, 0), Ray(car, 45), Ray(car, -45), Ray(car, 90), Ray(car, -90)] frame = 0 run = True while run and frame < 420: for event in pygame.event.get(): if event.type == pygame.QUIT: run = False break if event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: run = False break screen.fill((20, 20, 20)) track.display() to_remove = [] for id, car in enumerate(cars): car.update(cars, ge) car.display() ge[id].fitness += 0.1 output = nets[id].activate(car.data) # 简化输入赋值逻辑 car.inputs = [output[i] > 0.75 for i in range(4)] # 碰撞检测 collided = False for line in track.racetrack: if car.colliding(line): collided = True break if collided: to_remove.append(id) # 反向删除避免索引混乱 for id in reversed(to_remove): nets.pop(id) ge.pop(id) cars.pop(id) if not cars: run = False pygame.display.flip() frame += 1
内容的提问来源于stack exchange,提问作者PogoSpin
相关产品推荐
相关产品推荐

