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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 08:27:10