Я создал игру, используя пакет черепахи Python, в котором вы используете клавишу со стрелкой вверх, чтобы помочь черепахе пересечь экран и избежать при этом препятствий. У него есть кнопка повтора, которую вы можете нажать после поражения, чтобы перезагрузить игру и сыграть снова. Однако, если вы проиграете дважды и нажмете кнопку «Повтор», препятствия внезапно начнут двигаться быстрее на экране, а после вашего проигрыша препятствия будут продолжать генерироваться и появляться над сообщением «Игра окончена». Этого не происходит в первые два раза игры.
Я считаю, что это связано с некоторыми функциями tkinter, о которых я не знаю. Несколько недель назад я попытался решить эту проблему с помощью отладчика и заметил следующее в функции Begin_game, когда черепаха сталкивается с препятствием и игра заканчивается:
play_game имеет значение False, потому что if cosmo.distance(c) <= 20 оценивается как True
Условие if Cosmo.reach_finish_line оценивается как False, и оно пропускается.
При достижении условия if play_game отладчик попадает в некоторый код tkinter и в конце его переходит к началу функции Begin_game и начинает ее выполнение. Этого не должно происходить, поскольку для параметра play_game установлено значение False, поэтому метод Begin_game не должен вызываться рекурсивно. Я даже дважды проверил значение play_game перед тем, как будет оценен оператор if, и оно все еще было ложным.
Кто-нибудь знает, почему это может происходить? Почему препятствия внезапно начинают двигаться быстрее, когда игра переигрывается во второй раз? И почему они продолжают появляться на экране, даже когда игрок проигрывает?
Ниже приведена проблемная функция 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)
Вы задаетесь вопросом, как возможно, что препятствия ускоряются... Причина проблемы кроется не в tkinter
, а в вашем коде, где функция replay_game()
вызывается несколько раз, что приводит к множеству play_game()
циклов событий таймера, работающих рядом друг с другом. А если выполняются две или более петель play_game()
, препятствия перемещаются каждой из этих петель, что приводит к более быстрому перемещению препятствий.
В вашем коде есть две основные причины наблюдаемой вами проблемы:
play_game
не является глобальной, но она должна быть глобальной, чтобы вписаться в логику управления игровым кодом, управляемую отдельными функциями.replay_game()
вызывается несколько разКак получается, что функция replay_game()
вызывается несколько раз? Причина — использование опции add=True
в методе onclick()
. Если для этой опции установлено значение True
, то будет добавлена дополнительная функция обратного вызова, которая будет запускаться при нажатии. В случае вашего кода это функция replay_game()
, которая добавляется в список функций, вызываемых при нажатии кнопки воспроизведения. С каждым завершением игры добавляется еще один вызов replay_game()
, что приводит к многократному запуску функции begin_game()
. Установка add=False
не добавляет еще одну функцию и решает таким образом наблюдаемую проблему.
Исходя из вышеизложенного, решение вашей проблемы следующее:
play_game
эту глобальную переменнуюplay_game = True
из begin_game()
и установите его везде в коде перед вызовом кода begin_game()
add
для регистрации функций обратного вызова на False
, чтобы предотвратить несколько запусков одной и той же функции одним щелчком мыши.Ниже приведен код «main.py» с минимальным набором изменений, реализующих вышеперечисленные модификации. Этот код решает проблему с ускорением прохождения препятствий при каждом новом перезапуске игры:
import time
from turtle import Screen, Turtle
from obstacle_generator import ObstacleGenerator
from player import Player
from scoreboard import Scoreboard
from welcome_screen import WelcomeScreen
# Complete initial setup of game
def setup(*args):
screen.setup(width=600, height=600)
screen.bgcolor("black")
# Display source of turtle icons
icon_credit.goto(-240, -285)
icon_credit.color("white")
icon_credit.write("Icons by Icon8", move=False, align = "center", font=FONT)
# Add images used in welcome screen turtle objects to screen's available shapes
for num in range(1, 5):
screen.addshape(f"images/welcome_screen{num}.gif")
screen.addshape("images/next.gif")
screen.addshape("images/back.gif")
screen.addshape("images/play.gif")
# Call welcome_screen object methods to display welcome message and instructions
welcome_screen.welcome1()
screen.addshape("images/turtle.gif")
cosmo.shape("images/turtle.gif")
cosmo.hideturtle()
screen.listen()
screen.onkeypress(fun=cosmo.move_up, key = "Up")
# Add images used in obstacle turtle objects to screen's available shapes
for obs_type in obstacle_generator.types:
screen.addshape(f"images/{obs_type}.gif")
check_game_trigger()
# Use Turtle's ontimer function to recursively check whether player has pressed 'Play' button for game to begin
def check_game_trigger():
if welcome_screen.trigger:
welcome_screen.goto(-1000, -1000)
welcome_screen.next_button.goto(-1000, -1000)
welcome_screen.back_button.goto(-1000, -1000)
begin_game()
else:
screen.update()
screen.ontimer(check_game_trigger, 100)
# Begin game when player hits 'Play' or 'Replay' buttons
def begin_game():
global play_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)
def end_game():
screen.bgcolor("black")
# Add end game screen turtle image shapes to screen's available shapes
screen.addshape("images/game_over_message.gif")
screen.addshape("images/alien_monster.gif")
screen.addshape("images/replay.gif")
screen.addshape("images/exit.gif")
# Call lose_screen method to display game over message and ask player to replay or exit
lose_screen.lose_screen()
lose_screen.exit_button.onclick(fun=exit_screen, add=False) ###<<<###
lose_screen.replay_button.onclick(fun=replay_game, add=False) ###<<<###
screen.update()
# Reset screen if player chooses to replay and call the begin_game function
def replay_game(*args):
global play_game ###<<<###
lose_screen.goto(-1000, -1000)
lose_screen.exit_button.goto(-1000, -1000)
lose_screen.replay_button.goto(-1000, -1000)
lose_screen.game_over_message.goto(-1000, -1000)
obstacle_generator.level = 0
level_display.clear()
level_display.level = 0
cosmo.goto(cosmo.starting_pos)
play_game = True
begin_game()
# Exit game screen if player clicks on the 'Exit' button
def exit_screen(*args):
screen.clearscreen()
screen.bye()
# Initialize objects used in game and call setup function
if __name__ == "__main__":
screen = Screen()
screen.tracer(0)
cosmo = Player()
welcome_screen = WelcomeScreen()
obstacle_generator = ObstacleGenerator()
lose_screen = WelcomeScreen()
level_display = Scoreboard()
icon_credit = Turtle()
icon_credit.hideturtle()
FONT = ("Courier", 8, "normal")
setup()
play_game = True
screen.mainloop()