Создание игр на Python в Tkinter и адаптация под мобильные устройства

Курс посвящён разработке 2D-игр на Python с использованием Tkinter: от архитектуры и игрового цикла до анимаций, столкновений и UI. Затем вы изучите практические подходы к адаптации и упаковке проекта для мобильных устройств, включая перенос интерфейса и управление касаниями.

1. Основы Tkinter для игр: Canvas, события и интерфейс

Основы Tkinter для игр: Canvas, события и интерфейс

Зачем Tkinter подходит для первых игр

Tkinter — это стандартная библиотека Python для создания графических приложений. Для учебных игр она хороша тем, что:

  • Устанавливать почти ничего не нужно: Tkinter обычно идёт вместе с Python.
  • Есть Canvas — удобная «сцена», на которой можно рисовать объекты и анимировать их.
  • Есть события (клавиатура, мышь), из которых легко построить управление.
  • Официальная документация:

  • Документация Tkinter в Python
  • Canvas (раздел документации)
  • Минимальная архитектура игры на Tkinter

    Почти любую игру на Tkinter удобно строить из трёх частей:

  • Состояние игры — данные: позиции, скорости, очки, здоровье, текущий экран.
  • Обновление (update) — как меняется состояние со временем.
  • Отрисовка (render) — как состояние превращается в картинку на Canvas.
  • Плюс управление через события: «нажали клавишу», «двинули мышь», «кликнули».

    !Схема показывает, что события и таймер меняют состояние, а Canvas отображает результат

    Окно, виджеты и главный цикл Tkinter

    Tk, mainloop и почему он важен

    В Tkinter вы создаёте главное окно Tk(), добавляете виджеты (например, Canvas, кнопки, метки), а затем запускаете mainloop().

    mainloop() — это внутренний цикл обработки событий. Он:

  • ждёт события от ОС (клавиши, мышь, перерисовка окна),
  • вызывает ваши обработчики событий,
  • обновляет интерфейс.
  • Важное правило для игр: не делайте долгие вычисления прямо в обработчиках событий и не используйте бесконечные циклы while True в основном потоке, иначе интерфейс «замрёт».

    Минимальный шаблон окна с Canvas

    highlightthickness=0 убирает рамку фокуса вокруг Canvas — часто полезно для игр.

    Canvas как игровая сцена

    Canvas хранит нарисованные объекты (элементы): линии, прямоугольники, овалы, текст, изображения. Каждый элемент имеет id (целое число), по которому им можно управлять.

    Координаты на Canvas

    Координаты в Tkinter:

  • (0, 0) — левый верхний угол.
  • x растёт вправо.
  • y растёт вниз.
  • Большинство фигур задаются двумя углами прямоугольника-рамки: (x1, y1, x2, y2).

    !Подсказка по системе координат Canvas

    Рисование объектов

    Примеры базовых примитивов:

    Полезные идеи:

  • Для игровых объектов часто убирают outline, чтобы не было рамки.
  • Для текста удобно задавать anchor="nw", тогда координаты — это левый верхний угол текста.
  • Движение объектов: move и coords

    Есть два основных подхода:

  • canvas.move(id, dx, dy) — сдвигает объект на смещение.
  • canvas.coords(id, x1, y1, x2, y2) — устанавливает точные координаты.
  • Пример сдвига:

    Пример чтения координат:

    Для игрового кода обычно удобнее хранить состояние отдельно (например, player_x, player_y, vx, vy), а Canvas использовать как отображение. Но на старте можно опираться на coords, чтобы быстрее увидеть результат.

    Теги (tags): управление группами объектов

    Каждому элементу можно назначить тег:

    Теперь можно работать с группой:

    Теги — это простой способ управлять «партией» объектов (например, пули, враги, эффекты).

    События: клавиатура, мышь и фокус

    События — это то, из чего строится управление. В Tkinter события подключаются через bind.

    Документация по обработке событий в Tkinter:

  • bind и события в Tkinter (раздел документации)
  • Обработчик события: что приходит в параметре event

    Функция-обработчик обычно принимает один аргумент — объект события.

    Часто используемые поля:

  • event.keysym — символическое имя клавиши (например, Left, a, space).
  • event.x, event.y — координаты мыши относительно виджета.
  • Пример печати нажатий:

    Фокус: почему клавиатура иногда «не работает»

    События клавиатуры получает тот виджет, у которого есть фокус. Для игр чаще всего нужно, чтобы фокус был у Canvas.

    Практичный шаблон:

    Если фокус не задан, нажатия могут «улетать» в окно или другой виджет.

    Нажатие и отпускание клавиш: основа плавного управления

    Для игр важно различать:

  • Нажали (старт движения)
  • Отпустили (стоп движения)
  • И хранить состояние клавиш в переменных.

    Пример:

    Теперь в игровом обновлении можно проверять:

  • есть ли "Left" в keys
  • есть ли "Right" в keys
  • И двигать персонажа каждый кадр, а не только в момент нажатия.

    Мышь: движение и клики

    Типовые привязки:

  • <Motion> — движение мыши
  • <Button-1> — клик левой кнопкой
  • <B1-Motion> — движение мыши с зажатой левой кнопкой
  • Пример:

    Для будущей адаптации на мобильные устройства важно привыкать мыслить действиями указателя (tap/drag): мышь в десктопе часто будет соответствовать касаниям на телефоне.

    Игровой таймер: after() вместо time.sleep()

    Почему нельзя sleep в игре

    time.sleep() останавливает выполнение программы, а вместе с ним — обработку событий и перерисовку окна. В итоге окно «подвисает».

    Как работает after()

    after(ms, func) просит Tkinter вызвать func через ms миллисекунд, не блокируя интерфейс.

    Игровой цикл на Tkinter обычно выглядит так:

  • Обновить состояние (позиции, столкновения, очки).
  • Перерисовать (переместить объекты на Canvas, обновить текст).
  • Запланировать следующий шаг через after.
  • Пример каркаса:

    В дальнейшем мы обсудим, как учитывать реальное время между кадрами. Но для первой игры фиксированный шаг в миллисекундах обычно достаточно понятен.

    Простейшая мини-игра: движущийся игрок и «монетка»

    Ниже пример, который соединяет Canvas, события и after() в рабочий игровой скелет.

    Что умеет пример:

  • Управление стрелками (удержание клавиш).
  • «Монетка» случайно появляется в другом месте, если игрок её коснулся.
  • Счёт отображается на Canvas.
  • Интерфейс вокруг Canvas: кнопки, панели, переключение экранов

    Игры часто имеют несколько экранов:

  • Главное меню
  • Экран игры
  • Пауза
  • Экран результата
  • В Tkinter это удобно реализовать двумя базовыми способами.

    Способ A: всё рисовать на Canvas

    Плюсы:

  • Единый стиль.
  • Легче переносить в «игровую» логику.
  • Минусы:

  • Нужно самим реализовывать кнопки (наведение, нажатие, текст).
  • Способ B: использовать Frame и обычные виджеты

    Плюсы:

  • Button, Label и другие элементы уже готовы.
  • Быстро сделать меню.
  • Минусы:

  • Визуально может отличаться на разных ОС.
  • Для «игрового» вида часто нужно больше стилизации.
  • Для первого курса мы будем комбинировать: меню и панели — на виджетах, сам игровой процесс — на Canvas.

    Пример: панель со счётом и кнопкой рестарта

    В следующих статьях мы научимся «связывать» кнопку с функцией, которая сбрасывает состояние игры.

    Подготовка к мобильной адаптации: мыслим масштабом и касаниями

    В этом курсе мы будем адаптировать игры под мобильные устройства позже. Но уже сейчас полезно закладывать привычки:

  • Делайте элементы управления крупнее, чем «для мыши» (кнопки, зоны нажатия, цели).
  • Избегайте управления, требующего точного наведения курсора.
  • Храните размеры и позиции через переменные (W, H, PLAYER_SIZE), чтобы масштабирование было проще.
  • Старайтесь отделять состояние от отрисовки — это облегчает перенос логики в другие интерфейсы.
  • Частые ошибки новичка в Tkinter-играх

  • Использование time.sleep() в игровом цикле.
  • Бесконечный while True вместо after().
  • Клавиатура не работает, потому что не установлен фокус на Canvas (canvas.focus_set()).
  • Попытка «двигать объект» только в обработчике нажатия, без постоянного обновления каждый кадр.
  • Что дальше по курсу

    В следующих материалах мы будем развивать этот фундамент:

  • Скорости, ускорение и более аккуратная физика движения.
  • Столкновения и хитбоксы для разных типов объектов.
  • Спрайты (изображения), простая анимация, слои.
  • Меню, пауза, экраны и структура проекта.
  • Подготовка проекта к адаптации под мобильные устройства.
  • 2. Игровая архитектура: сцены, объекты и главный цикл

    Игровая архитектура: сцены, объекты и главный цикл

    Зачем нужна архитектура, если игра маленькая

    В предыдущей статье мы собрали базовый фундамент: Canvas как сцена, события (bind) для управления и after() для игрового таймера. Но как только игра становится больше, появляются типичные проблемы:

  • Логика меню, игры и паузы смешивается в одну кучу.
  • Становится сложно добавлять новых врагов, бонусы и эффекты.
  • Код управления и отрисовки начинает дублироваться.
  • Игровая архитектура — это способ разложить проект по понятным блокам, чтобы игру было легко расширять и переносить (в том числе готовить к мобильным устройствам).

    !Общая карта: ядро игры переключает сцены, сцена управляет объектами

    Три уровня: Game, Scene, GameObject

    Удобная минимальная структура для Tkinter-игр:

  • Game: создаёт окно и Canvas, запускает главный цикл, маршрутизирует ввод, переключает сцены.
  • Scene: один экран игры (меню, игровой процесс, пауза, результат), хранит свои объекты, обновляет и рисует их.
  • GameObject: отдельный объект мира (игрок, враг, монета, пуля), умеет обновляться и отображаться.
  • Важно: это не единственно правильный вариант, но он прост, масштабируем и хорошо ложится на Tkinter.

    Главный цикл: фиксированные кадры и реальное время

    В Tkinter главный цикл игры строится через after(), как мы делали раньше. Архитектурно в каждом кадре почти всегда выполняются шаги:

  • Считать прошедшее время (опционально, но полезно).
  • Обновить состояние активной сцены.
  • Отрисовать активную сцену.
  • Запланировать следующий кадр.
  • Почему полезно учитывать реальное время:

  • На разных компьютерах кадры могут быть неравномерными.
  • Если в будущем вы захотите менять частоту кадров или переносить игру, логика движения будет стабильнее.
  • Документация по таймеру Tkinter:

  • Метод after в Tkinter (документация Python)
  • Сцены: меню, игра, пауза как отдельные модули

    Сцена — это экран с собственной логикой и набором объектов.

    Типичный набор сцен:

  • MenuScene: кнопка старт, настройки.
  • PlayScene: игрок, враги, очки.
  • PauseScene: затемнение, продолжить, выйти в меню.
  • GameOverScene: итоговый счёт, рестарт.
  • Плюс сцен:

  • Каждая сцена живёт отдельно, и её проще тестировать.
  • Переключение экранов становится одной командой, а не десятками if по всему коду.
  • Как сцена работает с Canvas

    Есть два подхода:

  • Сцена создаёт свои элементы на Canvas и удаляет их при выходе.
  • Сцена переиспользует элементы и просто прячет или перемещает.
  • Для обучения проще первый вариант: при входе сцена создаёт всё нужное, при выходе удаляет.

    Игровые объекты: ответственность и жизненный цикл

    Чтобы объекты не превращались в хаос, удобно договориться о правилах.

    Обычно у GameObject есть:

  • update(dt): изменить состояние (позиции, таймеры, столкновения).
  • render(): применить состояние к Canvas (например, через coords).
  • destroy(): удалить свои элементы с Canvas, если объект исчезает.
  • Почему разделение update и render полезно:

  • Легче искать ошибки: логика отдельно, отрисовка отдельно.
  • Проще оптимизировать.
  • Проще переносить игру на другие движки или оболочки, где отрисовка устроена иначе.
  • Ввод: не привязываем логику к клавиатуре

    В предыдущей статье мы использовали keys = set() и обрабатывали нажатия клавиш. Это хороший старт.

    Архитектурное улучшение: ядро игры принимает события и передаёт их активной сцене.

    Плюсы:

  • В меню одни события, в игре другие.
  • Для мобильной адаптации вы позже замените клавиатуру на касания, а сцена сохранит понятный интерфейс управления.
  • Практический каркас: Game + Scene + Objects

    Ниже рабочий шаблон, который показывает:

  • Главный цикл через after().
  • Активную сцену и переключение сцен.
  • Отдельные игровые объекты.
  • Что здесь важно

  • Game ничего не знает про игрока: он только запускает цикл и переключает сцены.
  • Scene отвечает за свои объекты: создала в on_enter, удалила в on_exit.
  • Player не знает про клавиатуру: ему передают направление, а не конкретные клавиши.
  • Это маленький шаг, который сильно упростит расширение игры.

    Типичные ошибки при переходе к архитектуре

  • Держать все объекты всех сцен на Canvas одновременно и пытаться их прятать без системы.
  • Делать обработчики событий, которые напрямую меняют Canvas, минуя состояние.
  • Хранить “всё обо всём” в глобальных переменных, из-за чего переключение сцен ломает игру.
  • Подготовка к мобильной адаптации на уровне архитектуры

    Когда вы заранее разделяете ввод и игровую логику, перенос упрощается.

    Полезные привычки уже сейчас:

  • Сцена должна принимать управление через методы вроде on_pointer_down(x, y) и on_pointer_move(x, y) (позже добавим), а не только через клавиатуру.
  • Игровые объекты должны опираться на размеры из переменных (W, H, размеры кнопок, размеры хитбоксов).
  • UI-элементы сцены должны быть достаточно крупными, чтобы их можно было нажимать пальцем.
  • Что дальше

    Теперь у нас есть каркас, на который можно “наращивать” механику:

  • столкновения и хитбоксы для разных объектов,
  • спрайты и простая анимация,
  • несколько уровней и сохранение результатов,
  • экран паузы и экран результата как полноценные сцены,
  • управление мышью как шаг к касаниям.
  • 3. Геймплей: анимация, физика, коллизии и спавн

    Геймплей: анимация, физика, коллизии и спавн

    В прошлых статьях мы построили фундамент:

  • Canvas как игровая сцена и after() как таймер.
  • Архитектуру Game → Scene → GameObject, где Game запускает цикл, Scene управляет экраном, а GameObject отвечает за конкретный объект.
  • Теперь добавим то, что делает игру игрой: движение, анимацию, столкновения и появление (спавн) объектов. При этом будем держать в голове будущую адаптацию под мобильные устройства: управление указателем (мышь сейчас, касание позже), крупные элементы, независимость логики от конкретного ввода.

    !Схема: как в кадре связаны ввод, обновление, отрисовка и таймер

    Анимация: от "движения" к "оживлению"

    В Tkinter-анимация почти всегда строится не как "проиграть видео", а как последовательность небольших изменений каждый кадр:

  • смена координат (объект движется),
  • смена цвета или размера (эффект),
  • смена кадра спрайта (персонаж шагает).
  • Варианты анимации на Canvas

  • Геометрическая: двигаем create_rectangle или create_oval через coords.
  • Текстовая: меняем надпись через itemconfig.
  • Спрайтовая: рисуем create_image и периодически меняем картинку.
  • Документация:

  • Tkinter Canvas
  • Спрайты в Tkinter: важный нюанс с памятью

    Если вы используете PhotoImage, нужно хранить ссылки на изображения, иначе Python может удалить объект из памяти, и картинка пропадёт.

    Правило:

  • храните PhotoImage в атрибуте объекта (например, self.frames) или в объекте сцены.
  • Мини-анимация без картинок (подходит для прототипа)

    Даже без спрайтов можно сделать ощущение анимации, например "мигание" или "пульс".

    Идея:

  • заведите таймер anim_t,
  • раз в, например, 0.12 секунды переключайте состояние,
  • в render() применяйте состояние к Canvas.
  • Физика движения: скорость, ускорение и стабильность

    В играх под "физикой" часто понимают простую кинематику:

  • позиция,
  • скорость,
  • иногда ускорение (гравитация, инерция).
  • Почему dt полезнее, чем "двигать на 5 пикселей"

    Если вы двигаете объект на фиксированное число пикселей каждый кадр, скорость зависит от того, насколько стабильно идут кадры.

    Поэтому удобно хранить скорость в пикселях в секунду и умножать на прошедшее время dt.

    В текстовом виде это выглядит так:

  • позиция увеличивается на скорость × прошедшее время, то есть .
  • Где:

  • — насколько сдвинулись по горизонтали за кадр,
  • — горизонтальная скорость (пикселей в секунду),
  • — время между кадрами (в секундах).
  • Мы не пишем эту формулу в коде как KaTeX, а просто делаем умножение в Python.

    Защита от "скачка времени"

    Иногда окно зависает (перетащили мышью, система подвисла), и следующий dt получается слишком большим. Тогда объект может "телепортироваться".

    Практика:

  • ограничивайте dt, например: dt = min(dt, 0.05).
  • Коллизии: как проверять столкновения

    Коллизия — это проверка, пересеклись ли объекты.

    В Tkinter есть два основных подхода.

    Подход A: проверять по данным (рекомендуется)

    Вы храните состояние (позиции, размеры) в переменных объектов и проверяете пересечение сами.

    Плюсы:

  • логика не зависит от Canvas,
  • легче переносить на мобильные оболочки и другие движки.
  • Подход B: опираться на Canvas (coords, bbox, find_overlapping)

    Canvas умеет возвращать ограничивающий прямоугольник объекта, а также искать пересечения.

    Плюсы:

  • быстро для прототипа.
  • Минусы:

  • логика "привязывается" к Canvas, сложнее переносить.
  • Документация:

  • Методы Canvas
  • AABB-коллизии: прямоугольник с прямоугольником

    Для большинства 2D-прототипов достаточно столкновений прямоугольников по AABB-логике.

    !Иллюстрация: как проверяется пересечение прямоугольников (AABB)

    Логика пересечения (словами):

  • если один прямоугольник находится полностью левее другого, пересечения нет,
  • если один полностью правее, пересечения нет,
  • если один полностью выше, пересечения нет,
  • если один полностью ниже, пересечения нет,
  • иначе — пересекаются.
  • Эта проверка быстрая и подходит для:

  • игрока и монет,
  • игрока и препятствий,
  • пуль и врагов.
  • Спавн: как "правильно" создавать новые объекты

    Спавн — появление новых объектов (врагов, бонусов, препятствий).

    Варианты спавна:

  • по таймеру (каждые 0.7 секунды),
  • волнами (10 врагов, пауза, снова),
  • по условию (если очки > 20),
  • случайный (рандомные позиции/типы).
  • Спавн с накопителем времени (таймер через dt)

    Надёжный способ:

  • хранить spawn_timer,
  • на каждом update(dt) делать spawn_timer += dt,
  • если таймер превысил интервал, создавать объект и уменьшать таймер на интервал.
  • Так вы не зависите от FPS и не теряете спавн при нестабильных кадрах.

    Практический пример: "Уклонись" (падение препятствий + сбор монет)

    Цели примера:

  • показать анимацию (простая пульсация монеты),
  • показать физику (скорости в пикселях/секундах + dt),
  • показать коллизии (AABB игрока с объектами),
  • показать спавн (по времени, с рандомом),
  • показать ввод указателем (перетаскивание мышью как шаг к касаниям).
  • Используем архитектуру из прошлой статьи: Game, Scene, объекты с update/render/destroy.

    Код

    Что здесь относится к анимации, физике, коллизиям и спавну

  • Физика: у камней и монет есть скорость vy в пикселях/секундах, а смещение идёт через vy * dt.
  • Анимация: монета "пульсирует" за счёт смены радиуса по таймеру anim_t.
  • Коллизии: игрок, камни и монеты дают прямоугольные хитбоксы, проверка — через aabb_intersect.
  • Спавн: камни и монеты создаются по накопителю времени (rock_spawn_t, coin_spawn_t), позиции по random.
  • Практические советы для роста проекта

    Делайте хитбокс чуть проще, чем картинка

    Если позже добавите спрайты, не обязательно делать коллизии "по пикселям". Для учебных игр лучше:

  • прямоугольники,
  • иногда круги,
  • иногда "уменьшенный" хитбокс, чтобы игра ощущалась честнее.
  • Не смешивайте создание и удаление с логикой кадра без правил

    Хорошие привычки:

  • объект сам умеет destroy(),
  • сцена хранит списки объектов и решает, кого убрать,
  • render() не должен создавать новые объекты.
  • Готовимся к мобильной адаптации уже сейчас

  • Управление через указатель (on_pointer_down/move) переносится на касания почти напрямую.
  • Размер игрока и "опасных" объектов делайте крупнее, чем для мыши.
  • Не завязывайте основной геймплей на клавиатуру: пусть она будет опциональной.
  • Что дальше по курсу

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

  • экраны паузы и результата как отдельные сцены,
  • спрайты и анимация кадрами (несколько изображений),
  • усложнение спавна: волны, уровни сложности, паттерны,
  • подготовка проекта к упаковке и запуску на мобильных устройствах (размеры, соотношение сторон, управление касаниями).
  • 4. Контент и качество: звук, меню, сохранения, оптимизация

    Контент и качество: звук, меню, сохранения, оптимизация

    В прошлых статьях мы сделали фундамент игры на Tkinter:

  • научились рисовать и двигать объекты на Canvas,
  • построили архитектуру Game → Scene → GameObject,
  • добавили геймплей: движение через dt, коллизии и спавн.
  • Теперь перейдём к тому, что превращает прототип в приятную игру:

  • звук (обратная связь),
  • меню и пауза (полноценные экраны, удобные нажатия),
  • сохранения (рекорды, настройки),
  • оптимизация (стабильные кадры и отзывчивость).
  • !Диаграмма, показывающая как звук и сохранения подключаются к архитектуре сцен

    Звук в Tkinter-играх: что реально работает

    Звук в учебной Tkinter-игре чаще всего нужен не как «музыка уровня AAA», а как моментальная обратная связь:

  • взяли монету — короткий звук,
  • проиграли — другой звук,
  • нажали кнопку — щелчок.
  • Ограничения, о которых важно знать

  • Tkinter сам по себе не является аудио-библиотекой.
  • В стандартной библиотеке Python есть winsound, но он только для Windows.
  • Для кроссплатформенного аудио часто подключают сторонние библиотеки (например, pygame), но это уже отдельная зависимость.
  • Документация:

  • Модуль winsound (Python документация)
  • Tkinter (Python документация)
  • Самый простой «звук без зависимостей»

    Tkinter умеет системный сигнал:

    Плюсы:

  • работает быстро,
  • без файлов,
  • подходит для «клик/ошибка».
  • Минусы:

  • звук зависит от настроек ОС,
  • не подходит для разнообразных эффектов.
  • Звуковые эффекты на Windows через winsound.PlaySound

    Если вы на Windows, можно проигрывать .wav.

    Практичное правило для игр: не разбрасывайте winsound.PlaySound по сценам, а заведите один менеджер.

    Как использовать в вашей архитектуре:

  • создайте SoundManager в Game,
  • передавайте его в сцены через game.sound,
  • играйте звуки по событиям (сбор монеты, проигрыш), а не «каждый кадр».
  • Пример подключения к Game:

    И в сцене:

    Важно для мобильной адаптации

    На мобильных устройствах звук почти всегда должен иметь настройку:

  • Sound: On/Off,
  • иногда отдельно Music: On/Off.
  • Это напрямую связано с сохранениями: настройка должна переживать перезапуск приложения.

    Меню, пауза и «ощущение законченности»

    Архитектурно меню и пауза — это сцены:

  • MenuScene: старт, настройки,
  • PlayScene: игровой процесс,
  • PauseScene: продолжить, выйти,
  • GameOverScene: результат и рестарт.
  • Ключевая идея качества: пользователь всегда понимает, что происходит и что нажимать.

    UI на Canvas или виджетах

    Есть два базовых подхода:

  • Виджеты Tkinter (Button, Frame): быстро, удобно, но внешний вид зависит от ОС.
  • Кнопки на Canvas: единый стиль и проще переносить «на палец», но нужно писать обработку нажатий.
  • Для игр и мобильной адаптации полезно уметь второй подход: кнопки на Canvas легко сделать крупными.

    Простейшая кнопка на Canvas как объект

    Сделаем кнопку, у которой есть прямоугольник, текст и проверка попадания (хитбокс).

    Под мобильные устройства важное правило: делайте кнопку большой. Практичный ориентир для учебных проектов: кнопки примерно от 60 до 90 пикселей по высоте, и с отступами.

    Пример меню, которое работает от указателя (мышь сейчас, касание потом)

    Ниже пример сцены меню, которая ловит клики через on_pointer_down(x, y) (то есть архитектурно уже готова к касаниям).

    Если вы хотите визуально «отжимать» кнопку, добавьте on_pointer_up и сбрасывайте set_pressed(False) там. Но даже этот минимальный вариант уже делает игру более «продуктовой».

    Пауза как отдельная сцена или как оверлей

    Есть две практики:

  • Отдельная сцена PauseScene: проще логически, PlayScene полностью не обновляется.
  • Оверлей поверх PlayScene: красивее, но сложнее (нужно оставить фон и заморозить логику).
  • Для учебного курса чаще выгоднее отдельная сцена, потому что меньше скрытых состояний.

    Сохранения: рекорды и настройки без базы данных

    Сохранения нужны даже маленькой игре:

  • рекорд (high score),
  • настройки звука,
  • иногда выбранный скин или сложность.
  • Что не нужно сохранять

    Не сохраняйте то, что относится к «текущему кадру»:

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

    Самый простой формат: JSON

    Плюсы:

  • читаемый,
  • стандартный модуль json,
  • легко расширять.
  • Документация:

  • json (Python документация)
  • pathlib (Python документация)
  • SaveManager: загрузка, сохранение, значения по умолчанию

    Как встроить в архитектуру:

  • в Game.__init__ создайте self.save = SaveManager() и вызовите self.save.load(),
  • настройте звук: self.sound.enabled = self.save.get("sound_enabled", True),
  • при завершении игры обновляйте high_score и вызывайте save().
  • Пример обновления рекорда в PlayScene при проигрыше:

    Оптимизация в Tkinter-играх: что действительно влияет

    Оптимизация в учебных Tkinter-играх обычно не про «ускорить математику», а про то, чтобы:

  • интерфейс не зависал,
  • кадры были стабильными,
  • игра не тратила лишнее на Canvas.
  • Главная ошибка производительности

    Нельзя делать тяжёлые операции в обработчиках событий и в кадре без контроля.

    Плохие признаки:

  • вы создаёте и удаляете десятки объектов Canvas каждый кадр,
  • вы часто вызываете print() в игровом цикле,
  • вы делаете сложные вычисления в update() без необходимости.
  • Практики, которые помогают почти всегда

  • Переиспользуйте объекты: вместо delete/create чаще обновляйте coords.
  • Удаляйте «умершие» объекты пакетно: сначала соберите список на удаление, потом удалите.
  • Меньше элементов Canvas: сотни ещё нормально, тысячи начинают ощущаться.
  • Не спавните слишком часто: ограничивайте объекты по количеству.
  • Стабилизируйте dt: как мы делали раньше, через dt = min(dt, 0.05).
  • Держите ввод и логику раздельно: обработчик события должен записывать состояние, а не «делать игру».
  • Обновляйте HUD только когда он изменился

    Текст на Canvas обновлять каждый кадр не нужно.

    Хорошая практика:

  • хранить прошлое значение,
  • делать itemconfig только при изменении.
  • Мини-измерение FPS без профилировщика

    Иногда достаточно понять: «просели кадры или нет».

    Дальше вы можете раз в секунду обновлять текст FPS: ... в углу.

    Качество с прицелом на мобильные устройства

    Даже если вы пока запускаете игру на ПК, полезно сразу держать в голове мобильные требования:

  • управление через tap/drag, а не через клавиатуру,
  • крупные кнопки и кликабельные зоны,
  • настройка звука в меню,
  • сохранение настроек и рекордов,
  • умеренное количество объектов на экране.
  • Если ваши сцены уже принимают on_pointer_down(x, y) и on_pointer_move(x, y), перенос управления на касания становится технически простым: меняется источник событий, а логика сцены остаётся прежней.

    Что должно получиться после этой статьи

    К концу внедрения идей из этого материала у вас будет не просто прототип, а аккуратная игра:

  • с меню и понятным управлением,
  • со звуковой обратной связью (или хотя бы с переключаемым «звуком»),
  • с сохранением рекордов и настроек,
  • с базовой устойчивостью по кадрам и без лишних операций с Canvas.
  • 5. Адаптация под мобильные: управление, экраны и упаковка приложения

    Адаптация под мобильные: управление, экраны и упаковка приложения

    Реальность про Tkinter и «мобильную адаптацию»

    Tkinter отлично подходит для обучения и быстрых 2D-игр, но у него нет «родной» поддержки Android и iOS как у мобильных движков. Поэтому в практике курса есть два уровня адаптации:

  • Touch-friendly режим: делаем управление и интерфейс удобными для пальца, добавляем масштабирование и работаем в разных соотношениях сторон. Такую игру можно комфортно запускать на сенсорных ноутбуках/планшетах, а также в некоторых мобильных окружениях (с ограничениями).
  • Настоящая мобильная сборка: переносим UI-слой на мобильный фреймворк, а игровую логику переиспользуем благодаря архитектуре из прошлых статей.
  • Ключевая цель этой статьи: сделать так, чтобы у вашей игры появилась переносимая сердцевина (логика, сцены, объекты), а ввод, размеры экрана и упаковка были сменными адаптерами.

    !Иллюстрация идеи переиспользования логики при смене оболочки

    Управление пальцем: один «указатель» вместо мыши и клавиатуры

    В прошлых статьях мы уже сделали важный шаг: сцены принимают ввод через методы вроде on_pointer_down(x, y) и on_pointer_move(x, y). Это почти прямая модель tap и drag.

    Добавляем отпускание: on_pointer_up

    Для кнопок, джойстиков и жестов обычно нужно понимать не только нажатие и движение, но и отпускание.

  • Добавьте метод в базовую сцену.
  • Привяжите событие Tkinter.
  • Почему нельзя полагаться на клавиатуру

    На телефоне клавиатуры может не быть, а на планшете она может появляться и закрывать половину экрана. Поэтому:

  • основной геймплей должен работать без клавиатуры
  • клавиатуру можно оставить как «бонус» для десктопа
  • Виртуальные кнопки на Canvas

    Из прошлой статьи у нас уже есть CanvasButton. Для пальца важно добавить:

  • «нажата» только пока палец внутри зоны
  • срабатывание по отпусканию внутри зоны (типичная мобильная логика)
  • Пример использования в сцене:

    Виртуальный джойстик (упрощённая модель)

    Для игр с движением персонажа популярна схема: палец «тянет» джойстик, а игра получает направление.

  • касание внутри зоны джойстика фиксирует центр
  • смещение пальца задаёт направление
  • отпускание обнуляет направление
  • Даже если вы не реализуете красивый джойстик сразу, полезно стандартизировать выходные данные управления как:

  • move_x в диапазоне от -1 до 1
  • move_y в диапазоне от -1 до 1
  • Тогда игрок двигается одинаково на клавиатуре, мыши и мобильном UI.

    !Как события превращаются в универсальное управление

    Ограничение Tkinter: мультитач

    Важно понимать ограничение: Tkinter на большинстве платформ не даёт полноценные события мультитача (несколько пальцев одновременно). Поэтому для «настоящих» мобильных механик (например, «двумя пальцами стрелять и двигаться») вам почти наверняка понадобится другая оболочка.

    Документация по событиям Tkinter:

  • Tkinter: Events and Bindings
  • Экраны и масштабирование: чтобы игра выглядела нормально на разных размерах

    Мобильные устройства отличаются:

  • разрешением
  • плотностью пикселей
  • соотношением сторон (16:9, 19.5:9, 4:3)
  • ориентацией (портрет/ландшафт)
  • Базовая идея: логическое разрешение и масштаб

    Практичный подход для учебной 2D-игры:

  • выбираем логическое (дизайнерское) разрешение, например W=800, H=450
  • вычисляем масштаб под реальный экран
  • рисуем всё в логических координатах, а при отрисовке умножаем на масштаб
  • Масштаб можно посчитать так:

    Где:

  • и — логическая ширина и высота вашей игры
  • и — реальная ширина и высота экрана (или окна)
  • — масштаб, который сохраняет пропорции и помещает игру целиком
  • Если соотношение сторон не совпадает, появятся поля (letterbox). Это нормально и часто лучше, чем растягивать картинку.

    Viewport: масштаб и смещение

    Вам понадобится преобразование:

  • логические координаты (x, y)
  • экранные координаты (X, Y)
  • Смещение нужно, чтобы центрировать «поле игры».

    Чтобы узнать размер экрана в Tkinter:

    Документация:

  • Tkinter: Widget methods
  • Важный нюанс: ввод тоже нужно «обратно» переводить

    Если вы рисуете в масштабе, то координаты указателя (event.x, event.y) приходят в экранных пикселях. Для логики сцены часто удобнее работать в логических координатах.

    Добавьте обратное преобразование:

    Тогда сцена получает «одинаковый мир» независимо от устройства.

    Крупные элементы и безопасные отступы

    Для пальца полезны простые правила:

  • высота кнопки обычно не меньше 60–90 пикселей в экранных координатах
  • кликабельная зона должна быть больше текста или иконки
  • UI лучше размещать с отступом от краёв
  • Даже без «safe area» (как на iOS) отступы помогают, потому что края экрана часто неудобны для нажатия.

    Переключение ориентации

    Если вы хотите поддержать портрет и ландшафт, проще всего:

  • Сделать игру «ландшафтной» по умолчанию.
  • В портрете показывать «поля» сверху и снизу или менять компоновку UI.
  • В учебном проекте достаточно честного решения: поддерживать одну ориентацию, а вторую запускать с полями.

    Упаковка приложения: что реально можно сделать

    Упаковка для Windows/macOS/Linux (просто и полезно)

    Для десктопа стандартный вариант — PyInstaller.

  • PyInstaller Documentation
  • Типовая команда:

    Чтобы ассеты (картинки, звуки, сохранения) работали и из исходников, и из собранного файла, делайте функцию получения пути к ресурсам.

    Это важная часть «качества», потому что на мобильных сборках вопрос путей к ресурсам тоже всегда возникает.

    Android и iOS: честная развилка

    Вариант A: запустить Tkinter на Android как окружение Python

    Это возможно в некоторых сценариях, но обычно неудобно для пользователей и не похоже на «настоящее мобильное приложение». Такие подходы используют:

  • Termux
  • X11-сервер/графическая оболочка поверх Android
  • Официальная страница Termux:

  • Termux (GitHub)
  • Этот путь годится для экспериментов, но редко подходит как «продукт».

    Вариант B: переиспользовать логику и заменить UI-слой на мобильный

    Это тот вариант, ради которого мы строили архитектуру Game → Scene → GameObject и отделяли ввод от логики.

    Два популярных направления:

  • Kivy: мобильный UI/графика, хорошо подходит для игр
  • BeeWare: набор инструментов для упаковки Python-приложений под мобильные платформы, UI через Toga
  • Официальные ресурсы:

  • Kivy Documentation
  • BeeWare Project
  • Briefcase Documentation
  • Смысл миграции:

  • ваш core остаётся почти прежним: объекты, сцены, правила, спавн, коллизии
  • меняются:
  • - отрисовка (не Canvas, а виджеты/рисование фреймворка) - источник ввода (touch события фреймворка) - упаковка и доступ к ресурсам

    Как подготовить проект к такой миграции заранее

    Удобная структура проекта:

  • core/
  • - логика сцен - игровые объекты - математика, коллизии - менеджеры сохранений
  • ui_tk/
  • - Game на Tkinter - адаптер ввода Tkinter -> on_pointer_* - рендер через Canvas
  • ui_mobile/ (появится позже)
  • - Game на Kivy или BeeWare - адаптер ввода touch -> on_pointer_* - рендер в выбранной технологии

    Главный признак готовности: в core нет импортов tkinter.

    Мини-чеклист «мобильно-дружелюбной» Tkinter-игры

  • Управление
  • - игра запускается и играется без клавиатуры - есть on_pointer_down/move/up - кнопки срабатывают по отпусканию внутри зоны
  • Экран
  • - есть логическое разрешение и масштабирование - ввод переводится в логические координаты - UI с отступами и крупными зонами нажатия
  • Производительность и устойчивость
  • - dt ограничивается, например dt = min(dt, 0.05) - HUD обновляется только при изменениях - число объектов на Canvas контролируется
  • Сборка
  • - десктопная сборка через PyInstaller - ресурсы грузятся через функцию resource_path

    Что дальше по курсу

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

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

  • выделить core модуль (логика без Tkinter)
  • сделать адаптеры ввода и координат (viewport)
  • собрать десктопный билд и проверить ресурсы
  • выбрать направление мобильной оболочки (Kivy или BeeWare) и перенести только UI-слой, оставив core без изменений