基于Python Turtle的游戏二次重玩异常问题求助
我用Python的turtle库做了一款障碍躲避游戏,玩家通过上箭头键控制乌龟穿越屏幕、躲避障碍物,失败后点击Replay按钮就能重置游戏。但第二次点击Replay重玩后,障碍物突然加速,而且玩家失败后,障碍物还会继续生成,把Game Over提示都盖住了。
我猜这和tkinter的某些机制有关,调试时发现:乌龟撞到障碍物后,play_game被设为False,但程序还是会递归调用begin_game函数,而且此时play_game的值确实是False。想问下这个异常的原因是什么?为什么二次重玩后障碍物变快、失败后还不停生成?
存在问题的begin_game函数代码
# Begin game when player hits 'Play' or 'Replay' buttons def begin_game(): cosmo.showturtle() level_display.show_level() play_game = True screen.update() # Call obstacle_generator methods to create obstacles and move them forward obstacle_generator.create_obstacles() obstacle_generator.obstacle_move() # Detect collision between obstacle and turtle for c in obstacle_generator.all_obstacles: if cosmo.distance(c) <= 20: time.sleep(0.3) screen.tracer(0) # Iterate over obstacle list, hide turtle, and append to recycle list since this game round is over, # and they should not be visible on the end game screen for num in range(len(obstacle_generator.all_obstacles)): obstacle_generator.all_obstacles[num].hideturtle() obstacle_generator.recycle.append(obstacle_generator.all_obstacles[num]) obstacle_generator.all_obstacles = [] cosmo.hideturtle() # Call function to display end game screen end_game() play_game = False break # Detect if player has completed current level if cosmo.reach_finish_line(): level_display.refresh_level() obstacle_generator.level_up() # Recursively call begin_game function if game is still on if play_game: screen.ontimer(fun=begin_game, t=100)
异常原因分析
1. 未清除旧定时器导致多循环叠加
每次点击Replay触发begin_game时,上一轮游戏通过screen.ontimer()设置的递归定时器并没有被取消。第一次游戏结束后,旧定时器仍在后台触发begin_game调用;二次重玩时又新增了一套定时器,相当于多个游戏循环同时运行。这会让障碍物生成、移动的频率翻倍,视觉上就是“加速”,而且旧循环会持续生成障碍物,覆盖Game Over提示。
2. 局部play_game变量无法终止所有循环
play_game是begin_game的局部变量,每个递归调用的begin_game实例都有独立的play_game。当一次碰撞把当前函数的play_game设为False,其他旧定时器触发的begin_game里的play_game还是True,会继续执行生成障碍物的逻辑。
3. 游戏结束后未阻断残留循环
调用end_game()后,虽然清空了当前障碍物列表,但旧的begin_game循环仍在运行,会持续调用obstacle_generator.create_obstacles()生成新障碍物,导致Game Over界面被覆盖。
修复建议
- 全局管理游戏状态:把
play_game改成全局变量,或者封装到游戏类中作为属性,确保所有begin_game调用共享同一个状态。 - 重置时清除旧定时器:在点击Play/Replay按钮时,先清除上一轮的定时器。可以用变量保存定时器ID,调用
screen.cleartimer(timer_id)取消未触发的定时器。 - 游戏终止时彻底停循环:在
end_game()中设置全局play_game = False,并清除所有定时器,阻断所有残留的begin_game调用。 - 重构循环逻辑:避免用递归
ontimer实现游戏循环,改用while循环结合screen.update(),更易控制启停。
内容的提问来源于stack exchange,提问作者esp

