Игра готова: у неё есть меню, пауза, уровни, звук, рекорд на диске, и вы уже умеете собрать её в один файл для друзей. Осталось последнее — отойти на шаг назад и увидеть «Комету» целиком, как видит её автор, который помнит, что за что отвечает. Тогда следующую идею вы будете вставлять в нужное место, а не искать наугад.
Что получилось: «Комета» по файлам
Представьте небольшую студию. В main.py сидит режиссёр: он включает свет, следит за временем и решает, какую сцену показывать. В sprites.py работают актёры: у каждого свой вид и своё движение. В logic.py лежит свод правил, который не знает ни про сцену, ни про зрителей. В settings.py — доска с цифрами, которые можно крутить, не лезя в сценарий.
Это и есть главный приём курса: логика отдельно от отрисовки. Поэтому logic.py проверяется без окна, а задачи в этом курсе проверяли именно её.
| Файл | За что отвечает | Главные имена |
|---|---|---|
main.py | окно, игровой цикл, экраны и состояния | handle_event, update_play, draw_play, draw_menu, draw_pause, draw_gameover, draw_frame, new_game, load_record, save_record |
sprites.py | всё, что можно нарисовать и подвигать | Player, Meteor, Crystal, Laser, Explosion, load_image, load_frames, load_sound |
logic.py | правила игры на чистом Python, без pygame | step_ship, SpawnTimer, Cooldown, next_state, handle_key, level_for_score, parse_record, resource_dir |
settings.py | все числа и цвета в одном месте | WIDTH, HEIGHT, FPS, PLAYER_SPEED, INVULNERABLE_TIME, SCORE_PER_LEVEL |
check_logic.py | быстрые проверки logic.py через assert | запуск без окна, за секунду |
make_assets.py и assets/ | скрипт рисует картинки и звуки, папка их хранит | ship.png, meteor.png, shot.wav, music.wav |
record.json | рекорд между запусками | {"record": 1234} |
Управление в готовой игре
Запомните и клавиши: каждая из них проходит через handle_key или get_pressed().
- Меню: стрелки вверх и вниз выбирают пункт,
Enterподтверждает,Escзакрывает игру. - Полёт: стрелки или
WASDдвигают корабль,Пробелстреляет,PилиEscставят паузу. - Пауза:
PилиEscпродолжают игру,Qвозвращает в меню. - Игра окончена:
Enterначинает заново,Qведёт в меню,Escзакрывает игру. - Везде:
Mвключает и выключает звук,F1показывает и прячет рамки столкновений.
Путь одного кадра
Игра 60 раз в секунду проходит один и тот же путь. Посмотрим его целиком, по именам из вашего main.py:
dt = clock.tick(FPS) / 1000— узнаём, сколько секунд прошло с прошлого кадра. Все скорости и таймеры дальше считаются от этого числа.handle_event(event)— для каждого события из очереди: нажатая клавиша превращается в имя черезpygame.key.name,handle_key(state, name)отвечает, что это значит в текущем состоянии, аnext_stateменяет состояние.update_play(dt)— только еслиstate == "play". Таймерыinvulnerable,Cooldown,SpawnTimer; рождение метеоров, кристаллов и выстрелов;all_sprites.update(dt, keys)сдвигает всех; затем столкновения, очки, жизни, смена уровня. Если жизни кончились,next_state(state, "die"), и рекорд уходит вrecord.json.draw_frame()— заливкаBG_COLOR, звёзды, затемdraw_play,draw_menu,draw_pauseилиdraw_gameoverпо состоянию.pygame.display.flip()— готовая картинка выводится на экран, и всё начинается заново.
Проверим схему на примере. Вы нажали P посреди игры. Событие KEYDOWN попадает в handle_event, имя клавиши "p", handle_key("play", "p") отвечает "pause", next_state переводит игру в "pause". На следующем кадре условие state == "play" ложно, и update_play не зовётся: метеоры замерли. А draw_frame рисует и мир, и поверх него draw_pause. Вся пауза — это один if в цикле и одна запись в таблице переходов.
С файлами то же самое. Картинки и звуки ищутся через ASSETS, который строится от logic.resource_dir, и поэтому игра находит их и рядом с исходниками, и внутри сборки PyInstaller. Рекорд лежит по пути RECORD_PATH, рядом с программой.
Идеи развития: где что менять
У каждой хорошей идеи есть своё место в проекте. Если сразу понять, где она будет жить, работа превращается из «не знаю, с чего начать» в список маленьких шагов.
| Идея | Правило в logic.py | Картинка и объекты | Подключение в main.py |
|---|---|---|---|
| Бонус-щит | сколько секунд защиты и как она складывается с invulnerable | класс Shield в sprites.py по образцу Crystal; картинка в make_assets.py | группа shields, таймер появления, при подборе — invulnerable растёт |
| Босс | запас здоровья и урон: функция, которая возвращает новое здоровье | класс Boss с большим radius | появляется при определённом level, выстрелы отнимают здоровье |
| Второе оружие | перезарядка и смещения выстрелов | новый класс по образцу Laser | своя клавиша и свой Cooldown в update_play |
| Таблица рекордов | parse_record читает список, новая функция вставляет результат и обрезает до пяти | — | record.json хранит список; экран "gameover" показывает строки |
Заметьте: у каждой идеи первая колонка про правила. Это не случайно. Сначала вы договариваетесь с игрой о числах и условиях, проверяете их без окна, и лишь потом рисуете.
Покажем это на самом маленьком шаге щита. Допустим, подобранный щит даёт пять секунд защиты, а если защита уже есть, берётся большее значение: бонус не должен её укорачивать.
SHIELD_TIME = 5.0
def pick_shield(invulnerable, shield_time):
return max(invulnerable, shield_time)
assert pick_shield(0.5, SHIELD_TIME) == 5.0
assert pick_shield(8.0, SHIELD_TIME) == 8.0Разбор по строкам
SHIELD_TIME = 5.0— число живёт отдельно, как вsettings.py: захотите семь секунд, поменяете в одном месте.def pick_shield(invulnerable, shield_time)— правило в виде функции. Ни спрайтов, ни окна, только числа.return max(invulnerable, shield_time)— берём большее из двух: оставшуюся неуязвимость или время щита.assert ... == 5.0— проверка: при почти закончившейся неуязвимости щит даёт полные пять секунд.assert ... == 8.0— проверка: щит не отнимает лишнего у того, у кого защиты больше.
Маленькими шагами
Работайте так: правило с проверкой, затем объект, затем подключение, и после каждого шага запускайте игру. Если что-то сломалось, виноват последний шаг, а не вся неделя работы.
Чему вы научились
Вы начинали с чёрного окна, а закончили игрой, у которой есть состояния, звук и сохранение. Важнее другое: почти всё, что вы освоили, не принадлежит ни Pygame, ни Python. Это общий язык игровых движков, просто в каждом он называется по-своему.
| В «Комете» | Что это такое | Как встретите в Unity |
|---|---|---|
while running и clock.tick | игровой цикл: кадр за кадром | метод Update(), который движок зовёт каждый кадр |
dt | время кадра в секундах, чтобы скорость не зависела от FPS | Time.deltaTime |
Sprite и Group | объекты игры и наборы объектов | игровые объекты (GameObject) на сцене |
Rect и круги столкновений | форма, по которой проверяются касания | коллайдеры (Collider) |
state и next_state | состояния игры и переходы между ними | сцены и конечные автоматы |
assets/ | картинки и звуки отдельно от кода | папка Assets проекта |
Поэтому переходить на другой движок вам будет проще, чем кажется. Незнакомыми будут кнопки и названия, а вопросы останутся прежними: что происходит каждый кадр, как зависит скорость от времени, кто с кем сталкивается и в каком состоянии сейчас игра.
Есть и вторая, менее заметная победа: привычка разделять правила и картинку. Задача в конце урока как раз об этом. Весь мир — обычный словарь, один кадр — одна функция, окна нет, а проверить можно за мгновение.
Куда дальше
Дальше две дороги, и они не исключают друг друга.
- Python. Если во время курса вы чувствовали, что классы, словари и файлы вам даются с усилием, пройдите бесплатный курс по Python: https://sudoteach.com/course/python. Там основы разобраны подробнее, включая объектно-ориентированное программирование. Хорошо подтянутый Python сделает следующую игру вдвое быстрее.
- Unity. Если хочется 3D и большие проекты, переходите на курс по Unity: https://sudoteach.com/course/unity. Игры там пишутся на C#, а идеи те же, что в таблице выше:
Update, время кадра, коллайдеры.
Что делать прямо сейчас
Не откладывайте. Выберите одну идею из таблицы, разбейте её на три шага и сделайте первый сегодня.
Практика: проверьте и выберите шаг
Нового кода в игре сегодня нет. Ваша задача — пройти проект как игрок и как автор, а затем выбрать следующий шаг.
- Откройте терминал в папке
kometa/с включённым виртуальным окружением и запустите проверки логики:python check_logic.py. Должно появиться «Все проверки пройдены». - Запустите игру:
python main.py. Пройдите весь круг: меню, игра, пауза клавишей P, продолжение, потеря всех жизней, «Игра окончена», Enter для новой партии. - Закройте игру и откройте снова. Рекорд в меню должен остаться на месте — значит, файл
record.jsonработает. - Выберите идею из таблицы и запишите на листке три шага: правило, объект, подключение.
- Соберите игру в
dist/и покажите её хотя бы одному человеку. Лучше посмотреть, как друг играет молча, чем объяснять ему управление.
python check_logic.py
python main.py- Могу ли я назвать пять шагов одного кадра по именам из своего
main.py? - Я знаю, в каком файле лежит число
1.5(время неуязвимости), а в каком — таблица клавишKEY_ACTIONS? - Проверки из
check_logic.pyпроходят без запуска окна? - Рекорд сохраняется между запусками, а игра с чистой папкой (без
record.json) не падает? - У меня записаны три шага для одной следующей идеи?
Частые ошибки (как советы)
- Переписывать всё с нуля. Новый проект кажется чище, но через неделю он станет таким же. Вместо этого доработайте старый маленькими шагами: рабочая игра на каждом шаге.
- Бросить, не показав. Игра, в которую никто не играл, не покажет своих слабых мест. Отдайте её друзьям сегодня, даже если она «ещё сырая».
- Делать три идеи сразу. Босс, щит и ракеты в один вечер приводят к ошибке, у которой не видно виновника. Одна идея, один запуск.
- Начинать с картинки. Красивый босс без правил про его здоровье придётся переделывать. Сначала правило и проверка, потом внешний вид.
- Сдаться после первой ошибки. Читайте последнюю строку сообщения, ищите имя файла и номер строки: так вы исправите её быстрее, чем кажется.
Что важно запомнить
- «Комета» держится на разделении:
logic.py— правила,sprites.py— объекты,main.py— цикл и экраны,settings.py— числа. - Один кадр:
dt, события, обновление мира (только в"play"), отрисовка,flip. - Новая идея начинается с правила и проверки в
check_logic.py, а не с картинки. - Игровой цикл,
dt, объекты, коллайдеры и состояния есть в любом движке, включая Unity. - Развивайте проект маленькими шагами и показывайте его людям.
Вы дошли до конца курса — а значит, умеете довести проект до готового файла. Следующий курс (Python или Unity) начнёт с того места, где остановилась «Комета».