Могу ли я написать гладко работающую игру про змейку с помощью Tkinter?

Ради интереса я попытался реализовать классическую игру в змейку на Python/Tkinter. Он чувствует себя очень медленным, и я не совсем уверен, смогу ли я это исправить или нет. Попытаюсь кратко изложить программу:

Сначала я создаю холст и добавляю обработчик событий для каждой клавиши со стрелкой:

root = self.root = Tk()
canvas = Canvas(root, width = width, height = height)
canvas.pack()
canvas.bind("<Left>", on_left) # on_left is a very short function

У меня также есть функция для временного шага. Он рисует графику и запланирует повторный вызов. Функция не очень эффективна, так как каждый раз перерисовывает 100 прямоугольников:

def timestep(self):
    # draw the graphics here (about 100 filled rectangles)
    timer = threading.Timer(interval, timestep)
    timer.start()

Теперь у меня две проблемы:

  • Даже когда я выбираю interval в timestep() около 0,05 (что эквивалентно 20 кадрам в секунду), я не получаю больше, чем 3-5 кадров в секунду.
  • Кажется, что ввод управления с клавиатуры задерживается примерно на 0,5 секунды.

Меня интересуют три вещи:

  • Можно ли вообще написать гладкую, не запаздывающую (20 кадров в секунду) змейку с холстом Tkinter, когда вы хотите рисовать 100 прямоугольников на каждом временном шаге?

  • Является ли threading.Timer правильным выбором для вызова функции временного шага?

  • Почему мой ввод с клавиатуры кажется задержанным?


person Michael    schedule 16.04.2016    source источник
comment
Если вы не используете canvas.delete(...) между каждым кадром, у вас есть утечка памяти. 100 прямоугольников — это всего лишь 200 треугольников (не так много, игровые движки могут делать миллионы при 60+ кадрах в секунду)   -  person kpie    schedule 16.04.2016
comment
Могу я спросить, почему вы создаете 100 прямоугольников на каждом этапе?   -  person TigerhawkT3    schedule 16.04.2016
comment
А self.parent.after(interval, timestep) наверное проще и легче. Можете ли вы показать достаточно кода, чтобы проблема была воспроизведена?   -  person TigerhawkT3    schedule 16.04.2016
comment
Вам не нужны нити. На этом сайте много примеров анимации с использованием метода after   -  person Bryan Oakley    schedule 17.04.2016


Ответы (4)


Попробуйте использовать встроенный canvas.after(funciton,interval) вместо threading.timer.

person kpie    schedule 16.04.2016

Если вы не используете canvas.delete(...) между каждым кадром, у вас есть утечка памяти.

person kpie    schedule 16.04.2016
comment
Это не должно быть проблемой, пока машина OP не достигнет максимального объема памяти. Похоже, низкая частота кадров возникает сразу. - person TigerhawkT3; 16.04.2016
comment
@TigerhawkT3: у холста были проблемы с производительностью при очень большом количестве объектов. Создание 2000 прямоугольников в секунду довольно быстро начнет упираться в эту стену, задолго до того, как системе не хватит памяти. Это не единственная проблема, но это определенно проблема - person Bryan Oakley; 17.04.2016
comment
Держите их ниже голосов, я просто здесь, чтобы попытаться помочь людям. - person kpie; 17.04.2016
comment
@kpie: если вы хотите получить больше голосов, напишите лучшие ответы. Однострочный ответ обычно не набирает много голосов. Кроме того, этот ответ недостаточно отвечает на вопрос, поэтому, вероятно, за него проголосовали. Вместо того, чтобы писать второй однострочный ответ, попробуйте улучшить этот ответ. - person Bryan Oakley; 17.04.2016

Да, вы можете создать плавно работающую игру со множеством объектов на экране, и вы можете сделать это без потоков. Хотя это зависит от того, как вы определяете «гладкий» и «много», и зависит от того, как вы это реализуете.

Если у вас есть алгоритм, который удаляет и воссоздает все в сцене на каждой итерации, он будет работать очень медленно. Правильный способ сделать анимацию — создать объекты, которые вы хотите анимировать, один раз и только один раз. Затем для каждого кадра вы можете перенастроить или переместить их с помощью методов холста itemconfigure, move и coords.

Я видел эту работу с сотнями предметов на холсте. Однако он начинает вязнуть, если у вас тысячи. Кроме того, у холста есть известная проблема с производительностью, когда вы создаете тысячи элементов, даже если вы удаляете старые элементы.

Итак, как и в случае со многими вопросами, связанными с производительностью, это частично сводится к окончательному алгоритму. Например, в игре со змеями вы можете выбрать перерисовку всей змеи в каждом кадре или посмотреть на проблему и понять, что вам нужно переместить только один или два сегмента змеи. Первый способ будет медленным, второй — очень быстрым.

Суть в том, что холст tkinter достаточно мощный, чтобы плавно анимировать, возможно, несколько десятков элементов одновременно, если он реализован с помощью надлежащего алгоритма.

person Bryan Oakley    schedule 17.04.2016

Существующий ответ точно не описывает, как я мог бы окончательно решить проблему, поэтому я дам ответ на свой вопрос здесь:

Основная проблема заключается в том, что такие функции, как canvas.create_rectangle(...), фактически добавляют новый прямоугольник при каждом вызове. Таким образом, вы столкнетесь с проблемами производительности, потому что у вас слишком много прямоугольников даже через очень короткое время.

Вы можете использовать canvas.delete(...) для удаления некоторых или всех прямоугольников. Если вы удалите все существующие прямоугольники и создадите новые прямоугольники на каждом временном шаге, производительность будет в порядке, но экран мерцает.

Лучший способ — создать прямоугольники один раз при инициализации, а затем просто менять их цвет на каждом временном шаге:

# on startup:
rect[x][y] = canvas.create_rectangle(...)

# in each timestep
canvas.itemconfig(rect[x][y], fill = myColor)

При этом я получаю хорошую производительность и отсутствие мерцания, хотя я по-прежнему меняю цвет 100 прямоугольников 20 раз в секунду.

person Michael    schedule 17.04.2016